向上造型,意思是把子类对象当作父类去看,更深层一点去看,是因为类的存在,其实体空间中包含的只有其下属的成员变量(可以通过指针获取地址来操作证明这一点,如下图)
像上面代码中描述的一般,作为A的子类B从指针级操作上看,仅仅是需要多加一位成员变量(int j),通过p++
即可实现相对父类增加的变量 j 的大小改变。
需要注意的是,在指针级操作上,子类override的的成员函数会回归到父类中的成员函数,而不会使用子类新改写函数,如下图中的print()函数。
多态性:
为了解决upcast在指针操作上的问题,在类中提出了 virtual 的修饰符。virtual
修饰符是为了在使用指针调用子类的override函数时,不会像前面upcasting中调取父类函数。
像下面中的Eilipse和Circle均基于Shape,但下面这种操作就会调用各自的重写函数。
探讨其中的本质原因,是因为virtual修饰符为子类对象创建了一个置于对象堆栈最顶部的vtable
(指针列表,也称Vptr),这个列表单元位于类创建的对象顶部,其位置加一才能得到各个成员变量的值,这个vtable中含有包括析构函数~在内的所有成员函数,如果没有使用virtual修饰的成员函数则继续沿用父类的函数(如图中的resize()函数)。
关于析构函数为什么也是Virtual,主要原因是在使用对象后必将有析构过程,这个过程必然会调用析构函数。调用析构函数即牵扯到是否动态绑定。
这里对动态静态类型和动态静态绑定进行说明:
1、对象的静态类型:对象在声明时采用的类型。是在编译期确定的。
2、对象的动态类型:目前所指对象的类型。是在运行期决定的。对象的动态类型可以更改,但是静态类型无法更改。
3、静态绑定:绑定的是对象的静态类型,某特性(比如函数)依赖于对象的静态类型,发生在编译期。
4、动态绑定:绑定的是对象的动态类型,某特性(比如函数)依赖于对象的动态类型,发生在运行期。
所以一般来讲,类中只要有一个成员函数是Virtual
的那么析构函数就必定是Virtual
的。
C++不同于其他OB语言的就是这个默认静态绑定(通过Virtual修饰符实现动态绑定),而其他OB语言是默认动态绑定的,究其原因是为了执行效率,因为一般的静态绑定只在编译期就完成了,不必在运行期消耗时间。
附:virtual子类成员函数调用父类函数的方法:即使用**Base::xxxx**
的方法即可
此外,在父类overload的virtual函数,在子类中也必须逐个override