目录
[类+类名:: 继承方式+被继承(父类)类名](#类+类名:: 继承方式+被继承(父类)类名)
继承的概念
在了解继承之前先看一段代码
cpp
#include <iostream>
using namespace std;
class Student
{
public:
// 进入校园/图书馆/实验室刷二维码等身份认证
void identity()
{
// ...
}
// 学习
void study()
{
// ...
}
protected:
string _name = "peter"; // 姓名
string _address; // 地址
string _tel; // 电话
int _age = 18; // 年龄
int _stuid; // 学号
};
class Teacher
{
public:
// 进入校园/图书馆/实验室刷二维码等身份认证
void identity()
{
// ...
}
// 授课
void teaching()
{
//...
}
protected:
string _name = "张三"; // 姓名
int _age = 18; // 年龄
string _address; // 地址
string _tel; // 电话
string _title; // 职称
};
int main()
{
return 0;
}
可以看到学生类和老师类的成员函数和成员变量有重复的,因此我们可以把他们相同的合并到一起,他们不同的放到自己的类里面。
合并后:
cpp
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
// 进入校园/图书馆/实验室刷二维码等身份认证
void identity()
{
cout << "void identity()" << _name << endl;
cout << _age << endl;
}
protected:
string _name = "张三"; // 姓名
string _address; // 地址
string _tel; // 电话
private:
int _age = 18; // 年龄
};
class Student : public Person
{
public:
// 学习
void study()
{
identity();
// ...
//cout << _age << endl;
cout << _tel << endl;
}
protected:
int _stuid; // 学号
};
class Teacher : public Person
{
public:
// 授课
void teaching()
{
//...
}
protected:
string title; // 职称
};
int main()
{
Student s;
Teacher t;
s.identity();
t.identity();
return 0;
}
基类/父类:从上面来理解就是student和Teacher共有的成员函数和成员变量的类。
派生类/子类:student和Teacher的类,里面有他们特有的成员函数和变量。
继承的概念:继承就是"老爸的财产,儿子可以直接用,还能自己再添新的"。(简单的理解)
补充:private、protected和public的区别
继承定义的格式
类+类名:: 继承方式+被继承(父类)类名
例如
cpp
class Student : public Person//类+类名:: 继承方式+被继承(父类)类名
继承基类成员访问方式的变化
| 类成员/继承方式 | public继承 | protected继承 | private继承 |
|---|---|---|---|
| 基类的public成员 | 派生类的public成员 | 派生类的protected成员 | 派生类的private成员 |
| 基类的protected成员 | 派生类的protected成员 | 派生类的protected成员 | 派生类的private成员 |
| 基类的private成员 | 在派生类中不可见 | 在派生类中不可见 | 在派生类中不可见 |
基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员 还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问 它。
cpp
class Person {
private:
int _age; // 私有成员
protected:
string _name;
};
class Student : public Person {
int _stuid;
};
内存的情况

可以看到student的类里面其实是有_age的,但是他是私有的,子类不可以访问,只能访问protectd和public。
cpp
#include <iostream>
using namespace std;
class Person {
private:
int _age = 30;
public:
void ShowAge() {
cout << _age << endl; // 基类自己当然能访问
}
};
class Student : public Person {
public:
void TryAccess() {
// _age = 20; // 编译报错:'Person::_age' is private
// cout << _age; // 同样报错
cout << "派生类内也无法访问基类private成员" << endl;
}
};
int main() {
Student s;
// s._age = 25; // 类外更不行
s.ShowAge(); // 通过基类公有接口间接访问,输出30
return 0;
}
如果想要访问的话,可以利用父类的成员函数间接访问,或者把private换成protected限定符。
| 限定符 | 类内访问 | 派生类内访问 | 类外(对象)访问 |
|---|---|---|---|
| public | ✅ | ✅ | ✅ |
| protected | ✅ | ✅ | ❌ |
| private | ✅ | ❌ | ❌ |
关键观察:protected 恰好卡在 public 和 private 中间------类内和派生类内能访问,类外不能。
所以protected是专门为继承准备的。
最终访问权限 = Min(成员在基类的访问限定符, 继承方式)
举例:
-
基类成员是
public,用protected继承 →Min(public, protected) = protected -
基类成员是
protected,用private继承 →Min(protected, private) = private -
基类成员是
private,不管用什么继承 →Min(private, 任何) = private(即"不可见")
注意:权限只能降低,不能变高。
cpp
class Person {
public:
int _pub = 1;
protected:
int _pro = 2;
private:
int _pri = 3;
};
// 情况1:public继承
class Student1 : public Person {
void func() {
cout << _pub << endl; // public → public,能访问
cout << _pro << endl; // protected → protected,能访问
// cout << _pri << endl; // private → 不可见
}
};
// 情况2:protected继承
class Student2 : protected Person {
void func() {
cout << _pub << endl; // public → protected,类内能访问
cout << _pro << endl; // protected → protected,类内能访问
// cout << _pri << endl; // private → 不可见
}
};
// 情况3:private继承
class Student3 : private Person {
void func() {
cout << _pub << endl; // public → private,类内能访问
cout << _pro << endl; // protected → private,类内能访问
// cout << _pri << endl; // private → 不可见
}
};
int main() {
Student1 s1;
s1._pub = 10; // public继承,_pub 还是 public,类外能访问
Student2 s2;
// s2._pub = 10; // protected继承后,_pub 变成 protected,类外不能访问
Student3 s3;
// s3._pub = 10; // private继承后,_pub 变成 private,类外不能访问
return 0;
}
class 默认 private 继承,struct 默认 public 继承。 但永远不要依赖默认,显式写出继承方式是最佳实践。
情况1:用 class 定义派生类(默认 private)
cpp
class Person {
public:
int _pub = 1;
};
class Student : Person { // 等价于 class Student : private Person
// 基类的 _pub 在这里是 private,派生类内能访问,类外不行
};
int main() {
Student s;
// s._pub = 10; // 编译报错!因为默认 private 继承
return 0;
}
情况2:用 struct 定义派生类(默认 public)
cpp
struct Person {
int _pub = 1;
};
struct Student : Person { // 等价于 struct Student : public Person
// 基类的 _pub 在这里还是 public
};
int main() {
Student s;
s._pub = 10; // 编译通过!因为默认 public 继承
return 0;
}
基类和派生类的转换
通常情况下我们把⼀个类型的对象赋值给另⼀个类型的指针或者引用时,存在类型转换,中间会产
生临时对象,所以需要加const,如: int a = 1; const double& d = a; public继承中,就是⼀个特殊处理的例外,派生类对象可以赋值给基类的指针 / 基类的引用,而不需要加const,这里的指针和引用绑定是派生类对象中的基类部分,如下图所示。也就意味着⼀个基类的指针或者引用,可能指向基类对象,也可能指向派生类对象。
普通类型的转换
cpp
int a = 1;
const double& d = a;//产生了临时对象,临时对象具有常性,const修饰的是临时对象
派生类型------基类的指针和引用
cpp
class Person { };
class Student : public Person { };
Student s;
Person* p = &s; // 不需要 const,也没有临时对象
Person& r = s; // 同样不需要 const
这里没有创建临时对象,没有类型转换,不需要const修饰。

派生类对象本身就"包含"一个完整的基类对象子对象。因此派生类对象可以赋值给基类指针/引用
-
Student 对象的内存中,开头部分就是 Person 的成员
-
所以
Person* p = &s;时,p 直接指向 s 内部的 Person 部分 -
不需要任何类型转换,不需要临时对象,就是指针偏移而已
cpp
class Person {
public:
string _name = "Person";
void identity() { cout << "Person::identity" << endl; }
};
class Student : public Person {
public:
int _stuid = 1001;
void study() { cout << "Student::study" << endl; }
};
int main() {
Student s;
// 1. 派生类指针 → 基类指针(天然正确,无临时对象)
Person* p = &s;
p->identity(); // 输出 Person::identity
// 2. 派生类对象 → 基类引用(天然正确,无临时对象)
Person& r = s;
r.identity(); // 输出 Person::identity
// 3. 派生类对象 → 基类对象(切片,拷贝构造)(切片)
Person pobj = s; // 调用 Person 的拷贝构造,切掉 Student 部分
pobj.identity(); // 输出 Person::identity
// 4. 基类指针可以指向基类对象,也可以指向派生类对象
Person p1; // 基类对象
Person* p2 = &p1; // 指向基类对象
Person* p3 = &s; // 指向派生类对象 (多态的基础)
return 0;
}
切片
派生类对象赋值给基类对象是通过基类的拷贝构造函数或者赋值重载函数完成的这个过程就像派生类自己定义部分成员切掉了⼀样,所以也被叫做切割或者切片,如下图中所示。
cpp
Person pobj = s; // 派生类对象赋值给基类对象
此时会发生切片:
-
只拷贝 Person 部分的成员
-
Student 自己新增的成员(如
_stuid)被"切掉" -
内存中只保留 Person 部分

注意:基类对象不能赋值给派生类对象。
基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针
是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使⽤RTTI(Run-Time Type
Information)的dynamic_cast 来进行识别后进行安全转换。(这里先不详细说)
安全的情况
cpp
class Person { };
class Student : public Person { };
Student s;
Person* p = &s; // p 指向 Student 对象
// 向下转型:安全,因为 p 实际上指向的就是 Student
Student* ps = (Student*)p; // 安全,但用了C风格强制转换
p 指向的完整对象就是 Student,所以转回 Student* 是安全的。
不安全的情况
cpp
Person p; // p 是纯粹的基类对象
Person* p2 = &p; // p2 指向基类对象
// 向下转型:危险!p2 指向的根本不是 Student 对象
Student* ps = (Student*)p2; // 编译能过,但运行时会出问题!
ps->study(); // 内存越界!访问了不存在的 Student 成员
继类中的作用域
隐藏规则
在继承体系中基类和派生类都有独立作用域
隐藏(Hide) = 同名成员在派生类作用域中"屏蔽"了基类的同名成员,直接访问默认使用派生类的,如果想用基类的,需要加基类:: 前缀显式指定。
cpp
#include <iostream>
using namespace std;
class Person
{
public:
void Print()
{
cout << "Person::Print()" << endl;
}
int _num = 111; // 身份证号
};
class Student : public Person
{
public:
void Print()
{ // 同名函数,构成隐藏
cout << "Student::Print()" << endl;
}
int _num = 999; // 同名成员变量,构成隐藏
};
int main()
{
Student s;
// 1. 直接访问:默认使用派生类自己的
s.Print(); // 输出 "Student::Print()"
cout << s._num << endl; // 输出 999
// 2. 显式指定作用域:访问基类的
s.Person::Print(); // 输出 "Person::Print()"
cout << s.Person::_num << endl; // 输出 111
return 0;
}
只要同名就隐藏,不看参数列表
cpp
class Person {
public:
void func() { cout << "Person::func()" << endl; }
void func(int x) { cout << "Person::func(int)" << endl; } // 重载
};
class Student : public Person {
public:
void func(double d) { cout << "Student::func(double)" << endl; } // 同名,隐藏
};
int main() {
Student s;
s.func(3.14); // 输出 Student::func(double)
// s.func(); // 编译报错!基类的 func() 被隐藏了
s.Person::func(); // 显式调用基类的 func()
s.Person::func(10); // 显式调用基类的 func(int)
return 0;
}
重点 :即使派生类的 func(double) 和基类的 func() 参数完全不同,只要名字相同,基类的所有同名重载都会被隐藏。
派生类的默认成员函数
派生类对象 = 基类部分 + 派生类自己的部分。
基类部分必须由基类的构造函数来初始化,派生类无法代劳。
如果基类没有默认构造函数(即无参构造函数),派生类必须在初始化列表中显式调用基类的带参构造函数。
有默认构造自动调用默认构造的情况
cpp
class Person {
public:
// 基类有默认构造函数(无参)
Person() {
cout << "Person::Person()" << endl;
}
};
class Student : public Person {
public:
// 派生类构造函数:自动调用基类的默认构造函数
Student() {
cout << "Student::Student()" << endl;
}
};
int main() {
Student s;
// 输出:
// Person::Person() ← 先调用基类构造
// Student::Student() ← 再调用派生类构造
return 0;
}
没有默认构造但是又带参的构造函数,子类要显示调用
cpp
#include <iostream>
using namespace std;
class Person {
public:
string _name;
int _age;
// 基类只有带参构造函数,没有默认构造函数
Person(const string& name, int age)
: _name(name), _age(age) {
cout << "Person::Person(name, age)" << endl;
}
};
class Student : public Person {
public:
int _stuid;
// 错误写法:没有显式调用基类构造函数
// Student() { // 编译报错!基类没有默认构造函数
// _stuid = 0;
// }
// 正确写法:在初始化列表中显式调用基类构造函数
Student(const string& name, int age, int stuid)
: Person(name, age) // ← 显式调用基类的带参构造函数
, _stuid(stuid) {
cout << "Student::Student(name, age, stuid)" << endl;
}
};
int main() {
Student s("张三", 18, 1001);
// 输出:
// Person::Person(name, age) ← 先调用基类构造
// Student::Student(name, age, stuid) ← 再调用派生类构造
return 0;
}
常见的错误
忘记在初始化列表中调用
cpp
class Student : public Person {
public:
Student() { // 编译报错:no matching function for call to 'Person::Person()'
// 因为基类没有默认构造函数
}
};
错误2:在函数体内调用(错误位置)
cpp
class Student : public Person {
public:
Student(const string& name, int age) {
Person(name, age); // 错误!构造函数必须在初始化列表中调用,不能在函数体内
}
};
错误3:传错参数
cpp
class Student : public Person {
public:
Student(int age)
: Person(age) { // 错误!Person 需要 string 和 int,但只传了 int
}
};
派生类拷贝构造函数 = 先拷贝基类部分(调用基类拷贝构造)+ 再拷贝派生类自己的成员。
不能直接赋值基类成员,必须通过基类的拷贝构造函数来完成。
cpp
#include <iostream>
using namespace std;
class Person {
public:
string _name;
int _age;
// 构造函数
Person(const string& name, int age)
: _name(name), _age(age) {
cout << "Person::Person(name, age)" << endl;
}
// 基类拷贝构造函数(关键!)
Person(const Person& other)
: _name(other._name), _age(other._age) {
cout << "Person::Person(const Person&)" << endl;
}
};
class Student : public Person {
public:
int _stuid;
// 构造函数
Student(const string& name, int age, int stuid)
: Person(name, age), _stuid(stuid) {
cout << "Student::Student(name, age, stuid)" << endl;
}
// 派生类拷贝构造函数(正确写法)
Student(const Student& other)
: Person(other) // ← 调用基类拷贝构造,完成基类部分的拷贝
, _stuid(other._stuid) {
cout << "Student::Student(const Student&)" << endl;
}
};
int main() {
Student s1("张三", 18, 1001);
Student s2(s1); // 调用派生类拷贝构造
// 输出:
// Person::Person(name, age) ← 构造 s1
// Student::Student(name, age, stuid)
// Person::Person(const Person&) ← 拷贝 s2 的基类部分
// Student::Student(const Student&) ← 拷贝 s2 的派生类部分
return 0;
}
派生类的 operator= 与基类的 operator= 同名,构成隐藏。
所以在派生类的 operator= 中调用基类的 operator= 时,必须加上 基类:: 前缀,否则编译器会认为你在调用自己(派生类的 operator=),导致无限递归。
cpp
#include <iostream>
using namespace std;
class Person {
public:
string _name;
int _age;
Person(const string& name = "", int age = 0)
: _name(name), _age(age) {}
// 基类的 operator=
Person& operator=(const Person& other) {
cout << "Person::operator=" << endl;
if (this != &other) {
_name = other._name;
_age = other._age;
}
return *this;
}
};
class Student : public Person {
public:
int _stuid;
Student(const string& name = "", int age = 0, int stuid = 0)
: Person(name, age), _stuid(stuid) {}
// 派生类的 operator=(正确写法)
Student& operator=(const Student& other) {
cout << "Student::operator=" << endl;
if (this != &other) {
// 重点:必须指定基类作用域!
Person::operator=(other); // ← 显式调用基类的 operator=
_stuid = other._stuid;
}
return *this;
}
};
int main() {
Student s1("张三", 18, 1001);
Student s2("李四", 20, 1002);
s1 = s2; // 调用派生类的 operator=
// 输出:
// Student::operator= ← 先进入派生类的 operator=
// Person::operator= ← 再调用基类的 operator=
return 0;
}
派生类的析构函数调用完成后,编译器会自动插入调用基类析构函数的代码,无需我们手动写。
cpp
#include <iostream>
using namespace std;
class Person {
public:
Person() {
cout << "Person::构造函数" << endl;
}
~Person() {
cout << "Person::析构函数" << endl;
}
};
class Student : public Person {
public:
Student() {
cout << "Student::构造函数" << endl;
}
~Student() {
cout << "Student::析构函数" << endl;
// 注意:这里我们没写调用基类析构,但编译器会自动帮我们加!
// 编译器会在这里插入:Person::~Person();
}
};
int main() {
cout << "创建对象开始:" << endl;
Student s; // 1. 调用构造
cout << "对象使用中..." << endl;
cout << "对象销毁开始:" << endl;
// 2. 离开作用域,调用析构
return 0;
}
你不用在派生类析构函数里写 Person::~Person();,编译器会偷偷帮你加。
但是,如果你非要手动调用,也不能写在析构函数体里 (会重复析构),只能在特定场景下 (如 placement new) 手动调用。
一个不能继承的类
cpp
// C++11的⽅法
class Base final//在父类的后面放个final
{
public:
void func5() { cout << "Base::func5" << endl; }
protected:
int a = 1;
private:
// C++98的⽅法,把构造函数放到private
Base()
{}
};
class Derive :public Base
{
void func4() { cout << "Derive::func4" << endl; }
protected:
int b = 2;
};
int main()
{
Base b;
Derive d;
return 0;
}
方法1:基类的构造函数私有,派⽣类的构成必须调用基类的构造函数,但是基类的构成函数私有化以后,派生类看不见就不能调用了,那么派生类就无法实例化出对象。
方法2:C++11新增了⼀个final关键字,final修改基类,派生类就不能继承了。
继承和友元
友元关系不能继承,也就是说基类友元不能访问派生类私有和保护成员 。
cpp
class Student;
class Person
{
public:
friend void Display(const Person& p, const Student& s);
protected:
string _name; // 姓名
};
class Student : public Person
{
protected:
int _stuNum; // 学号
};
void Display(const Person& p, const Student& s)
{
cout << p._name << endl;
cout << s._stuNum << endl;
}
int main()
{
Person p;
Student s;
// 编译报错:error C2248: "Student::_stuNum": 无法访问 protected 成员
// 解决方案:Display也变成Student 的友元即可
Display(p, s);
return 0;
}
继承和静态成员
基类定义了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;
Person::_count++;
cout << p._count << endl;
cout << s._count << endl;
return 0;
}
继承模型
单继承:一个派生类(子类)只有一个直接基类(父类),可以多层继承。

多继承:一个派生类(子类)有两个或以上的直接基类(父类)。

菱形继承:多继承的一种特殊情况:两个中间父类继承自同一个爷爷类,孙子类同时继承这两个中间父类,导致爷爷类在孙子对象中有两份拷贝。


cpp
class Person
{
public:
Person(const char* name)
:_name(name)
{}
string _name; // 姓名
int _tel;
int _age;
string _gender;
string _address;
// ...
};
class Student :public Person
{
public:
Student(const char* name, int num = 0)
:Person(name)
,_num(num)
{}
protected:
int _num; //学号
};
class Teacher : public Person
{
public:
Teacher(const char* name, int id = 1)
:Person(name)
, _id(id)
{}
protected:
int _id; // 职工编号
};
// 不要去玩菱形继承
class Assistant : public Student, public Teacher
{
public:
Assistant(const char* name1, const char* name2, const char* name3)
:Student(name1)
,Teacher(name2)
,Person(name3)
{}
protected:
string _majorCourse; // 主修课程
};
int main()
{
// 编译报错:error C2385: 对"_name"的访问不明确
//Assistant a;
//a._name = "peter";
//// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
//a.Student::_name = "小李";
//a.Teacher::_name = "老李";
//cout << sizeof(a) << endl;
//Student s;
Assistant a("张三", "李四", "王五");
return 0;
}
虚继承
虚继承 = 让共同祖先在派生类对象中只保留一份,解决菱形继承的"数据冗余"和"二义性"问题。
不用虚继承的情况
cpp
class Person {
public:
string _name; // 爷爷的族谱
};
class Student : public Person { }; // 父亲:普通继承,抄一份
class Teacher : public Person { }; // 母亲:普通继承,抄一份
class Assistant : public Student, public Teacher { };
int main() {
Assistant a;
// a._name = "张三"; // 编译报错!不知道是 Student::_name 还是 Teacher::_name
a.Student::_name = "张三"; // 指定走父亲那条路
a.Teacher::_name = "李四"; // 指定走母亲那条路
// 现在 a 对象里有两份 _name,一份是"张三",一份是"李四",浪费内存
return 0;
}
用虚继承
cpp
class Person {
public:
string _name; // 爷爷的族谱(原件放在祠堂)
};
class Student : virtual public Person { }; // 父亲:虚继承,只保留引用
class Teacher : virtual public Person { }; // 母亲:虚继承,只保留引用
class Assistant : public Student, public Teacher { };
int main() {
Assistant a;
a._name = "张三"; // 直接访问,没有二义性!只有一份 _name
cout << a._name << endl; // 输出:张三
return 0;
}