一种错误的观点是:c++与java多态性的实现机制不同,但它们的表现形式应该相同。以下我将举出两个例子(请耐心看)。
class Super {
public Super() {
init();
}
protected void init() {
System.out.print("1:Super");
}
}
public class Sub extends Super{
public Sub(){
init();
}
public void init() {
System.out.print("2:Sub");
}
public static void main(String[] args)
{
Super test=new Sub();
// 输出
// 2:Sub 2:Sub
}
}
如果换成C++相信大家都知道会输出 1:Super 2:Sub
1. C++中的函数调用方式
- 普通函数调用:具体调用哪个方法在编译时间就可以决定(通过查找编译器的符号表),同时在使用标准过程调用机制基础上增加一个表示对象身份的指针(this指针)。
- 虚函数调用:函数调用依赖于对象的实际类型,一般地说,对象的实际类型只能在运行时间才能确定。虚函数一般要有两个步骤来支持,首先每一个类产生出一堆指向虚函数的指针,放在表格中,这个表格就叫虚函数表(virtual table);然后每一个类对象(class object)会添加一个指向相关虚函数表(virtual table)的指针,通常这个指针叫做vptr(自己动手写几个虚函数试试,检查class的size ,会发现明显变大。C++中Vptr放在每个对象实例内存最开始的位置)。
2. Java中的函数调用方式
- 在java虚拟机中,类实例的引用(实例对象名)就是指向一个句柄(handle)的指针,而该句柄其实是一对指针:其中一个指针指向一张表,该表格包含了对象的方法列表以及一个指向类对象(表示对象类型)的指针;另一个指针指向一块内存地址,该内存是从java堆中为对象的数据而分配出来的。
现在回到我们刚才的例子,java中的类对象在构造前(调用构造函数之前)就已经存在了,其函数表和对象类型也已经确定了,就是说还没有出生就已经存在了。
而C++中只有在构造完毕后(所有的构造函数都被成功调用)才存在,其函数表和对象的实际类型才会确定。所以这两个例子的执行结果会不一样。(个人的猜测是先构建父类对象,所以此时虚函数指针指向的是父类的虚函数,当构建子类时,虚函数指针被覆盖为子类虚函数,每一层构造都完成后,虚函数指针才被确定)
从这个例子,我们可以看出这样一个陷阱——java例子中按照逻辑应该执行的初始化函数,没有执行,即父类的初始化函数没有得到执行,这点在写代码是要注意规避。
最后讲讲java中,属性与方法的隐藏与覆盖的例子。
package Temp;
class Parent{
int x=10;
public Parent(){
System.out.println("1:" + x);
add(2);
System.out.println("2:" + x);
}
void add(int y){
System.out.println("3:" + x);
x+=y;
System.out.println("4:" + x);
}
}
class Child extends Parent{
int x=9;
void add(int y){
System.out.println("5:" + x);
x+=y;
System.out.println("6:" + x);
}
public static void main(String[] args){
Parent p=new Child();
System.out.println(p.x);
}
}
输出为:
1:10
5:0
6:2
2:10
10
有兴趣可以研究为什么这样。
本文是我在看了这篇文章后的总结。