C++之多重继承

C++多重继承是很大一块,也不多说,就说一两点。首先探究下多重继承下派生类的内部是怎么布局的。且看下面一个例子

例1:

#include<iostream>

using namespace std;

class A{
public:
	int x;
public:
	A(){ cout<<"调用A构造函数!\n";}
	A(const A& a){ cout<<"调用拷贝构造函数!\n";}
	void testA(){ cout<<"testA!\n";}
	~A(){ cout<<"调用A析构函数!\n";}
};

class B{
public:
	B(){ cout<<"调用B构造函数!\n"; }
	void testB(){ cout<<"testB!\n";}
	~B(){ cout<<"调用B析构函数!\n";}
};

class C:public A,public B{
public:
<span style="white-space:pre">	</span>C(){ cout<<"调用C构造函数!\n";}
	~C(){ cout<<"调用C析构函数!\n";}
};


int main(){
	A *pa;
	B *pb;
	C *pc;
	C c;
	pa = &c;
<span style="white-space:pre">	</span>pb = &c;
<span style="white-space:pre">	</span>pc = &c;
<span style="white-space:pre">	</span>cout<<endl;
<span style="white-space:pre">	</span>cout<<sizeof(A)<<'\t'<<sizeof(B)<<'\t'<<sizeof(C)<<endl;
<span style="white-space:pre">	</span>cout<<pa<<'\t'<<pb<<'\t'<<pc<<endl;
<span style="white-space:pre">	</span>cout<<endl;
	return 0;
}	


输出:

分析下输出结果: A的大小为4,因为有个int型成员数据,成员函数不占对象空间。B大小其实为0,并不是1字节,至于为什么显示1这是经过编译器特殊处理的。C的大小为A、B之和。再分析下打印的pa,pb,pc指针,pa与pc指针相同,而pb却比pa要大4*16(16进制),我的机器是64位,也就是大4字节。为什么会这样呢?因为C继承了A,B,所以C的对象同时包含了A、B的部分,A、B部分在C中的排列顺序与他们被继承的顺序相同,而将派生类的地址赋给基类的指针时,基类指针会被调整指向自己的那部分内容。A部分排在前面,所以A的指针不需要调整,刚好指向c的头部,这也说明了为什么pa=pc。而B部分排在后面,它前面隔了个sizeof(A)的区块,所以需要调整B的指针,将其指向属于B部分的那块。所以pb会向前挪动sizeof(A)=4个字节。

再看个例子:

#include<iostream>

using namespace std;

class A{
public:
	int x;
public:
	A(){ cout<<"调用A构造函数!\n";}
	A(const A& a){ cout<<"调用拷贝构造函数!\n";}
	void testA(){ cout<<"testA!\n";}
	~A(){ cout<<"调用A析构函数!\n";}
};

class B{
public:
	B(){ cout<<"调用B构造函数!\n"; }
	void testB(){ cout<<"testB!\n";}
	~B(){ cout<<"调用B析构函数!\n";}
};

class C:public A,public B{
public:
	C(){ cout<<"调用C构造函数!\n";}
	~C(){ cout<<"调用C析构函数!\n";}
};


int main(){
	A *pa;
	B *pb;
	C *pc;
	
	pa = new C;
	delete pa;
	cout<<"====================================\n";
	pc = new C;
	delete pc;
	cout<<"====================================\n";
	pb = new C;
	delete pb;
	return 0;
}	

输出:

当delete pb时会出现运行时错误。先不管这个错误,看下输出结果,调用构造函数没问题,关键是析构函数。可以看到,当delete pa和delete pb时只分别调用了A、B基类的构造函数,也就是说pa,pb本质上其实跟普通的指向基类的指针并无二致。也就是说当delete pa时,只析构了A部分内容。至于为什么delete pb会出现运行时错误,我猜测是因为pb指向的内容不在头部,delete pb只会析构掉尾部,而头部仍然保留,这可能会被编译器认为不合法。

好了,既然delete基类指针只会调用基类的析构函数,那怎么样才能把派生类和其他基类部分也析构掉呢?答案是用虚函数实现。将A,B,C的析构函数都声明为虚函数,就可以了。原理是什么呢? 如果基类有虚函数,那么在外面就会有一张虚表,虚表存放着虚函数的地址,基类对象会有个隐藏的虚指针vptr指向虚表。如果派生类继承了多个虚基类,则派生类就会从每个虚基类虚表中copy一份作为派生类虚表的一部分,当然也会继承每个虚基类的虚指针。一般来说派生类的虚表分为两种,主表和次表。主表存放着派生类本身定义或重写的虚函数地址以及第一个虚基类的各虚函数,次表则存放第二个及后继虚基类的虚函数地址。当第二个(或后继)虚基类指针指向派生类对象时,跟普通继承一样,先调整到指向自己的那部分。但是基类指针调用该基类的虚函数时,会通过派生类中与该基类对应的虚指针vptr查找虚函数地址,如果在派生类中已经重写了虚函数,就会将vptr调整到主表。例如上面的例子,当delete pa或delete pb时,会先将虚指针调整到指向派生类虚表的头部,调用派生类重写后的虚析构函数,所以会析构掉所有部分。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值