C++面向对象继承全面解析:不能被继承的类、多继承、菱形虚拟继承与设计模式实践

🔥个人主页:************************************************************************************************************************************************************************************************************************************************************胡萝卜3.0****************************************************************************************************************************************************************************************************************************************************************

📖个人专栏:

⭐️人生格言:不试试怎么知道自己行不行


🎥胡萝卜3.0🌸的简介:


目录

一、实现一个不能被继承的类

[1.1 间接方式](#1.1 间接方式)

[1.2 直接方式](#1.2 直接方式)

二、继承和友元

三、继承与静态成员

四、超越单继承:多继承中的菱形陷阱与虚继承破解之道

[4.1 类的不同"血缘"关系:单继承与多继承](#4.1 类的不同“血缘”关系:单继承与多继承)

[4.1.1 虚继承](#4.1.1 虚继承)

[4.1.2 题目](#4.1.2 题目)

[4.2 IO库中的菱形虚拟继承](#4.2 IO库中的菱形虚拟继承)

五、核心设计抉择:组合与继承的对比分析


一、实现一个不能被继承的类

1.1 间接方式

我们知道在继承中先构造父类再构造子类,那如果我们将基类中的构造函数私有化,派生类中就看不见基类中的构造函数,就不能调用基类中的构造函数,那么派生类就无法实例化出相应的对象

cpp 复制代码
class Person
{
private:
	Person(const char* name)
		: _name(name)
	{
		cout << "Person()" << endl;
	}
protected:
	string _name; // 姓名
};
class Student : public Person
{
public:
	Student(const char* name = "张三", int num = 999, const char* address = "中国")
		:Person(name)
		, _num(num)
		, _address(address)
	{}
	Student(const Student& s)
		:Person(s)
		, _num(s._num)
		, _address(s._address)
	{}
	//赋值重载
	Student& operator=(const Student& s)
	{
		if (this != &s)
		{
			Person::operator=(s);
			_num = s._num;
			_address = s._address;
		}
		return *this;
	}
	////析构
	//~Student()
	//{
	//	Person::~Person();
	//}
	void print()
	{
		cout << _name << " " << _num << " " << _address << endl;
	}
protected:
	int _num; //学号
	string _address;
};

int main()
{
	Student s;
	return 0;
}

1.2 直接方式

C++11中新增了一个final关键字,final修饰基类,派生类就不能继承了

二、继承和友元

友元关系不能继承,也就是说基类友元不能访问派生类的私有和保护成员

(比如说:你爸爸有一些朋友,你去找你爸爸的朋友,让你爸爸的朋友帮忙,不一定帮,但是如果是你爸爸让朋友帮忙,你爸爸的朋友会帮忙)

假设现在我想在类外面访问基类和派生类的成员变量------

但是我们知道类里面的私有成员或者保护成员在类外无法访问,此时我们就可以将Display函数成为基类的友元函数------

但是好像还不能访问Student派生类中的保护成员,这是为什么?我不是已经继承了基类吗?

ok,其实这是因为友****元关系不能继承,也就是说基类友元不能访问派生类私有和保护成员 。

但是我们可以这样写,让Display函数分别成为基类的友元和派生类的友元:

三、继承与静态成员

静态成员共享机制:父子共用同一份

通过前面的学习,我们知道派生类继承基类中的成员,派生类中的基类成员和基类中的成员是两部分,而如果我们在基类中定义了一个静态成员,那么派生类继承基类后,基类和派生类共用一份。

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个派生类,都只有⼀个static成员实例。

ok,我们通过代码来看看究竟是不是这样的:

cpp 复制代码
class Person
{
public:
	string _name;
	static int _count;
};

// 静态成员会被继承
int Person::_count = 10;

class Student : public Person
{
protected:
	int _stuNum;
};

int main()
{
	Person p;
	Student s;
	// 从这里的运行结果可以看到非静态成员_name的地址是不一样的
	// 说明派生类继承下来了,父类、派生类对象各有一份
	cout << &p._name << endl;
	cout << &s._name << endl;
	// 从这里的运行结果可以看到静态成员_count的地址是一样的
	// 说明派生类和基类共用同一份静态成员
	cout << &p._count << endl;
	cout << &s._count << endl;

	// 公有的情况下,父类、派生类指定类域都可以访问静态成员
	cout << Person::_count << endl;
	cout << Student::_count << endl;

	return 0;
}

运行结果:

四、超越单继承:多继承中的菱形陷阱与虚继承破解之道

前情提示:多继承会是一个坑!!!

4.1 类的不同"血缘"关系:单继承与多继承

所谓单继承就是:一个派生类只有一个直接基类时称这个继承关系为单继承

cpp 复制代码
//单继承
class Person
{
protected:
	int _age;
	string _name;
	string _address;
	string _num;
};

class Student : public Person
{
protected:
	int _stuNum;
};

多继承是指:一个派生类有两个或者两个以上直接基类时称这个继承关系为多继承,多继承对象在内存中的模型是,先继承的基类在前面,后面继承的基类在后面,派生类成员放在最后面。

cpp 复制代码
//多继承
class Student
{
protected :
	int _num; //学号
};
class Teacher
{
protected :
	int _id; // 职⼯编号
};
class Assistant : public Student, public Teacher
{
protected :
	string _majorCourse; // 主修课程
};

先继承的基类放前面,后继承的基类放后面,然后中间用逗号隔开。

但是吧,如果我是这种继承,会不会有什么问题呢:

cpp 复制代码
class person
{
protected:
	string _name;
};
class student:public person
{
protected:
	string _num;//学号
};
class teacher :public person
{
protected:
	string _id;//职工号
};
class assistant:public student,public teacher
{
protected :
	string _majorCourse; // 主修课程
};

ok,其实上面的继承就构成了菱形继承:菱形继承是多继承的一种特殊情况

菱形继承的问题,从上面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题,在assistant的对象中person成员会有两份,那就会导致一些问题------

ok,那对于这种情况,我们只能显示指定访问哪个基类成员,编译器就知道该访问哪个_name了

这样就可以解决二义性的问题,但是还是没有解决数据冗余的问题!!!

ok,接下来,我们看看这个问题祖师爷是怎么解决的------

4.1.1 虚继承

祖师爷想出了虚继承去解决数据冗余的问题,虚继承的关键字是------virtual:

代码图示:

  • 总结:

虚继承应该加在中间层的基类上,也就是直接继承自公共基类的那些类。

我们可以设计出多继承,但是不建议设计出菱形继承,因为菱形虚拟继承以后,无论是使用还是底层都会复杂很多。当然有多继承语法支持,就⼀定存在会设计出菱形继承,像Java是不支持多继承的,就避开了菱形继承。所以在现实中不要去玩菱形继承!!!

4.1.2 题目

多继承中指针偏移问题?下⾯说法正确的是( C)

A:p1 == p2 == p3 B:p1 < p2 < p3 C:p1 == p3 != p2 D:p1 != p2 != p3

4.2 IO库中的菱形虚拟继承

其实库中是有菱形继承的(现实中不要玩菱形继承!!!):

ok,那学到现在,我们已经学习两种复用的方式:组合和继承,那这两个我们该用哪个呢?

五、核心设计抉择:组合与继承的对比分析

public继承是一种 is-a 的关系,也就是说每个派生类对象中都是一个基类对象

组合是一种 has-a 的关系。假设B组合了A,每个B对象中都有一个A对象

继承允许你根据基类的实现来定义派生类的实现。这种通过⽣成派生类的复⽤通常被称为⽩箱复⽤(white-box reuse)。术语"白箱"是相对可视性而言在继承方式中,基类的内部细节对派生类可见。继承⼀定程度破坏了基类的封装,基类的改变,对派生类的影响派生类和基类间的依赖关系很强,耦合度高。

组合是类继承之外的另一种复用选择新的更复杂的功能可以通过组装或组合对象来获得。**对象组合要求被组合的对象具有良好定义的接口。****这种复用被称为"黑箱复用",因为组合对象的内部细节是看不见的。**组合类之间没有很强的依赖关系,耦合度低。

  • 总结:

优先使用对象组合有助于你保持每个类被封装。 实际上尽量多去用组合,组合的耦合度低,代码维护性好但这不是绝对的,类之间的关系如果适合继承就用继承,另外实现多态,也必须继承。如果类之间的关系既适合继承也适合组合,优先使用组合

相关推荐
蜗牛沐雨3 小时前
解决 OpenSSL 3.6.0 在 macOS 上 Conan 构建失败的链接错误
c++·macos
Violet_YSWY3 小时前
将axios、async、Promise联系在一起讲一下&讲一下.then 与其关系
开发语言·前端·javascript
Danileaf_Guo3 小时前
29瓦功耗运行140亿参数模型!Mac mini M4的AI能效革命
人工智能
小草cys3 小时前
华为910B服务器(搭载昇腾Ascend 910B AI 芯片的AI服务器查看服务器终端信息
服务器·人工智能·华为·昇腾·910b
高洁013 小时前
大模型-模型压缩:量化、剪枝、蒸馏、二值化 (4)
人工智能·python·深度学习·aigc·transformer
twl3 小时前
别再写单次调用的 AI 了!这四种设计模式让你的应用真正"智能"起来
人工智能
极新3 小时前
华尔街之狼,与AI共舞
人工智能·区块链
luoganttcc3 小时前
用Python的trimesh库计算3DTiles体积的具体代码示例
开发语言·python·3d
Web3_Daisy3 小时前
从冷换仓到热追踪:项目方如何在不暴露风险的前提下守住主动权
大数据·人工智能·安全·区块链