简体   繁体   English

为什么继承在 Java 和 C++ 中的行为不同,超类调用(或不调用)子类的方法?

[英]Why does inheritance behave differently in Java and C++ with superclasses calling (or not) subclasses' methods?

I have written - what seemed to be - exactly the same example of inheritance both in Java and C++.我已经写了 - 似乎是 - 在 Java 和 C++ 中完全相同的继承示例。 I am really amazed to see the different outputs of these programs.看到这些程序的不同输出,我真的很惊讶。 Let me share both code snippets and the corresponding outputs.让我分享两个代码片段和相应的输出。


C++ Code: 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();
}

Output:输出:

A.Sleep
A.Eat
B.Sleep
B.Eat
A.Sleep
A.Eat
B.run

executed successfully...

Java Code: 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();
    }
}

Output:输出:

A.Sleep
B.Eat
A.Sleep
B.Eat
A.Sleep
......
......
......
(Exception in thread "main" java.lang.StackOverflowError)

I don't know why these two examples of inheritance behave differently.我不知道为什么这两个继承示例的行为不同。 Shouldn't it work similarly?它不应该类似地工作吗?

What is the explanation for this scenario?这个场景的解释是什么?

In your C++ example you are hiding the base methods, but you don't override them.在您的 C++ 示例中,您隐藏了基本方法,但没有覆盖它们。 So they are actually different methods which just happen to have the same name.所以它们实际上是不同的方法,只是碰巧具有相同的名称。 If you are calling如果你打电话

A* a = new B();
a->sleep();

it will actually print "A.Sleep" .它实际上会打印"A.Sleep" If you want to override a method, you need to declare it virtual in the Base class (automatically making it virtual in all sub classes too).如果你想覆盖一个方法,你需要在 Base 类中将它声明为virtual (在所有子类中也自动使其成为 virtual )。 You can read more about function hiding vs overriding in C++ in this post .您可以在这篇文章中阅读有关 C++ 中函数隐藏与覆盖的更多信息

In your Java example you actually override the methods, so they are the same method.在您的 Java 示例中,您实际上覆盖了这些方法,因此它们是相同的方法。 One taking the place of the old.一个代替旧的。 You can think of it this way: all Java functions are secretly marked as virtual , meaning they can be overridden.您可以这样想:所有 Java 函数都被秘密标记为virtual ,这意味着它们可以被覆盖。 If you want a method to not be overridable in Java, you must declare it final .如果您希望某个方法在 Java 中不可覆盖,则必须将其声明为final

Note: be careful, every language as its own way of thinking .注意:小心,每种语言都有自己的思维方式 There is a lot of ways to interpret/implement OO.有很多方法可以解释/实现 OO。 Even if C++ and Java looks similar, they are far from similar.即使 C++ 和 Java 看起来相似,它们也远非相似。

In both languages, the compiler verifies at compile-time if you can call a method, by examining the class (and the one inherited from the current one, etc) for a method of the right signature and visibility.在这两种语言中,编译器会在编译时验证您是否可以调用方法,方法是检查类(以及从当前类继承的类等)以获取具有正确签名和可见性的方法。 What makes things different is the way the call is really emitted.使事情不同的是调用的真正发出方式。

C++ : C++

In the case of non-virtual methods the method called is fully determined at compile-time .在非虚拟方法的情况下,调用的方法在编译时完全确定。 This is why even if the object is of class B , when it is executing A::sleep the call to eat is resolved as a call to A::eat ( eat is not virtual then compiler calls A::eat because you are in level A ).这就是为什么即使对象属于B类,当它执行A::sleep ,对eat的调用也被解析为对A::eat的调用( eat不是虚拟的,然后编译器调用A::eat因为你在A级)。 In B::sleep() the call to this->eat() is resolved as a call to B.eat() because at that place this is of type B .B::sleep() ,对this->eat()的调用被解析为对B.eat()的调用,因为在那个地方thisB类型。 You can't go down to the inheritance hierarchy (call to eat in class A will never call an eat method in a class below).你不能再往继承层次(调用eatA永远不会叫eat在下一个类的方法)。

Be aware that things are different in the case of virtual methods (it is more similar to the Java case while being different).请注意,虚拟方法的情况有所不同(它更类似于 Java 的情况,但有所不同)。

Java :爪哇

In Java, the method called is determined at run-time , and is the one that is the most related to the object instance.在 Java 中,调用的方法是在运行时确定的,并且是与对象实例最相关的方法。 So when in A.sleep the call to eat will be a call related to the type of the current object, that means of the type B (because the current object is of type B ) then B.eat will be called.因此,当在A.sleep中调用eat将是与当前对象的类型相关的调用,即B类型的调用(因为当前对象是B类型)然后B.eat将被调用。

You then have a stack overflow because, as you are playing with an object of type B a call to B.sleep() will call A.sleep() , which will call B.eat() , which in turn will call B.run() which will call A.sleep() , etc in a never ending loop.然后,您会遇到堆栈溢出,因为当您使用B类型的对象时,对B.sleep()调用将调用A.sleep() ,后者将调用B.eat() ,后者又将调用B.run()它将在永无止境的循环中调用A.sleep()等。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM