C++和Java中的抽象方法和覆盖函数

abstract methods and overiding function in C++ and Java

本文关键字:覆盖 函数 抽象方法 Java C++      更新时间:2023-10-16

在C++和Java中,或者它们的尊重规则中,对覆盖抽象方法施加了哪些限制。 必须与参数或返回类型匹配。 我通常看到抽象函数只用返回类型实现而没有参数,是否由派生类指定其余部分。 它究竟是如何工作的?

方法重写必须具有与它重写的父方法相同的方法签名,否则它不称为重写。

爪哇

public abstract class AbstractTest {
    public abstract void test() throws Exception;
}
public class ConcreteTest extends AbstractTest {
    @Override
    public void test() throws Exception {
    }
}

如您所见,ConcreteTest(扩展AbstractTest(必须覆盖test() 。它们具有相同的方法名称、返回类型,并且没有方法参数。子类可以省略从基类引发的异常并引发自己的异常。子类还可以添加其他(未(检查的异常。

正如Peter Lawrey所提到的,java接口方法是隐式抽象方法(请参阅我关于Java抽象接口的SO问题(。

这里的关键是方法可见性在这种情况下不能更改(因为它是分层可见性,即私有>保护>公共(。不过这是有效的:

public abstract class AbstractTest {
    protected abstract void test() throws Exception;
}
public class ConcreteTest extends AbstractTest {
    @Override
    public void test() throws Exception {
    }
}

(父类有一个受保护的方法,子类可以覆盖相同的方法,并且只有 2 个可见性选择:受保护或公共(。

另外,假设你有

public class B {
}
public class D extends B {
}
public abstract class Base {
    public abstract B foo();
}
public class Derived extends Base {
    @Override
    public D foo() {
        // TODO Auto-generated method stub
        return new D();
    }
}

您将看到Derived返回D而不是B。为什么?这是因为派生类遵循与父类相同的签名,并且派生类的返回类型是父类的返回类型的subtype

所以,我可以有这个:

Base pureBase = new Derived();
B b = pureBase.foo(); //which returns class D
if (b instanceof D) {
   //sure, it is, do some other logic
}

在C++中,您可以使用协变返回类型获得类似的效果

C++

class AbstractTest {
public:
    virtual void test() = 0;
};

class ConcreteTest : AbstractTest {
public:
    void test() {
        //Implementation here...
    }
};

在C++中,具有纯虚函数(以=0结尾的虚函数(的类称为抽象类。子类(在C++中,类扩展由:分隔(覆盖纯虚拟方法(除了它不包含=0(。它具有相同的签名,具有其父类。

回到我们的 Java 示例,假设您有:

class B {
};
class D : B {
};
class Base {
public:
    virtual B* foo() = 0;
}
class Derived : Base {
public:
    D* foo() {
        return new D();
    }
}

同样的推理(如Java中所解释的(在这里完成。协变返回类型也适用于受保护和私有继承。详细了解协变返回类型。

我不

了解Java,但是在C++中,您必须指定完全相同的参数类型。另一方面,返回类型是协变类型,这意味着如果在原始函数中返回对类型 A 的指针或引用,则只要 B 是 A,或者直接或间接派生自 B 的,重写函数就可以返回指向类型 B 的指针或引用。

正如 Als 所指出的,函数必须被声明为虚拟才能被覆盖。由于OP明确询问了抽象方法,这些抽象方法既virtual=0定义,因此没有必要指出这一点。但是,我想特别明确一点,覆盖函数不需要声明为虚拟。正如引用的标准所说,与声明为 virtual 的基本成员函数的签名(对于协变类型,具有宽松的规则(匹配的成员函数将是覆盖,无论它是否指定为 virtual。也就是说,重写函数不需要声明为虚拟;另一方面,抽象成员函数必须是。

这两种语言在用自然语义差异覆盖的要求方面是相似的。基本上两者都需要对调用代码(即参数(进行完全相同的约束,并在处理上提供相同或更严格的保证。这听起来可能有点模糊,但如果你记住这一点,这很简单。

什么时候是覆盖

对于覆盖基类成员的成员函数(方法(,两种语言都要求函数是多态的(virtual在C++中,而不是在Java中final(具有相同的名称和相同的参数类型。有些语言允许反向变量参数类型,但Java和C++都没有。

协变返回类型

这里的协变意味着返回类型的类型以与实现成员函数的类型相同的方式更改。也就是说,派生函数返回的类型必须是多态的,并且与基类中声明的相同类型相同或派生自同一类型。Java 是一种参考语言,因此除基元类型外,所有返回类型都可以表现出多态性。C++是一种语言,只有引用指针是多态的。这意味着在 Java 中,返回的类型必须完全匹配,或者是引用类型,并且是从基返回的类型派生的。在C++中,它必须是指向相同或派生类型的引用指针。如介绍所示,原因是如果您通过基调用成员函数,您将拥有一个与预期匹配的对象。

异常规范

异常规范在C++中不是很常见,但在 Java 中却很常见。在这两种语言中,尽管重写的方法是相同的:派生类中的重写方法必须对可以抛出的内容有更严格的约束。语言的差异在这里浮出水面,因为 Java 只验证检查异常,因此它将允许未由 base 抛出的派生类型中未经检查的异常。另一方面,派生函数不能添加基类中不存在的新检查异常,同样,协方差开始发挥作用,派生函数可以抛出协变异常。C++异常规范具有完全不同的含义,但以同样的方式,派生类型中的规范必须比基类型中的规范更受约束,并且它还允许协变异常规范。

基本原理是相同的,如果你通过对基类型的引用围绕调用编写一个try {} catch() {}块,并且它捕获了在基中声明的所有异常,那么对覆盖的调用将在同一块中捕获所有异常 - 除了 Java 中可能未经检查的异常

访问修饰符

在Java中,派生方法的访问规范必须至少与基的访问规范一样严格,也就是说,如果基函数声明指定protected,则派生函数不能public,但另一方面可以private,有趣的是Java不允许你覆盖基类中的private函数。

在C++中,访问说明符不会用于重写,您可以根据需要修改访问说明符,使其在派生类中或多或少受到限制。顺便说一下,您可以覆盖基类中的private成员(声明为 virtual (,该成员通常用于实现 NVI 模式(非虚拟接口(,该模式必须通过 Java 中的protected方法实现。

停止覆盖

Java 允许您打破任何级别的覆盖链,方法是将成员函数标记为 final 或使其private。在 C++(当前标准(中,您不能在任何时候破坏覆盖链,即使在最终覆盖器无法访问它正在覆盖的成员函数的情况下也是如此,这会产生奇怪的效果:

struct base {
   virtual void f() {}
};
struct derived : private base {
   void g() {
      f();
   }
};
struct most_derived : derived {
   void f() {                    // overrides base::f!!!
      //base::f();               // even if it does not have accesss to it
   } 
};

在该示例中,由于继承在derived级别是私有的,因此most_derived无法访问base子对象,从其角度来看,它不会从base派生(base::f()无法在most_derived::f()内编译的原因(,但另一方面,通过实现具有签名的函数void ()它为base::f提供了覆盖。对most_derived对象的g()调用将被调度到most_derived::f(),而在derived对象上,将调度给base::f()

Java:

abstract class MyAbstract {
    abstract String sayHelloTo(String name);
}
final class SayEnglish extends MyAbstract {
    @Override
    public String sayHelloTo(String name) {
        return "Hello, " + name + "!";
    }
}
final class SayLatin extends MyAbstract {
    @Override
    public String sayHelloTo(String name) {
        return "Lorem, " + name + "!";
    }
}
对于

考虑语法差异的C++也是如此,即覆盖抽象方法的相同签名。

Java 中的覆盖方法应与要重写的抽象方法具有相同的签名。此外,您不能限制超过父类的访问。请参阅 http://download.oracle.com/javase/tutorial/java/IandI/override.html

我假设你的意思是C++。与 java 相同,覆盖方法签名应与被覆盖方法签名匹配。见 http://www.learncpp.com/cpp-tutorial/126-pure-virtual-functions-abstract-base-classes-and-interface-classes/

维基有一个页面太 en.wikipedia.org/wiki/Method_overriding。抽象方法可以有参数。对此没有限制。在许多情况下,传递参数可能没有意义。希望这对:)<</p>

div class="answers"有所帮助>

方法的签名(返回类型,参数的类型和数量(在派生类中应该与基类的签名完全匹配。 否则派生类也将变得抽象。

例:

struct foo{
    virtual void foobar( int myNum) = 0;
};
struct bar: foo{
    int foobar(int myNum ){}    
};
int main(){
    foo *obj = new bar();
    return 0;
}

test.cc:6:错误:为"虚拟整数栏::foobar(int(">
指定的返回类型冲突 test.cc:2:错误:覆盖"虚拟虚空 foo::foobar(int(">

如@Als所述,协变返回类型是返回类型可以不同的例外。通过不同,我的意思是不同的类型应该与每个类型兼容。C++ 中派生类类型的指针/引用与基类型的指针/引用兼容。

链接中的示例:

#include <iostream>
// Just create a class, and a subclass
class Foo {};
class Bar : public Foo {};
class Baz
{
  public:
  virtual Foo * create()
    {
      return new Foo();
    }
};
class Quux : public Baz
{
  public:
  // Different return type, but it's allowed by the standard since Bar
  // is derived from Foo
  virtual Bar * create()
    {
      return new Bar();
    }
};
int main()
{
  Quux *tmp = new Quux();
  Bar *bar = tmp->create();
  return 0;    
}