为啥继承在 Java 和 C++ 中的行为不同,超类调用(或不调用)子类的方法?
Posted
技术标签:
【中文标题】为啥继承在 Java 和 C++ 中的行为不同,超类调用(或不调用)子类的方法?【英文标题】:Why does inheritance behave differently in Java and C++ with superclasses calling (or not) subclasses' methods?为什么继承在 Java 和 C++ 中的行为不同,超类调用(或不调用)子类的方法? 【发布时间】:2018-04-12 11:40:56 【问题描述】:我在 Java 和 C++ 中写过 - 似乎是 - 完全相同的继承示例。看到这些程序的不同输出,我真的很惊讶。让我分享一下代码 sn-ps 和相应的输出。
C++ 代码:
class A
public:
A()
void sleep()
cout << "A.Sleep" << endl;
eat();
void eat() cout << "A.Eat" << endl;
;
class B: public A
public:
B()
void sleep()
A::sleep();
cout << "B.Sleep " <<endl;
this->eat();
void eat()
cout << "B.Eat" << endl;
run();
void run()
A::sleep();
cout << "B.run" << endl;
;
int main()
B *b = new B();
b->sleep();
输出:
A.Sleep
A.Eat
B.Sleep
B.Eat
A.Sleep
A.Eat
B.run
executed successfully...
Java 代码:
class A
A()
void sleep()
System.out.println("A.Sleep");
this.eat();
void eat() System.out.println("A.Eat");
;
class B extends A
B()
@Override
void sleep()
super.sleep();
System.out.println("B.Sleep");
this.eat();
@Override
void eat()
System.out.println("B.Eat");
run();
void run()
super.sleep();
System.out.println("B.Run");
public class Test
public static void main(String[] args)
B b = new B();
b.sleep();
输出:
A.Sleep
B.Eat
A.Sleep
B.Eat
A.Sleep
......
......
......
(Exception in thread "main" java.lang.***Error)
我不知道为什么这两个继承示例的行为不同。它不应该同样工作吗?
这种情况的解释是什么?
【问题讨论】:
在 Java 中,所有方法都是虚拟的(即可以被覆盖)。在 C++ 中它们不是。如果你给一个子类一个与基类中的非虚拟方法同名的方法,它们只是两个名称相似的方法。 我会将标题/问题更改为“What 在这种情况下 C++ 和 Java 继承有什么区别?”,因为 为什么?只是因为c++和java是两种不同的语言 为什么不一样呢?在两种不相关的语言之间进行比较通常会适得其反。 因为Java中的方法默认是dynamic bounded。 “我用 Java 和 C++ 编写了完全相同的继承示例”——不,你没有。您试图尽可能接近相同的语法(但使用 override 关键字做得不够)。 【参考方案1】:在您的 C++ 示例中,您是 hiding 基本方法,但您不会覆盖它们。所以它们实际上是不同的方法,只是碰巧有相同的名称。如果你打电话
A* a = new B();
a->sleep();
它实际上会打印"A.Sleep"
。如果你想重写一个方法,你需要在基类中声明它virtual
(在所有子类中也自动使其成为虚拟)。您可以在this post 中阅读有关 C++ 中函数隐藏与覆盖的更多信息。
在您的 Java 示例中,您实际上覆盖了方法,因此它们是相同的方法。一个代替旧的。你可以这样想:所有 Java 函数都被秘密标记为virtual
,这意味着它们可以被覆盖。如果您希望某个方法在 Java 中不可被覆盖,则必须将其声明为 final
。
【讨论】:
`final` 在 C++ 中确实具有相同的含义,顺便说一句。 @MSalters true,但这仅在基类已经将该方法定义为虚拟方法时才需要。【参考方案2】:注意:小心,每种语言都有自己的思维方式。有很多方法可以解释/实现 OO。即使 C++ 和 Java 看起来很相似,它们也远非相似。
在这两种语言中,编译器在编译时验证您是否可以调用方法,方法是检查类(以及从当前类继承的类等)以获取正确的方法签名和知名度。真正不同的是调用的发出方式。
C++:
对于非虚拟方法,调用的方法在编译时完全确定。这就是为什么即使对象属于B
类,当它执行A::sleep
时,对eat
的调用被解析为对A::eat
的调用(eat
不是虚拟的,然后编译器调用A::eat
,因为你在级别A
)。在B::sleep()
中,对this->eat()
的调用被解析为对B.eat()
的调用,因为在那个地方this
的类型是B
。您不能深入到继承层次结构(在 A
类中调用 eat
永远不会在下面的类中调用 eat
方法)。
请注意,虚拟方法的情况是不同的(它更类似于 Java 的情况,但有所不同)。
Java:
在Java 中,调用的方法是在运行时 确定的,并且是与对象实例最相关的方法。因此,当在A.sleep
中,对eat
的调用将是与当前对象类型相关的调用,这意味着B
类型(因为当前对象是B
类型)然后B.eat
将被调用。
然后您会发生堆栈溢出,因为当您在玩B
类型的对象时,对B.sleep()
的调用将调用A.sleep()
,这将调用B.eat()
,而后者又将调用B.run()
这将在一个永无止境的循环中调用A.sleep()
等。
【讨论】:
以上是关于为啥继承在 Java 和 C++ 中的行为不同,超类调用(或不调用)子类的方法?的主要内容,如果未能解决你的问题,请参考以下文章