系列文章目录
提示:可以学完上篇,再学下篇哦!
从零开始C++----七.继承及相关模型和底层(上篇)
文章目录
[1.1 直接继承](#1.1 直接继承)
[1.2 多重继承](#1.2 多重继承)
[1.3 菱形继承](#1.3 菱形继承)
[1.3.1 数据冗余](#1.3.1 数据冗余)
[1.3.2 访问二义性](#1.3.2 访问二义性)
[1.3.3 数据如何传?](#1.3.3 数据如何传?)
[1.4 虚继承](#1.4 虚继承)
前言
提示:这里可以添加本文要记录的大概内容:
例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。
提示:以下是本篇文章正文内容,下面案例可供参考
一、多继承及其菱形继承问题
1.继承模型
继承模型共4类:直接继承(单一继承),多重继承,菱形继承(钻石继承),虚继承(解决菱形继承问题)
接下来足逐一讲解:
1.1 直接继承
单继承:⼀个派⽣类只有⼀个直接基类时称这个继承关系为单继承

cpp
#include <iostream>
#include <string>
using namespace std;
// 基类
class Person {
protected:
string _name;
public:
Person(const string& name)
: _name(name) {}
};
// 派生类:Student 继承 Person
class Student : public Person {
protected:
int _stuId;
public:
Student(const string& name, int id)
: Person(name), _stuId(id) {}
};
// 派生类:PostGraduate 继承 Student(多级继承)
class PostGraduate : public Student {
private:
string _researchTopic;
public:
PostGraduate(const string& name, int id, const string& topic)
: Student(name, id), _researchTopic(topic) {}
void PrintInfo() {
cout << "姓名:" << _name
<< ",学号:" << _stuId
<< ",研究方向:" << _researchTopic << endl;
}
};
int main() {
PostGraduate pg("小明", 2023001, "量子计算");
pg.PrintInfo();
return 0;
}
1.2 多重继承

cpp
#include <iostream>
#include <string>
using namespace std;
class Student {
protected:
int _stuId;
public:
Student(int id) : _stuId(id) {}
void Study() { cout << "我在上课学习" << endl; }
};
class Teacher {
protected:
string _course;
public:
Teacher(const string& course) : _course(course) {}
void Teach() { cout << "我在教" << _course << endl; }
};
// 多重继承:同时继承 Student 和 Teacher
class Assistant : public Student, public Teacher {
public:
Assistant(int id, const string& course)
: Student(id), Teacher(course) {}
void Work() {
Study();
Teach();
cout << "我是助教,既上课又教课" << endl;
}
};
int main() {
Assistant a(2023001, "C++程序设计");
a.Work();
return 0;
}
1.3 菱形继承

cpp
#include <iostream>
#include <string>
using namespace std;
class Person {
protected:
string _name;
public:
Person(const string& name) : _name(name) {}
void PrintName() { cout << "姓名:" << _name << endl; }
};
// Student 继承 Person
class Student : public Person {
protected:
int _stuId;
public:
Student(const string& name, int id)
: Person(name), _stuId(id) {}
};
// Teacher 继承 Person
class Teacher : public Person {
protected:
string _course;
public:
Teacher(const string& name, const string& course)
: Person(name), _course(course) {}
};
// Assistant 同时继承 Student 和 Teacher(菱形继承)
class Assistant : public Student, public Teacher {
public:
Assistant(const string& name, int id, const string& course)
: Student(name, id), Teacher(name, course) {}
// 二义性问题:直接调用 PrintName() 会报错,必须指定类域
void PrintInfo() {
// 错误:PrintName();
Student::PrintName(); // 必须指定是 Student 的 PrintName
cout << "学号:" << _stuId << endl;
cout << "课程:" << _course << endl;
}
};
int main() {
Assistant a("小红", 2023001, "数据结构");
a.PrintInfo();
return 0;
}
直接继承和多重继承的内存分布很好理解,唯独菱形继承有些难以琢磨,我们来仔细分析一下

图中继承关系很直观:
这是Assistant 类对象的内存结构,它继承自 Student 和 Teacher,而这两个类又都继承自 Person,这样就会造成两个问题
1.3.1 数据冗余
同样问题也很明显,Assistant 对象里,有两份 _name, 一份来自 Student 继承的 Person,这两份内容完全相同,却占了双倍内存,造成浪费
1.3.2 访问二义性
cpp
Assistant a;
cout << a._name; // 编译报错!
以上代码会直接报错,因为编译器不知道你要访问的是 Student 里的 _name,还是 Teacher 里的 _name
而解决的方法就是虚继承
这里在介绍虚继承前,先明白一下,这个是怎么传的
1.3.3 数据如何传?
cpp
Assistant a("小红", 2023001, 10086, "数据结构");
① Person(最顶层)
cpp
Person(string name)
: _name(name)
{}
② Student
cpp
Student(string name, int num)
: Person(name), _num(num)
{}
③ Teacher
cpp
Teacher(string name, int id)
: Person(name), _id(id)
{}
④ Assistant(重点!)
cpp
Assistant(string name, int num, int id, string major)
: Person(name), // 必须直接传给最顶层!
Student(name, num),
Teacher(name, id),
_major(major)
{}
它会按下面顺序执行构造:
-
Person ("小红") → 给
name = "小红" -
Student ("小红", 2023001) → 给
num = 2023001 -
Teacher ("小红", 10086) → 给
id = 10086 -
Assistant 自己 → 给
major = "数据结构"
注意:
菱形继承构造时,最顶层基类(Person)只能构造一次!
- 虽然
Student和Teacher都写了Person(name) - 但编译器只会执行一次
Person构造 - 必须由 最后的孙子类 Assistant 直接调用
另外,不是说规整的菱形就是菱形继承,以下这图也是菱形继承

1.4 虚继承
有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂,性能也会有⼀些损失,所以最好不要设计出菱形继承
cpp
class Person
{
public:
string _name; // 姓名
/*int _tel;
int _age;
string _gender;
string _address;*/
// ...
};
// 使用虚继承Person类
class Student : virtual public Person
{
protected:
int _num; //学号
};
// 使用虚继承Person类
class Teacher : virtual public Person
{
protected:
int _id; // 职工编号
};
// 教授助理
class Assistant : public Student, public Teacher
{
protected:
string _majorCourse; // 主修课程
};
int main()
{
// 使用虚继承,可以解决数据冗余和二义性
Assistant a;
a._name = "peter";
return 0;
}
因为 Student 和 Teacher 都用了 virtual 继承 Person 所以这是 虚继承 → 解决菱形继承的数据冗余和二义性,即在继承的基类前面加一个"virtual"
- 基类 Person
cpp
class Person
{
public:
string _name; // 姓名
};
最顶层的类,只有一个成员:姓名 _name
- Student 虚继承 Person
cpp
class Student : virtual public Person
{
protected:
int _num; //学号
};
virtual public Person= 虚继承- 作用:告诉编译器 我和 Teacher 共享同一个 Person,不要给我存两份
- Teacher 虚继承 Person
cpp
class Teacher : virtual public Person
{
protected:
int _id; // 职工编号
};
- 和上面一样,也是虚继承
- 同样共享 Person
- Assistant 继承 Student + Teacher
cpp
class Assistant : public Student, public Teacher
{
protected:
string _majorCourse; // 主修课程
};
- 多重继承
- 因为父类都是虚继承 ,所以 Assistant 里 只有一份 Person
main 函数里发生了什么?
cpp
int main()
{
Assistant a;
a._name = "peter";
return 0;
}
1.如果不用虚继承
a._name 会报错!因为有 两份 _name:
- Student 里有一份
- Teacher 里有一份编译器不知道你要哪一个!
2.用了虚继承
a._name = "peter" 正确!因为 只有一份 _nameStudent 和 Teacher 共享同一个 Person
总结:在菱形继承中,当中间类(
Student和Teacher)都用virtual虚继承了顶层基类(Person),那么它们的派生类(Assistant)里,只会共享唯一一份顶层基类的成员,不会出现多份拷贝
如果上面的都理解了,那么下面这一题就能理解:
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; // 职工编号
};
class Assistant : public Student, public Teacher
{
public:
Assistant(const char* name1, const char* name2, const char* name3)
:Person(name3)
,Student(name1, 1)
,Teacher(name2, 2)
{}
protected:
string _majorCourse; // 主修课程
};
int main()
{
// 思考一下这里a对象中_name是"张三", "李四", "王五"中的哪一个?
Assistant a("张三", "李四", "王五");
return 0;
}
答案是王五,这里不过多解释
二、多继承的空间分布
1.例子a
我们先用一道题来引入这一点
多继承中指针偏移问题?下⾯说法正确的是( )
A:p1 == p2 == p3 B:p1 < p2 < p3 C:p1 == p3 != p2 D:p1 != p2 != p3
cpp
class Base1 { public: int _b1; };
class Base2 { public: int _b2; };
class Derive : public Base1, public Base2 { public: int _d; };
int main()
{
Derive d;
Base1* p1 = &d;
Base2* p2 = &d;
Derive* p3 = &d;
return 0;
}
这一题选"C"
cpp
#include <iostream>
using namespace std;
class Base1 { public: int _b1; };
class Base2 { public: int _b2; };
class Derive : public Base1, public Base2 { public: int _d; };
int main()
{
Derive d;
Base1* p1 = &d;
Base2* p2 = &d;
Derive* p3 = &d;
cout << "p1(Base1*) 地址: " << p1 << endl;
cout << "p2(Base2*) 地址: " << p2 << endl;
cout << "p3(Derive*)地址: " << p3 << endl;
return 0;
}
我们来看一下运行结果:


p3是Derive*,指向整个对象的起始地址p1是Base1*,指向Derive中Base1子对象的地址,也就是整个对象的起始地址p2是Base2*,指向Derive中Base2子对象的地址,这个地址比对象起始地址偏移了一个Base1的大小(sizeof(int))
关键结论:
多重继承中,只有第一个基类的子对象地址和派生类对象的起始地址是相同的,其他基类子对象的地址都会发生偏移
因此这里只要我交换一下继承的顺序,那答案也会发生变化,变成****p2 == p3 != p1
多重继承时,只有排在第一个的基类,它的子对象地址和派生类对象的起始地址相同,其他基类都会因为前面的成员而发生地址偏移。
2.例子b

这张图展示的是 C++ 标准库 I/O 流体系的整体设计 ,它的核心是一个典型的多重 + 菱形继承结构 ,而且还是虚继承,非常经典

我们先来了解一个概念,什么是"抽象"类,"抽象"二字的理解
在 C++ 中,包含至少一个纯虚函数的类,就是抽象类
它的核心特点:
- 不能直接实例化对象(不能用它创建变量)
- 作用是:定义一套通用的接口(规则),让子类必须去实现这些接口
- 通常作为继承体系的基类,用来规范子类的行为
那什么是存虚函数?
格式:
virtual 返回值 函数名(参数) = 0;
它表示:这个函数没有具体实现,必须由子类来重写
由于还未讲过重写,这里只提一下,只需先记住格式即可
