c++: 继承(下)

继承与静态成员变量

基类定义了static静态成员,则整个继承体系⾥⾯只有⼀个这样的成员。⽆论派⽣出多少个派⽣类,都只有⼀个static成员实例。

cpp 复制代码
class Person
{
public:
	string _name;
	static int _count;
};
int Person::_count = 0;
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;
}

可以看到基类中定义的static成员变量在基类自己那和派生类都是同一个地址,而指定类域对象count值和指定派生类类域count值相同,能证明static静态成员变量在基类和派生类都是公用一个地址

多继承

单继承:⼀个派⽣类只有⼀个直接基类时称这个继承关系为单继承
多继承:⼀个派⽣类有两个或以上直接基类时称这个继承关系为多继承,多继承对象在内存中的模型
是,先继承的基类在前⾯,后⾯继承的基类在后⾯,派⽣类成员在放到最后⾯。

cpp 复制代码
class Person
{
public:
string _name; // 姓名
 
};
class Student : public Person
{
protected:
};
int _num; //学号
 
class Teacher : public Person
{
protected:
int _id; // 职⼯编号
 
};
class Assistant : public Student, public Teacher
{
protected:
string _majorCourse; // 主修课程
 
};
int main()
{
// 编译报错:error C2385: 对"_name"的访问不明确
 
Assistant a;
a._name = "peter";
// 需要显⽰指定访问哪个基类的成员可以解决⼆义性问题,但是数据冗余问题⽆法解决
 
a.Student::_name = "xxx";
a.Teacher::_name = "yyy";
return 0;
}

多继承的语法就如上图的代码,派生类的基类也会有它的基类,上面的意思是,我假设为博士生,我可能是某科院长的学生,我还可以是某主修课程的老师,我这两个身份的我的真实名字是我这两个身份公共的,所以我定义一个Person作为它们的基类

然后你照着写的话然后调试窗口看一下创建的对象,会发现同一个对象有两个Person类,一个是student中的,一个是teacher中的,我不管是什么身份我的名字都是同一个,所以这就导致数据冗余

还有你如果这样写:

cpp 复制代码
Assistant a;
a._name = "peter";

会报错,编译器会报不知道指定哪一个_name,因为你Assistant中继承的student和teacher中都有继承Person的_name,所以编译器才会报这个错误,这就是二义性

所以在这里可以把name定义为Assistant的私有成员,只是这样违背了实际生活

这种情况是多继承的特殊情况:菱形继承

菱形继承

菱形继承:菱形继承是多继承的⼀种特殊情况。菱形继承的问题,从下⾯的对象成员模型构造,可以
看出菱形继承有数据冗余和⼆义性的问题,在Assistant的对象中Person成员会有两份。⽀持多继承就⼀定会有菱形继承,像Java就直接不⽀持多继承,规避掉了这⾥的问题,所以实践中我们也是不建议设计出菱形继承这样的模型的

所以要解决数据冗余和二义性的话就要引出下面的知识: 虚继承

虚继承

很多⼈说C++语法复杂,其实多继承就是⼀个体现。有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂,性能也会有⼀些损失,所以最好不要设计出菱形继承。多继承可以认为是C++的缺陷之⼀,后来的⼀些编程语⾔都没有多继承,如Java。

语法就是在会产生冗余的类加上virtual,比如在上面案例中,Student和Teacher里都有Person的成员,那就在它们继承Person中加vitrual

cpp 复制代码
class Person
{
public:
	
	Person(const char* name)
		:_name(name)
	{ }
	string _name; // 姓名

};
class Student :virtual public Person
{
public:
	Student(const char* name,int num)
		:Person(name)
		,_num(num)
	{ }
protected:
	int _num; //学号

};

class Teacher :  virtual public Person
{
public:
	Teacher(const char* name,int id)
		:Person(name)
		,_id(id)
	{ }
protected:
	int _id; // 职⼯编号

};

只看创建类的名称那,就是增加了virtual,里面的构造和单继承是一样的

那Assistant的构造有点不一样我先写出来引出个题来,然后再解答

a对象的名字最终是什么?

cpp 复制代码
class Assistant : public Student, public Teacher
{
public:
	Assistant(const char* name1,const char* name2,const char* name3,int num,int id,const char* majorCourse)
		:Person(name3)
		,Student(name2, num)
		,Teacher(name1, id)
		,_majorCourse(majorCourse)
	{ }
protected:
	string _majorCourse; // 主修课程

};
int main()
{
		Assistant a("张三","李四","王五",25565,1005,"计算机工程");

这里的答案是名字最后是王五

原因: 这里编译器做了特殊处理在构造函数中虽然你写了派生类Assistant在各个基类中构造基类它们自己的成员,但是它不会走它们构造的Person()这个初始化列表中的,只会走student和teacher它的基类Person中的构造去构造name,这也是为什么在Assistant中多了额外要写的Person

cpp 复制代码
a._name = "peter";
a.Student::_name = "xxx";
a.Teacher::_name = "yyy";

而且这里改的话在调试窗口中student和teachet中的name都会改,就证明虚继承中是公用一个继承的基类成员,

所以虚继承的特点:
1.在这里的Assistant中的student和teacher中的Person是指针偏移量或指针(具体看编译器如何处理)
2. 如果是student和teachet的对象,那构造函数会正常走它的基类的整体构造

后面我再写的多态会很好体现

相关推荐
江屿风1 小时前
C++OJ题经验总结(竞赛)4
开发语言·c++·笔记·算法·dp·双指针
JAVA9651 小时前
JAVA面试-并发篇 02-synchronized 锁可以重入吗
java·面试
AI玫瑰助手1 小时前
Python函数:可变参数(星号args与双星号kwargs)详解
android·开发语言·python
RemainderTime1 小时前
Spring Boot脚手架集成Sa-Token实现生产级RBAC权限管理
java·spring boot·后端·系统架构
进击的荆棘1 小时前
优选算法——栈
数据结构·c++·算法·leetcode·
韦胖漫谈IT1 小时前
选语言不是站队,是选适合问题的工具
java·python·ai·rust·go·技术落地
SWAGGY..1 小时前
【C++初阶】:(11)list的功能介绍&&list迭代器模拟实现
开发语言·c++
lpd_lt1 小时前
AI生成Spring Boot + Vue 3 + MySQL + MyBatis-Plus的项目实战
java·spring boot·vue·ai编程
JAVA面经实录9171 小时前
Kafka 全套学习知识手册
java·kafka