1. 单一继承
2. 多重继承
3. 虚继承
下面循序渐进的逐个分析,环境是ubuntu 12.04.3 LTS+gcc4.8.1
单一继承
为了实现运行时多态,虚函数的地址并不能在编译期决定,需要运行时通过虚函数表查找实际调用的虚函数地址。单一继承主要要弄明白两个问题:
1. 虚函数表在哪里?
2. 基类和派生类的虚函数是按照怎样的规则组织的?
类结构代码如下:
复制代码
class base{
public:
base(int bv):bval(bv){};
virtual void base_f(){cout<<"base::f()"<
virtual void base_g(){cout<<"base::g()"<
private:
int bval;
};
class derived: public base{
public:
derived(int bv, int dv):base(bv), dval(dv){};
void base_f(){cout<<"derived::f()"<
virtual void derived_h(){cout<<"derived::h()"<
private:
int dval;
};
复制代码
使用以下测试代码查看基类和派生类的内存空间:
复制代码
base b(10);
FUN fun;
int **pvtab = (int**)&b;
cout<<"[0]:base->vptr"<
for(int i=0; i<2; i++){
fun = (FUN)pvtab[0][i];
cout<<" "<
fun();
}
cout<<" 2 "<
cout<<"[1]:bval "<<(int)pvtab[1]<
derived d(10, 100);
pvtab = (int**)&d;
cout<<"[0]:derived->vptr "<
for(int i=0; i<3; i++){
fun = (FUN)pvtab[0][i];
cout<<" "<
fun();
}
cout<<" 3 "<
cout<<"[1]:bval "<<(int)pvtab[1]<
cout<<"[2]:dval "<<(int)pvtab[2]<
复制代码
运行结果:
[0]:base->vptr
0 base::f()
1 base::g()
2 1919247415
[1]:bval 10
[0]:derived->vptr
0 derived::f()
1 base::g()
2 derived::h()
3 0
[1]:bval 10
[2]:dval 100
用图表示就是这样的(本文中属于同一个类的函数和数据成员我都用同一种颜色表示,以便区分)。
结果可以看出:
1. 虚函数表指针在对象的第一个位置。
2. 成员变量根据其继承和声明顺序依次排列。
3. 虚函数根据继承和声明顺序依次放在虚函数表中,派生类中重写的虚函数在原来位置更新,不会增加新的函数。
4. 派生类虚函数表最后一个位置是NULL,但是基类是一个非空指针(红色字体显示),我不明白这个位置在gcc中的作用,有知道的朋友可以告诉我,非常感谢!
多重继承
如果加上多重继承,情况会有那么一点点复杂了,多个基类的虚函数如何组织才能使用任意一个基类都可以达到多态效果?
类结构代码如下:
复制代码
class base1{
public:
base1(int bv):bval1(bv){};
virtual void base_f(){cout<<"base1::f()"<
virtual void base1_g(){cout<<"base1::g()"<
private:
int bval1;
};
class base2{
public:
base2(int bv):bval2(bv){};
virtual void base_f(){cout<<"base2::f()"<
virtual void base2_g(){cout<<"base2::g()"<
private:
int bval2;
};
class derived: public base1, public base2{
public:
derived(int bv1, int bv2, int dv):base1(bv1),base2(bv2), dval(dv){};
virtual void base_f(){cout<<"derived::f()"<
virtual void derived_h(){cout<<"derived::h()"<
private:
int dval;
};
复制代码
测试代码查看内存布局:
复制代码
derived d(10, 100, 1000);
FUN fun;
int **pvtab = (int**)&d;
cout<<"[0]:base1->vptr "<
for(int i=0; i<3; i++){
fun = (FUN)pvtab[0][i];
cout<<" "<
fun();
}
cout<<" 3 "<
cout<<"[1]:bval1 "<<(int)pvtab[1]<
cout<<"[2]:base2->vptr "<
for(int i=0; i<2; i++){
fun = (FUN)pvtab[2][i];
cout<<" "<
fun();
}
cout<<" 2 "<
cout<<"[3]:bval2 "<<(int)pvtab[3]<
cout<<"[4]:dval "<<(int)pvtab[4]<
复制代码
运行结果如下:
[0]:base1->vptr
0 derived::f()
1 base1::g()
2 derived::h()
3 -8
[1]:bval1 10
[2]:base2->vptr
0 derived::f()
1 base2::g()
2 0
[3]:bval2 100
[4]:dval 1000
总结:
1. 每个基类有单独的虚函数表,子类的虚函数放在第一个基类的虚函数表中。
2. 基类内存空间按基类的声明顺序依次排列
3. 如果多个基类之间有同名虚函数,派生类重写时会覆盖所有基类的该函数。这里插一句,如果没有覆盖,不同基类之间的同名函数是会产生二义性的,使用时必须指定哪个类的函数。
4. 第一个基类的虚函数表最后一个位置仍不为空(红色字体),作用不明。
虚继承
虚继承需要保证基类在派生类中只有一份,所以内存布局比多重继承又复杂那么一点点,我们以最典型的菱形继承为例子介绍。
类结构代码如下
复制代码
class base{
public:
base(int bv):bv