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

2022-09-03 04:56:24

我已经写了 - 似乎是 - 在Java和C++中完全相同的继承示例。我真的很惊讶地看到这些程序的不同输出。让我分享代码片段和相应的输出。


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.StackOverflowError)

我不知道为什么这两个继承的例子表现得不同。它不应该以类似的方式工作吗?

这种情况的解释是什么?


答案 1

在C++示例中,隐藏了基方法,但不会重写它们。因此,它们实际上是不同的方法,碰巧具有相同的名称。如果您正在致电

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

它实际上会打印.如果要重写某个方法,则需要在基类中将其声明为虚拟方法(在所有子类中也会自动使其成为虚拟方法)。您可以在这篇文章中阅读有关函数隐藏与覆盖C++的更多信息"A.Sleep"

在 Java 示例中,您实际上重写了这些方法,因此它们是相同的方法。一个取代了旧的。你可以这样想:所有Java函数都被秘密标记为,这意味着它们可以被覆盖。如果希望某个方法在 Java 中不可重写,则必须将其声明为 finalvirtual


答案 2

注意:要小心,每种语言都把自己的思维方式。有很多方法可以解释/实现OO。即使C++和Java看起来很相似,但它们远非相似。

在这两种语言中,编译器在编译时验证您是否可以调用方法,方法是检查类(以及从当前类继承的类等)以获取具有正确签名和可见性的方法。使事情变得不同的是调用的实际发出方式。

C++

对于非虚拟方法,调用的方法在编译时完全确定。这就是为什么即使对象是类的,当它执行调用时,也会解析为对(不是虚拟的,然后编译器调用,因为你在级别中)。在调用中,被解析为对 的调用,因为在该位置是类型 。您不能向下转到继承层次结构(调用 in 类永远不会调用下面类中的方法)。BA::sleepeatA::eateatA::eatAB::sleep()this->eat()B.eat()thisBeatAeat

请注意,在虚拟方法的情况下,情况是不同的(它更类似于Java的情况,尽管不同)。

爪哇

在 Java 中,调用的方法在运行时确定,并且是与对象实例最相关的方法。所以当调用时,将调用与当前对象的类型相关,该类型的表示(因为当前对象是类型)然后将被调用。A.sleepeatBBB.eat

然后你有一个堆栈溢出,因为当你玩一个类型的对象时,调用将调用,这将调用,反过来将调用哪个将调用,等等,在一个永无止境的循环中。BB.sleep()A.sleep()B.eat()B.run()A.sleep()


推荐