- 子类继承父类, 父类的变量被子类继承, 也就是在内存上子类继承父类的变量
- 子类继承父类, 父类的函数被子类继承, 不是继承父类函数的内存大小, 而是继承父类函数的调用权
假设A是父类, B是继承于A的子类
- 如果A有虚函数, B会继承A的函数的调用权, 因此B不可能没有虚函数, 因此, 如果父类有虚函数, 那么子类一定有虚函数
例子:
在上图的左下角, 有个p
, 这个p
是这样来的C* p = new C;
在c语言时代, 如果要调用一个函数, 编译器会把代码编译成CALL XXX
(XXX是地址), 然后去这个地址执行, 之后进行return
返回, 这种行为称为静态绑定
在C++中, 是这样的:
先通过p
指针指向的地址里找到vptr
, 然后通过vptr
找到虚函数表, 也即vtbl
, 然后通过vtbl
找到对应函数的地址, 最后进行调用. 这种行为叫做动态绑定(dynamic binding). 下图是过程:
用c语言模拟调用过程的代码:
(*(p->vptr)[n])(p);
或者
(* p->vptr[n])(p); //下面也可以, 是因为运算符优先级的关系
继承和虚函数结合结合起来, 其实就是多态(polymorphism)
例子:
假设C继承于B, B继承于A, 如下图, 我们把A比作形状, B类比矩形, C类比正方形
假设我们想要在一个list中存储不同的形状, 没有哪个容器可以存放内存大小不一样的类实例, 那么我们只能放指针, 结合向上转型, 于是我们放父类的指针.
当我们希望画出某个形状的时候, 我们可以直接通过地址, 找到指针指向的对象, 然后通过虚指针, 找到对应的函数, 如下下图.
2. 关于动态绑定
为什么
B b;
A a = (A)b;
a.vfunc1(); //静态绑定
是静态绑定呢?
首先要明确, 动态绑定有三点要求:
- 函数调用是指针, 意思是需要一个指针指向那个类
- 有继承关系
- 有虚函数
上面的是用了对象a
去调用函数, 因此是静态绑定.
我个人觉得:
首先你b强转为了A类, 编译器会把b的东西都转成a的类型, 也就是说, 如果b中有一个类型是double, A类中声明的地址分布对应这个类型是int的话, 那么就会转为int, 诸如此类.
然后b就彻底成为A类型了(当然深层的东西我不知道会不会转, 比如说b中的虚函数指针), 此时用a.vfunc1()
, 是用对象去调用, 这个对象绑定的函数地址在编译时已经确定了, 因此汇编代码就是call xxx
对比
pa = &b;
pa->vfunc1(); //动态绑定
这里是pa
指向b
的地址, 当用地址去寻找函数的时候,编译器行为就不一样了, 就会用虚函数指针去找虚函数表等一系列行为.
(以上纯属我个人理解)
测试一下:
#include <iostream>
using namespace std;
class A{
public:
virtual void vfunc1(){
cout << "A" << endl;
}
};
class B: public A{
public:
virtual void vfunc1(){
cout << "B" << endl;
}
};
int main(int argc, char *argv[]){
B b;
A a = (A)b;
a.vfunc1(); //静态绑定
A* pa = new B;
pa->vfunc1(); //动态绑定
pa = &b;
pa->vfunc1(); //动态绑定
return 0;
}
输出:
A
B
B