You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Java与C++继承行为差异:父类调用子类方法的不同表现

为什么Java和C++继承中的方法调用表现截然不同?

这问题问得太戳中痛点了!我当初刚在C++和Java之间横跳的时候,也被这个差异坑得一脸懵😅。本质原因是两者对多态(动态绑定)的默认规则完全相反,下面一步步给你拆解清楚:

1. C++:默认静态绑定,只有虚函数才会“看实际对象类型”

在C++的世界里,普通成员函数默认是静态绑定的——简单说就是,编译器在编译的时候就直接确定了要调用哪个类的方法,判断依据是变量声明时的“表面类型”,而不是运行时这个对象真正的类型。

回到你的代码场景:
A::sleep()里面调用eat()时,编译器会觉得“我现在在A类的函数里,调用者的类型肯定是A”,所以直接绑定到A::eat(),哪怕实际运行时这个对象是B的实例也没用。
只有当你把父类的eat()声明为virtual(虚函数),C++才会开启动态绑定,这时才会根据对象的实际类型来调用对应的方法。比如修改A类的eat方法:

virtual void eat() {cout << "A.Eat" << endl;}

这么一改,运行时就会调用B类的eat()了。

补充个小细节:C++里子类重写父类的虚函数时,子类的这个函数自动也是虚的,不用再写virtual,但显式写上会更清晰,方便别人读代码。

2. Java:默认动态绑定,特殊方法才会“看表面类型”

Java刚好反过来,所有非静态、非final、非私有的成员函数,默认都是动态绑定的——也就是说,方法调用是在运行时才确定的,看的是对象真正的类型,而不是声明时的类型。

所以在你的Java代码里:
super.sleep()(也就是A类的sleep方法)调用eat()时,JVM会检查当前对象的实际类型是B,所以直接调用B类重写的eat()方法,完全忽略A类的版本。
只有当方法是static(属于类,不是对象)、final(不能被重写)、private(子类根本看不到,没法重写)的时候,Java才会用静态绑定。

3. 核心差异总结

特性C++Java
默认绑定方式静态绑定(非虚函数)动态绑定(非static/final/private)
开启动态绑定的方式给方法加virtual关键字默认开启,除非是特殊方法类型
父类中调用子类重写方法必须把父类方法声明为virtual自动调用子类重写版本(只要满足动态绑定条件)

实际运行效果对比

假设我们都创建B的实例并调用sleep方法:

  • C++(无virtual)的输出:
    A.Sleep
    A.Eat
    B.Sleep
    
  • Java的输出:
    A.Sleep
    B.Eat
    B.Sleep
    

如果给C++的A::eat加上virtual,输出就会和Java完全一致了。

内容的提问来源于stack exchange,提问作者RajibTheKing

火山引擎 最新活动