C++知识点52——多重继承

本文介绍了C++多重继承的概念、规则及问题。多重继承允许一个类有多个基类,其构造与析构函数、拷贝构造与operator=规则和单继承类似。但多重继承会出现二义性错误,可通过添加作用域或在子类实现同名函数避免。

一、概念

C++允许一个类拥有多个基类,所以就有了多重继承。多重继承和单一继承很多方面都是类似的。

示例

class zooanimal
{
public:
	zooanimal(){cout<<__func__<<endl;}
	virtual ~zooanimal(){cout<<__func__<<endl;}
};

class bear:public zooanimal
{
public:
	bear(){cout<<__func__<<endl;}
	~bear(){cout<<__func__<<endl;}
};

class endangered
{
public:
	endangered(){cout<<__func__<<endl;}
	~endangered(){cout<<__func__<<endl;}
};
class panda:public bear, public endangered
{
public:
	panda(){cout<<__func__<<endl;}
	~panda(){cout<<__func__<<endl;}	
};

上述代码中panda就是继承了bear和endanger

 

二、规则

1、多重继承的构造函数与析构函数

和单继承一样,子类对象是由每个基类和子类特有的部分共同组成的,上述代码中,子类panda对象的结构如下

当创建一个panda对象时,各个类构造函数和析构函数的执行顺序和单继承的规则一样:根据基类在派生列表中出现的顺序,依次进行构造,派生列表中第一个出现的是bear类,所以先创建bear对象,又发现bear是zooanimal的子类,所以先创建zooanimal的对象,然后创建bear对象。第二个出现在派生列表中的类是endangered,所以创建endangered类对象,最后创建panda中独有的部分,之后,整个panda对象生成完毕。析构函数的调用顺序和构造函数相反

上述代码只是隐式使用bear和endangered的构造函数,可以显示调用

class panda:public bear, public endangered
{
public:
	panda():bear(),endangered(){cout<<__func__<<endl;}
	~panda(){cout<<__func__<<endl;}
};

 

2.多重继承的拷贝构造与operator=

和单继承一样,如果不显示调用基类的拷贝构造,那么在拷贝初始化子类对象时,只会调用基类的构造函数

class zooanimal
{
public:
	zooanimal(){cout<<__func__<<endl;}
	zooanimal(const zooanimal &t) {cout<<"zooanimal(const zooanimal &t)"<<endl;}
	~zooanimal(){cout<<__func__<<endl;}
};

class bear:public zooanimal
{
public:
	bear(){cout<<__func__<<endl;}
	bear(const bear &t) {cout<<"bear(const bear &t)"<<endl;}
	~bear(){cout<<__func__<<endl;}
};

class endangered
{
public:
	endangered(){cout<<__func__<<endl;}
	endangered(const endangered &t) {cout<<"endangered(const endangered &t)"<<endl;}
	~endangered(){cout<<__func__<<endl;}
};

class panda:public bear, public endangered
{
public:
	panda():bear(),endangered(){cout<<__func__<<endl;}
	panda(const panda &t){cout<<"panda(const panda &t)"<<endl;}
	~panda(){cout<<__func__<<endl;}
};

int main(int argc, char const *argv[])
{
	panda t;
	panda t2=t;
	return 0;
}

可见,上图中,只调用了子类对象的拷贝构造函数,而没有调用各个基类的拷贝构造函数

显示调用基类中的拷贝构造函数后,拷贝初始化子类对象时就会调用各个基类的拷贝构造

class zooanimal
{
public:
	zooanimal(){cout<<__func__<<endl;}
	zooanimal(const zooanimal &t) {cout<<"zooanimal(const zooanimal &t)"<<endl;}
	~zooanimal(){cout<<__func__<<endl;}
};

class bear:public zooanimal
{
public:
	bear(){cout<<__func__<<endl;}
	bear(const bear &t):zooanimal(t) {cout<<"bear(const bear &t)"<<endl;}
	~bear(){cout<<__func__<<endl;}
};

class endangered
{
public:
	endangered(){cout<<__func__<<endl;}
	endangered(const endangered &t) {cout<<"endangered(const endangered &t)"<<endl;}
	~endangered(){cout<<__func__<<endl;}
};

class panda:public bear, public endangered
{
public:
	panda():bear(),endangered(){cout<<__func__<<endl;}
	panda(const panda &t):bear(t),endangered(t){cout<<"panda(const panda &t)"<<endl;}
	~panda(){cout<<__func__<<endl;}
};

如果panda没有显示定义拷贝构造函数,那么在对panda对象进行拷贝初始化时,会使用编译器提供的拷贝构造函数,此时也会调用基类的拷贝狗构造函数,将上述代码的29行注释掉后,输出结果如下

可见,使用编译器提供的拷贝构造函数时,会调用子类的拷贝构造函数

 

operator=也是一样,如果各个基类显示定义了operator=,那么如果子类也定义了operator=,那么要在子类中显示调用基类的operator=,否则只会调用子类的operator=

class zooanimal
{
public:
	zooanimal(){cout<<__func__<<endl;}
	zooanimal &operator=(const zooanimal &t) {cout<<__func__<<"zooanimal"<<endl;return *this;}
	~zooanimal(){cout<<__func__<<endl;}
};

class bear:public zooanimal
{
public:
	bear(){cout<<__func__<<endl;}
	bear &operator=(const bear &t) {cout<<__func__<<"bear"<<endl;return *this;}
	~bear(){cout<<__func__<<endl;}
};

class endangered
{
public:
	endangered(){cout<<__func__<<endl;}
	endangered &operator=(const endangered &t) {cout<<__func__<<"endangered"<<endl;return *this;}
	~endangered(){cout<<__func__<<endl;}
};

class panda:public bear, public endangered
{
public:
	panda():bear(),endangered(){cout<<__func__<<endl;}
	panda &operator=(const panda &t) {cout<<__func__<<"panda"<<endl;return *this;}
	~panda(){cout<<__func__<<endl;}
};

int main(int argc, char const *argv[])
{
	panda t;
	panda t2;
	t2=t;
	return 0;
}

上述输出结构是子类定义了operator=,但是没有显示调用的结果,显示调用后的代码和输出结果如下

class zooanimal
{
public:
	zooanimal(){cout<<__func__<<endl;}
	zooanimal(const zooanimal &t) {cout<<"zooanimal(const zooanimal &t)"<<endl;}
	zooanimal &operator=(const zooanimal &t) {cout<<__func__<<"zooanimal"<<endl;return *this;}
	~zooanimal(){cout<<__func__<<endl;}
};

class bear:public zooanimal
{
public:
	bear(){cout<<__func__<<endl;}
	bear(const bear &t):zooanimal(t) {cout<<"bear(const bear &t)"<<endl;}
	bear &operator=(const bear &t) {
		cout<<__func__<<"bear"<<endl;
		zooanimal::operator=(t); 
		return *this;
	}
	~bear(){cout<<__func__<<endl;}
};

class endangered
{
public:
	endangered(){cout<<__func__<<endl;}
	endangered(const endangered &t) {cout<<"endangered(const endangered &t)"<<endl;}
	endangered &operator=(const endangered &t) {cout<<__func__<<"endangered"<<endl;return *this;}
	~endangered(){cout<<__func__<<endl;}
};

class panda:public bear, public endangered
{
public:
	panda():bear(),endangered(){cout<<__func__<<endl;}
	panda(const panda &t):bear(t),endangered(t){cout<<"panda(const panda &t)"<<endl;}
	panda &operator=(const panda &t) {
		cout<<__func__<<"panda"<<endl;
		bear::operator=(t);
		endangered::operator=(t);
		return *this;
	}
	~panda(){cout<<__func__<<endl;}
};

如果子类没有显示定义operator=,那么,一般情况,编译器也会自动生成一个operator=,此时对子类对象进行赋值时,也会调用基类的operator=,将上述代码的operator=注释掉的输出结果如下

3.多重继承的问题

和单一继承一样,因为多重继承,所以子类很多个基类,所以,子类对象可以切割转换成任意基类对象,任意基类的指针或引用都可以指向子类。但是可访问的成员依然是由对象,指针,和引用的静态类型决定

访问成员时,查找成员的顺序也和单一继承一样,见博客https://blog.youkuaiyun.com/Master_Cui/article/details/109849186

但是,因为有很多个基类,当不同基类中出现了同名函数时,会出现二义性错误

class zooanimal
{
public:
	zooanimal(){cout<<__func__<<endl;}
	~zooanimal(){cout<<__func__<<endl;}
};

class bear:public zooanimal
{
public:
	bear(){cout<<__func__<<endl;}
	void func() {cout<<__func__<<endl;}
	~bear(){cout<<__func__<<endl;}
};

class endangered
{
public:
	endangered(){cout<<__func__<<endl;}
	void func() {cout<<__func__<<endl;}
	~endangered(){cout<<__func__<<endl;}
};

class panda:public bear, public endangered
{
public:
	panda():bear(),endangered(){cout<<__func__<<endl;}
	~panda(){cout<<__func__<<endl;}
};

int main(int argc, char const *argv[])
{
	panda t;
	t.func();
	return 0;
}

上述代码中,endangered和bear都定义了func,所以,子类对象无法确定该调用哪个,所以出现二义性错误

想避免这种二义性,有两种办法:

1.添加作用域

将上述代码中的34行改为如下

t.bear::func();

此时,指定了func的作用域,指定调用bear中的func,此时不会出现二义性

2.在子类中实现一个同名函数

在子类中实现一个func

void func() {cout<<__func__<<endl;}

此时也不会报错,因为查找func时,会先在子类panda中查找,找到了就不找了,所以不会报错

此外,和单一继承一样,不要让子类和基类中出现名字相同但形参列表不同的函数。示例见博客https://blog.youkuaiyun.com/Master_Cui/article/details/109849186

 

参考

《C++ Primer》

 

欢迎大家评论交流,作者水平有限,如有错误,欢迎指出

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值