一、什么是C++继承?
简单来说,继承就是"子类继承父类的属性和方法",就像现实生活中,子女会继承父母的某些特征(比如外貌、性格),同时也会有自己独有的特点。在C++中,我们把被继承的类称为基类(父类) ,继承父类的类称为派生类(子类)。
核心目的:减少代码冗余,提升代码可维护性,建立类之间的层次关系。
二、继承的基本语法
C++继承的语法非常简洁,核心格式如下:
cpp
// 基类(父类)
class 基类名
{
// 成员变量、成员函数(public、protected、private)
};
// 派生类(子类)继承基类
class 派生类名 : 继承方式 基类名
{
// 派生类自己的成员变量、成员函数
};
这里有一个关键知识点:继承方式。C++提供了3种继承方式,不同的继承方式会影响基类成员在派生类中的访问权限,这也是继承的重点和难点,我们后面详细说。
继承定义格式:

还有两种定义格式:
cpp
//父类
class Preson
{
public:
void fun();
};
//子类
class Student:Preson//没指定继承方式,默认private继承
{
public:
};
//子类
struct Teacher:Preson//没指定继承方式,默认public继承
{
};
下⾯我们看到没有继承之前我们设计了两个类Student和Teacher,Student和Teacher都有姓名/地址/ 电话/年龄等成员变量,都有identity⾝份认证的成员函数,设计到两个类⾥⾯就是冗余的。当然他们 也有⼀些不同的成员变量和函数,⽐如⽼师独有成员变量是职称,学⽣的独有成员变量是学号;学⽣ 的独有成员函数是学习,⽼师的独有成员函数是授课。
cpp
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;//职称
};
下⾯我们公共的成员都放到Person类中,Student和teacher都继承Person,就可以复⽤这些成员,就 不需要重复定义了,省去了很多⿇烦。
cpp
//Person类
class Person
{
public:
void identity()
{
cout << "void identity()" << _name << endl;
}
protected:
string _name = "张三";//姓名
string _address;//地址
string _tel;//电话
private:
int _age = 18;//年龄
};
//学生类
class Student :public Person
{
public:
//学习
void study()
{
identity();
cout << _tel << endl;
}
protected:
int _stuid;//学号
};
//教师类
class Teacher :public Person
{
public:
//授课
void teaching()
{
identity();
}
protected:
string title;//职称
};
int main()
{
Student s;
Teacher t;
s.study();
t.teaching();
return 0;
}
三、3种继承方式(重点!)
继承方式决定了基类的public、protected、private成员在派生类中的访问权限,简单来说,就是"基类的成员在子类中能被访问到什么程度"。3种继承方式分别是:public(公有继承)、protected(保护继承)、private(私有继承)。
首先我们回顾一下类的访问权限(基础回顾):
-
public:类内、类外都能访问(最开放);
-
protected:类内、子类能访问,类外不能访问;
-
private:只有类内能访问,子类、类外都不能访问(最封闭)。
下面我们分别讲解3种继承方式,用表格和示例帮大家理清逻辑(重点记公有继承,实际开发中最常用)。
cpp
//Person类
class Person
{
public:
void identity()
{
cout << "void identity()"<< _name << endl;
}
void eat()
{
cout << "吃饭"<<endl;
}
void sleep()
{
cout << "睡觉" << endl;
}
void address()
{
cout << _address << endl;
}
string _name = "张三";//姓名
string _address;//地址
string _tel;//电话
private:
int _age = 18;//年龄
};
//学生类
class Student :public Person
{
public:
//学习
void study()
{
cout << "今天学C++" << endl;
}
protected:
int _stuid;//学号
};
int main()
{
//创建派生类对象
Student s;
//访问继承自基类成员
s._name = "小明";
s._address = "广州";
s.eat();//继承的方法
s.sleep();//继承的方法
s.address();//继承的方法
//访问派生类自己的方法
s.study();
return 0;
}
结果:

从示例中可以看到,Student类没有定义_name、_address成员和eat()、sleep()、address()方法,但因为它继承了Person类,所以可以直接使用这些成员和方法,同时还能添加自己的特有方法study(),这就是继承的魅力。
1. 公有继承(public)
最常用的继承方式,也是最推荐的方式。
规则:基类的public成员 → 派生类的public成员;基类的protected成员 → 派生类的protected成员;基类的private成员 → 派生类无法访问(无论哪种继承,基类private成员子类都无法直接访问)。
通俗理解:基类的"公开内容"和"保护内容",子类继承后,保持原有的访问级别(公开的还是公开,保护的还是保护),只有基类的"私有内容",子类拿不到。
基类Person中,函数identity、eat、sleep、address是public,Student公有继承Person后,这些成员在Student中还是public。所以在main函数中(类外)可以直接访问s._name、s._eat()。
2. 保护继承(protected)
较少使用,通常用于特殊场景(比如希望子类能访问基类成员,但类外不能访问)。
通俗理解:基类的"公开内容",子类继承后变成"保护内容",类外不能访问了;基类的"保护内容",子类继承后还是"保护内容"。
cpp
//学生类
class Student :protected Person
{
public:
//学习
void study()
{
cout << "今天学C++" << endl;
}
protected:
int _stuid;//学号
};
int main()
{
//创建派生类对象
Student s;
//s._name = "小明";//err! _name在Student中是protected,类外不能访问
//s.eat();//err! eat()在Student中是protected,类外不能访问
s.study();//正确!study是Student的public成员
return 0;
}
3. 私有继承(private)
极少使用,会将基类的所有可继承成员(public、protected)都变成派生类的private成员,子类的子类无法再继承这些成员。
通俗理解:基类的"公开内容"和"保护内容",子类继承后都变成自己的"私有内容",不仅类外不能访问,连子类的子类也不能访问。
cpp
#include<iostream>
using namespace std;
class Person
{
public:
void identity()
{
cout << "void identity()" << _name << endl;
cout << "_age=" << _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;//学号
};
int main()
{
Student s1;
s1.study();
return 0;
}
图解:
总结:3种继承方式对比表
| 基类成员访问权限 | 公有继承(public) | 保护继承(protected) | 私有继承(private) |
|---|---|---|---|
| public | 派生类public | 派生类protected | 派生类private |
| protected | 派生类protected | 派生类protected | 派生类private |
| private | 无法访问 | 无法访问 | 无法访问 |
核心记住2点:
① 基类private成员,无论哪种继承,子类都无法直接访问;
② 实际开发中,90%以上的场景用公有继承(public),其余两种极少用。
四、继承中的构造函数和析构函数
1. 构造函数的执行顺序
规则:先执行基类的构造函数,再执行派生类的构造函数。
原因:派生类继承了基类的成员,只有先初始化基类的成员,才能初始化派生类自己的成员(就像先有父母,才有子女,子女的出生依赖于父母)。
2. 析构函数的执行顺序
规则:先执行派生类的析构函数,再执行基类的析构函数(后定义的先析构)。
原因:派生类的成员依赖于基类的成员,要先释放派生类自己的成员,再释放基类的成员(就像先注销子女的信息,再注销父母的信息,避免依赖错误)。
示例:
cpp
//Person类
class Person
{
public:
//构造
Person()
{
cout << "Person构造" << endl;
}
//析构
~Person()
{
cout << "Person析构" << endl;
}
private:
int _age = 18;//年龄
};
//学生类
class Student :public Person
{
public:
//构造
Student()
{
cout << "Student构造" << endl;
}
//析构
~Student()
{
cout << "Student析构" << endl;
}
protected:
int _stuid;//学号
};
int main()
{
Student s;
return 0;
}
结果:

五、继承的核心特性(易错点)
1. 子类不能继承基类的4种东西
很多新手会误以为子类能继承基类的所有内容,其实不是,以下4种内容子类无法继承:
-
基类的构造函数和析构函数(只能调用,不能继承);
-
基类的private成员(无法直接访问,除非通过基类的public/protected成员函数间接访问);
-
基类的友元函数(友元关系不能继承,基类的友元不能访问子类的成员);
-
基类的赋值运算符重载函数(可以继承,但通常需要子类自己重写)。
2. 继承中的作⽤域
隐藏规则重点
1.成员变量隐藏:只要变量名相同,无论类型、参数是否一致,均构成隐藏;
2.成员函数隐藏:只要函数名相同,无论参数列表、返回值是否一致,均构成隐藏(区别于多态的重写);
3.访问基类被隐藏的成员:需使用基类作用域解析符 :: 显式访问。
- 注意在实际中在继承体系⾥⾯最好不要定义同名的成员。
示例:
cpp
// Student的_num和Person的_num构成隐藏关系,可以看出这样代码虽然能跑,但是⾮常容易混淆
class Person
{
protected:
int _num = 111;// ⾝份证号
string _name = "王彪"; // 姓名
};
class Student : public Person
{
public:
void Print()
{
cout << " 姓名:"<<_name<< endl;
cout << " 身份证号: "<<Person::_num<< endl;//显式访问基类被隐藏的_num
cout << " 学号: "<<_num<<endl;
}
protected:
int _num = 999; // 学号
};
int main()
{
Student s1;
s1.Print();
return 0;
};
结果:

来点列题加深印象:
1.下面A类和B类中的两个func构成什么关系(B)
A. 重载 B.隐藏 C.没关系
2.下⾯程序的编译运⾏结果是什么(A)
A. 编译报错 B.运⾏报错 C.正常运⾏
cpp
class A
{
public:
void func()
{
cout << "func()" << endl;
}
};
class B : public A
{
public:
void func(int i)
{
cout << "func(int i)" << i << endl;
}
};
int main()
{
B b;
b.func(10);
b.func();//正确修改:b.A::func();
return 0;
};
题解:b.fun()没传参,就没指定类域,所以报错。
六、继承的特殊场景:友元、静态成员与类型转换
1. 继承与友元
核心规则:友元关系不能继承。基类的友元函数只能访问基类的成员,无法访问派生类的private/protected成员;若想让基类友元访问派生类成员,需将该友元函数也声明为派生类的友元。
示例:
cpp
#include<iostream>
#include<string>
using namespace std;
class Student;//前置声明
class Person
{
public:
//友元关系不能被继承
friend void Display(const Person& p, const Student& s);
protected:
string _name="张三";//姓名
};
class Student :public Person
{
friend void Display(const Person& p, const Student& s);//声明为派生类友元,才能访问_stuNum
protected:
int _stuNum=123456;//学号
};
void Display(const Person& p, const Student& s)
{
cout << p._name << endl;//可访问基类成员
cout << s._stuNum << endl;//可访问派生类成员(已声明为友元)
}
int main()
{
Person p;
Student s;
Display(p, s);
return 0;
}
2. 继承与静态成员
核心规则:基类定义的static静态成员,在整个继承体系中只有一份实例,无论派生出多少个子类,都共用这一个静态成员。
示例:
cpp
#include<iostream>
#include<string>
using namespace std;
class Person
{
public:
string _name="张三";
static int _count;//静态成员类里面定义
};
int Person::_count = 0;//类外初始化
class Student :public Person
{
protected:
int _stuNum=123456;
};
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;
}

3. 基类与派生类的类型转换
仅在public继承下,支持以下类型转换(形象称为"切片"或"切割"):
-
派生类对象可以赋值给基类对象、基类指针、基类引用(仅切割出基类部分);
-
基类对象不能赋值给派生类对象(派生类有额外成员,无法初始化);
-
基类指针/引用可以通过强制类型转换赋值给派生类指针/引用,但只有当基类指针/引用指向派生类对象时,转换才安全(多态中可使用dynamic_cast安全转换)。
示例:
cpp
class Person
{
protected:
string _name;
string _sex;
int _age;
};
class Student :public Person
{
public:
int _NO;//学号
};
int main()
{
Student sobj;
//1.派生类对象->基类指针/引用/对象(合法)
Person* pp = &sobj;
Person& rp = sobj;
Person pobj = sobj;//调用基类拷贝构造完成切片
//2.基类对象->派生类对象(非法,编译报错)
//sobj=pobj;
return 0;
}
七、多继承与菱形继承(难点)
前面讲的都是"单继承"(一个派生类只有一个直接基类),而C++还支持"多继承"(一个派生类有两个及以上直接基类)。多继承虽然灵活,但会带来菱形继承问题,这也是C++语法的一个难点。
1. 菱形继承的问题
菱形继承是多继承的特殊情况:两个派生类继承同一个基类,再由一个派生类继承这两个派生类。此时会出现两个核心问题:
-
数据冗余:派生类对象中会有两份基类成员(比如下面的Assistant对象中,会有两份Person成员);
-
二义性:访问基类成员时,无法确定访问的是哪一个派生类继承的基类成员。
cpp
#include <iostream>
#include <string>
using namespace std;
// 基类:Person
class Person {
public:
string _name; // 姓名
};
// 派生类1:Student(继承Person)
class Student : public Person {
protected:
int _num; // 学号
};
// 派生类2:Teacher(继承Person)
class Teacher : public Person {
protected:
int _id; // 职工编号
};
// 派生类3:Assistant(继承Student和Teacher)→ 菱形继承
class Assistant : public Student, public Teacher {
protected:
string _majorCourse; // 主修课程
};
int main() {
Assistant a;
// a._name = "peter"; // 错误:二义性,无法确定访问哪个Person的_name
a.Student::_name = "xxx"; // 显式指定,解决二义性,但无法解决数据冗余
a.Teacher::_name = "yyy";
return 0;
}
图解:

2. 解决方法:虚继承
C++引入虚继承(virtual关键字),可以解决菱形继承的数据冗余和二义性问题。核心原理:虚继承会让所有派生类共享一份基类成员,不再重复存储。
cpp
#include <iostream>
#include <string>
using namespace std;
class Person {
public:
string _name;
};
// 虚继承:在继承方式前加virtual
class Student : virtual public Person {
protected:
int _num;
};
class Teacher : virtual public Person {
protected:
int _id;
};
// 菱形继承,此时Assistant对象中只有一份Person成员
class Assistant : public Student, public Teacher {
protected:
string _majorCourse;
};
int main() {
Assistant a;
a._name = "peter"; // 无歧义,正常访问
return 0;
}
注意:虚继承会增加底层实现的复杂度和性能损耗,因此实际开发中,尽量避免设计菱形继承。很多编程语言(如Java)直接不支持多继承,就是为了规避这个问题。
八、继承与组合(设计原则)
除了继承,C++还有另一种代码复用方式------组合。两者的核心区别的是:
-
继承(is-a关系):派生类是一个基类对象,基类的内部细节对派生类可见,耦合度高(基类修改会影响所有子类),属于"白箱复用";
-
组合(has-a关系):一个类中包含另一个类的对象,被组合的对象细节不可见,耦合度低(只需依赖接口),属于"黑箱复用"。
设计原则:优先使用组合,而非继承
组合的耦合度更低,代码维护性更好;但如果类之间确实是"is-a"关系(如Student is a Person),或需要实现多态,就必须使用继承。如果两者都适用,优先选择组合。
cpp
#include <iostream>
#include <string>
using namespace std;
// 轮胎类
class Tire {
protected:
string _brand = "米其林";
size_t _size = 17;
};
// 组合:Car has a Tire(车有轮胎)
class Car {
protected:
string _colour = "白色";
Tire _t1, _t2, _t3, _t4; // 组合轮胎对象
};
// 继承:BMW is a Car(宝马是车)
class BMW : public Car {
public:
void Drive() {
cout << "宝马:好开-操控佳" << endl;
}
};
int main() {
BMW b;
b.Drive();
return 0;
}
核心要点回顾
-
继承的价值:类层次复用,减少冗余,建立清晰的类结构;
-
访问权限与继承方式:记住核心公式,重点掌握公有继承;
-
默认成员函数:构造先基类后派生类,析构先派生类后基类;
-
隐藏规则:同名成员会屏蔽,需用作用域解析符访问基类成员;
-
特殊场景:友元不继承、静态成员共享、public继承支持切片;
-
菱形继承:用虚继承解决,但尽量避免设计;
-
设计原则:优先组合,按需继承。