在C ++和Java或他们尊重的规则中,对覆盖抽象方法有什么限制。必须匹配参数或返回类型。我通常看到只使用返回类型而没有参数实现的抽象函数,是由派生类来指定其余的。它是如何工作的?
答案 0 :(得分:4)
方法覆盖必须具有覆盖的父方法的相同方法签名,否则不会调用覆盖。
<强>爪哇强>:
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 Abstract Interface上的我的问题)。
这里至关重要的是在这种情况下方法可见性不会改变(因为它是分层可见性,即私有 - >受保护 - >公共)。这是有效的:
public abstract class AbstractTest {
protected abstract void test() throws Exception;
}
public class ConcreteTest extends AbstractTest {
@Override
public void test() throws Exception {
}
}
(父类有一个受保护的方法,子类可以覆盖相同的方法,只有2个可见性选择:protected或public)。
另外,假设你有
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中所述)。协变返回类型也适用于受保护和私有继承。更多关于Covariant return types。
答案 1 :(得分:2)
我不了解Java,但在C ++中,您必须指定完全相同的参数类型。返回类型 - 另一方面 - 是协变类型,这意味着如果在原始函数中返回指向类型A的指针或引用,则覆盖函数可以返回指针或对类型B的引用,只要B是A,或直接或间接来自它。
正如Als所指出的,必须将一个函数声明为虚拟才能被覆盖。由于OP明确询问了virtual
和=0
定义的抽象方法,因此不需要指出这一点。但是,我想更清楚地表明,重写函数不需要被声明为虚拟。正如所引用的标准所述,与声明为virtual的基本成员函数的签名(使用宽松规则,对于协变类型)匹配的成员函数将是一个覆盖,无论它是否被指定为虚拟。也就是说,覆盖函数不需要声明为虚拟;另一方面,抽象成员函数必须是。
答案 2 :(得分:1)
这两种语言在覆盖自然语义差异的要求方面是相似的。基本上两者都需要对调用代码(即参数)完全相同的约束,并在处理时提供相同或更严格的保证。这听起来有点模糊,但如果你记住这一点很简单。
什么时候是覆盖
对于覆盖基类成员的成员函数(方法),两种语言都要求函数是多态的(C ++中为virtual
,而Java中不是final
)具有相同的名称和相同的数字是一种参数。有些语言允许使用 contra-variant 参数类型,但Java和C ++都没有。
协变返回类型
这里的协变意味着返回类型的类型以与实现成员函数的类型相同的方式进行更改。也就是说,派生函数返回的类型必须是 polymorphic ,并且与基类中声明的相同类型相同或派生。 Java是引用语言,因此所有返回类型都可以表现出除 primitive 类型之外的多态性。 C ++是 value 语言,只有引用和指针是多态的。这意味着在Java中,返回的类型必须完全匹配或者是引用类型,并从基类返回的类型派生。在C ++中,它必须是引用或指针到相同或派生类型。与介绍中一样,原因是如果您通过基础调用成员函数,您将拥有匹配您期望的对象。
例外规范
异常规范在C ++中并不常见,但它们都是Java语言。在这两种语言中,尽管覆盖的方法是相同的:派生类中的重写方法必须对可以抛出的内容有更严格的约束。这里语言表面的差异,因为Java只验证已检查的异常,因此它将允许未被基类抛出的派生类型中的未经检查的异常。另一方面,派生函数不能添加基类中不存在的新已检查异常,同样,协方差发挥作用,派生函数可以抛出协变异常。在C ++中,异常规范具有完全不同的含义,但是以相同的方式,派生类型中的规范必须比基础中的规范更具约束性,并且它还允许协变异常规范。
基本原理是相同的,如果你通过对基类型的引用在一个调用周围编写一个try {} catch() {}
块,并且它捕获了在基类中声明的所有异常,对覆盖的调用将会将所有异常捕获在相同的块中 - 除了Java中可能未经检查的异常。
访问修饰符
在Java中,派生方法的访问规范必须至少与基类的访问规范一样,即,如果基函数声明指定protected
,则派生函数不能是public
,但另一方面可以private
,有趣的是Java不允许你覆盖基类中的private
函数。
在C ++中,访问说明符不会用于覆盖,您可以根据需要修改访问说明符,使其在派生类中具有或多或少的限制性。顺便提一下,您可以覆盖基类中的private
成员(声明为virtual
),这通常用于实现NVI模式(非虚拟接口),必须通过{实现Java中的{1}}方法。
停止覆盖
Java允许您通过将成员函数标记为protected
或者使其成为final
来打破任何级别的覆盖链。在C ++(当前标准)中,您无法在任何时候打破覆盖链,即使在最终覆盖无法访问其覆盖的成员函数的情况下也是如此,这会产生奇怪的效果:
private
在该示例中,由于继承在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
级别派生的{1}}(base
无法在base::f()
内编译的原因)但另一方面,通过实现具有签名most_derived::f()
的函数,它为{{1}提供了覆盖}}。对void ()
对象的base::f
号调用将发送至g()
,而most_derived
对象的调用将发送至most_derived::f()
。
答案 3 :(得分:0)
爪哇:
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 ++也是如此,即重写抽象方法的签名相同。
答案 4 :(得分:0)
您在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/
Wiki也有一个页面en.wikipedia.org/wiki/Method_overriding。 抽象方法可以有参数。没有限制。在许多情况下,传递参数可能没有意义。希望这会有所帮助:)
答案 5 :(得分:0)
方法的签名(返回类型,参数的类型和数量)应该在派生类中与基类完全匹配。否则派生类也将变得抽象。
示例:
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:错误:为'virtual int bar :: foobar(int)指定的冲突返回类型' test.cc:2:错误:覆盖'virtual void foo :: foobar(int)'
正如@Als所提到的,Covariant Return Type是一个例外,其中返回类型可以不同。不同的是,我的意思是不同的类型应该与每种类型兼容。 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;
}