继承与包含的区别

本文详细探讨了C++中继承和包含两种情况下的拷贝构造函数和赋值函数的行为。对于继承,讨论了基类和派生类在不同拷贝构造和赋值函数存在情况下的行为。在包含的情况下,同样分析了成员对象如何影响类的拷贝构造和赋值操作。文章强调了正确理解和使用这些函数以确保类的行为一致性的重要性。

一,继承

1.拷贝构造函数
拷贝构造函数难以维护,且由程序人员维护;
未动态申请空间或不需要外部资源,就不要写拷贝构造函数,系统自主合成的构造函数可以负荷
(1)基类有拷贝构造函数,派生类无拷贝构造函数:合成一个拷贝构造函数,即可以调动基类的拷贝构造函数,也可以合成调动派生类的拷贝构造函数

(2)基类无拷贝构造函数,派生类有拷贝构造函数:合成一个拷贝构造函数,无法去调动基类的拷贝构造函数(不会反向要求基类产生一个合法的拷贝构造函数)

(3)基类,派生类均有拷贝构造函数:由于构造派生类对象时需先构造基类对象,所以在派生类的拷贝构造函数中,需明确告知调动基类拷贝构造,否则默认引用基类的缺省拷贝构造函数。

(4)基类,派生类均无拷贝构造函数:用子对象构建另一子对象时,将子对象的地址抓住,按位拷贝
2.赋值函数
(1)基类无复写赋值语句,派生类有复写赋值语句:按要求合成一个赋值语句

(2)基类有复写赋值语句,派生类无复写赋值语句:派生类也会产生一个赋值语句

(3)基类,派生类均有复写赋值语句:需显示的说明调用基类的赋值语句,即在派生类中调动基类的赋值语句,否则无法调动基类的赋值语句

(4)基类,派生类均无复写赋值语句(即有缺省赋值语句):将合成赋值语句,两个对象同时赋值(正常赋值)

class Object
{
   
   
	int value;
public:
	Object(int x = 0) :value(x) {
   
   
		cout << "construct object: " << this << endl;
	}
	Object(const Object& obj) :value(obj.value)
	{
   
   
		cout << "copy construct object: " << this << endl;
	}
	Object& operator=(const Object& obj)
	{
   
   
		if (this != &obj)
		{
   
   
			value = obj.value;
		}
		cout << "Object = " << endl;
		return *this;
	}
	~Object()
	{
   
   
		cout 
<think>嗯,用户想了解C++中虚拟继承普通继承区别及使用场景。首先,我需要回顾一下普通继承虚拟继承的基本概念。普通继承就是一般的继承方式,子会获得父的成员变量函数。而虚拟继承主要用于解决多重继承中的菱形继承问题,也就是所谓的钻石问题。这时候,多个父继承同一个基,导致最底层的子会有多个基副本,可能造成数据冗余二义性。 接下来,我需要找到两者的主要区别。首先,内存布局不同。普通继承下,每个继承的基在子中都有独立的副本,而虚拟继承会让虚基的成员在子中只保留一份。然后是构造函数初始化的方式不同,虚拟继承需要由最底层的派生直接初始化虚基,而普通继承则由直接父初始化基。另外,访问方式也不同,虚基的成员访问可能会有额外的间接层,可能影响性能。 然后,使用场景方面,普通继承适用于单继承或不会出现菱形继承的多重继承情况,而虚拟继承则专门用于解决菱形继承带来的问题。例如,当多个中间继承自同一个基,而最终的子继承这些中间时,使用虚拟继承可以避免基成员的重复。 我需要检查提供的引用资料,看看有没有相关的例子或解释。引用[3]提到虚继承的内存布局更大,访问更慢,初始化更复杂。引用[5]中的例子展示了菱形继承下虚继承如何共享基成员。引用[4]中的School同时继承StudentTeacher,如果这两个继承自Person而没有虚拟继承,School会有两个Person副本,导致问题。这时候应该用虚拟继承。 可能还需要代码示例来对比两种情况。比如,普通继承下,菱形结构会导致基成员重复,而虚拟继承则合并为一个。同时,构造函数的调用顺序也需要说明,虚基由最底层初始化。 另外,用户可能需要知道虚拟继承的代价,比如内存性能的开销,以及何时应该避免使用。例如,如果不需要解决菱形问题,使用普通继承更高效。 总结一下,回答的结构应该是先解释两者的概念,然后列出主要区别,包括内存、初始化、访问方式等,接着给出使用场景代码示例,最后总结优缺点注意事项。确保引用的资料正确,比如引用[3][5]中的相关内容来支持观点。</think>### 虚拟继承普通继承区别及使用场景 #### 一、核心概念对比 1. **普通继承** - 每个派生直接包含的成员副本 - 多重继承时若出现**菱形继承**(如图1),会导致基成员的重复存储 $$ \text{Class D} \rightarrow (\text{Class B} \rightarrow \text{Class A}) \quad \text{AND} \quad (\text{Class C} \rightarrow \text{Class A}) $$ 2. **虚拟继承** - 通过`virtual`关键字声明继承关系 - 解决菱形继承问题,确保基成员在派生中**仅保留一份副本**[^5] - 虚基的初始化由**最终派生**直接完成[^3] #### 二、核心差异分析 | 特性 | 普通继承 | 虚拟继承 | |--------------------|----------------------------------|----------------------------------| | 内存布局 | 基成员直接嵌入派生 | 虚基成员通过指针间接访问[^5] | | 构造函数调用顺序 | 由直接父初始化基 | 由最底层派生初始化虚基[^3] | | 数据冗余 | 菱形继承时出现重复数据 | 消除数据冗余二义性[^5] | | 访问性能 | 直接访问更快 | 需要间接寻址,访问略慢[^3] | #### 三、代码示例对比 1. **普通继承的菱形问题** ```cpp class Person { public: string _name; }; class Student : public Person {}; class Teacher : public Person {}; class School : public Student, public Teacher {}; // 包含两个Person副本 ``` 此时`School`对象访问`_name`需要指明路径: `school.Student::_name` 或 `school.Teacher::_name` 2. **虚拟继承解决方案** ```cpp class Person { public: string _name; }; class Student : virtual public Person {}; // 虚拟继承 class Teacher : virtual public Person {}; // 虚拟继承 class School : public Student, public Teacher { public: School() : Person("School") {} // 直接初始化虚基 }; ``` 此时`School`对象仅含一个`_name`,可直接访问`school._name` #### 四、使用场景建议 1. **推荐虚拟继承** - 明确需要解决菱形继承问题 - 需要实现接口的多重继承似Java接口) - 例如:GUI控件体系中的公共属性继承[^1] 2. **推荐普通继承** - 单继承场景 - 多重继承无公共基 - 对性能敏感的底层代码 #### 五、注意事项 1. 虚拟继承会增加对象内存开销(约增加一个指针大小)[^3] 2. 虚基构造函数必须在最终派生中显式调用 3. 优先考虑**组合模式**替代多重继承[^1]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值