为啥继承在 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-&gt;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++ 中的行为不同,超类调用(或不调用)子类的方法?的主要内容,如果未能解决你的问题,请参考以下文章

为啥受保护的修饰符在 Java 子类中的行为不同?

Java面向对象—— 继承

为啥我的 C# 和 C++ dll 表现出不同的行为?

为啥在调用 int main 和在使用继承 C++ 的类中值不同?

Java继承

为啥 C++ 构造函数在继承中需要默认参数?