
个人主页 : 流年如梦
专栏 : 《零基础轻松入门C语言》 《数据结构:从入门到掌握》 《C++编程研习录》
文章目录
- 一.继承的概念与定义
-
- [1.1 继承的概念](#1.1 继承的概念)
- [1.2 定义格式](#1.2 定义格式)
- [1.3 三种继承方式与访问权限](#1.3 三种继承方式与访问权限)
- [1.4 继承类模板](#1.4 继承类模板)
- 二.基类与派生类的对象转换
-
- [2.1 向上转换(派生类 --> 基类)](#2.1 向上转换(派生类 --> 基类))
- [2.2 向下转换(基类 --> 派生类)](#2.2 向下转换(基类 --> 派生类))
- 三.继承中的作用域与隐藏
-
- [3.1 隐藏规则](#3.1 隐藏规则)
- [3.2 重载与隐藏的核心区别(重点)](#3.2 重载与隐藏的核心区别(重点))
- [3.3 经典例题解析](#3.3 经典例题解析)
- 四.派生类的默认成员函数
-
- [4.1 构造函数](#4.1 构造函数)
- [4.2 拷贝构造函数](#4.2 拷贝构造函数)
- [4.3 赋值运算符重载](#4.3 赋值运算符重载)
- [4.4 析构函数](#4.4 析构函数)
- [4.5 完整执行顺序](#4.5 完整执行顺序)
- 五.不能被继承的类
-
- [5.1 C++98实现方式](#5.1 C++98实现方式)
- [5.2 C++11实现方式](#5.2 C++11实现方式)
- 六.继承与友元
- 七.继承与静态成员
- 八.多继承与菱形继承
-
- [8.1 单继承与多继承](#8.1 单继承与多继承)
- [8.2 多继承的对象模型](#8.2 多继承的对象模型)
- [8.3 菱形继承](#8.3 菱形继承)
- [8.4 虚继承](#8.4 虚继承)
- [8.5 底层原理与构造规则](#8.5 底层原理与构造规则)
- [8.6 标准库实例:IO流体系](#8.6 标准库实例:IO流体系)
- 九.继承与组合
-
- [9.1 核心对比](#9.1 核心对比)
- [9.2 选择原则](#9.2 选择原则)
- [9.3 典型案例:stack与vector](#9.3 典型案例:stack与vector)
- 🎯总结
- ⚠️易错点
前言 继承是面向对象编程三大核心特性之一,是类层次级别的代码复用机制。它允许在保留原有类特性的基础上扩展成员与方法,派生出新的类,体现了由简单到复杂的认知层次。本文系统讲解单继承的语法规则、访问权限变化、隐藏机制、默认成员函数行为,深入剖析多继承、菱形继承与虚继承的底层原理,对比继承与组合的适用场景,夯实面向对象编程核心基础
一.继承的概念与定义
1.1 继承的概念
继承(inheritance)允许在已有类的基础上进行扩展,新增成员变量与成员函数,产生新的派生类。原有类称为基类(父类) ,新类称为派生类(子类)
- 核心价值:类级别的代码复用,避免相同逻辑重复编写
- 语义关系:is-a(是一个),派生类对象也是一种特殊的基类对象
问题引入 :
在设计 Student 和 Teacher 两个类时,姓名、地址、年龄、身份认证等成员完全重复。通过继承将公共成员抽取到 Person 基类,两个派生类直接复用,大幅减少冗余代码
如下所示:
cpp
// 基类:公共成员
class Person
{
public:
void identity() { cout << "身份认证:" << _name << endl; }
protected:
string _name; // 姓名
string _address; // 地址
int _age; // 年龄
};
// 派生类:继承公共成员,扩展独有成员
class Student : public Person
{
public:
void study() {
// 学习逻辑
}
protected:
int _stuId; // 学号
};
class Teacher : public Person
{
public:
void teaching() {
// 授课逻辑
}
protected:
string _title; // 职称
};
1.2 定义格式
cpp
class 派生类名 : 继承方式 基类名
{
// 派生类新增成员
};
继承方式有三种:public、protected、private
1.3 三种继承方式与访问权限
| 基类成员 \ 继承方式 | public 继承 | protected 继承 | private 继承 |
|---|---|---|---|
| 基类 public 成员 | 派生类 public 成员 | 派生类 protected 成员 | 派生类 private 成员 |
| 基类 protected 成员 | 派生类 protected 成员 | 派生类 protected 成员 | 派生类 private 成员 |
| 基类 private 成员 | 派生类中不可见 | 派生类中不可见 | 派生类中不可见 |
补充说明:
- private 成员始终不可见:基类私有成员会被继承到派生类对象中,但语法限制派生类直接访问
- 权限变化口诀 :派生类中成员访问权限 =
min(基类成员权限, 继承方式),权限优先级public > protected > private - 默认继承方式 :
class关键字默认private继承,struct关键字默认public继承;工程中几乎都使用public继承 - protected 的意义:专门为继承设计,允许派生类访问,禁止类外直接访问
1.4 继承类模板
当基类是类模板时,派生类访问基类成员必须加类域限定,否则编译器会报 "找不到标识符" 错误
cpp
namespace my
{
template<class T>
class stack : public vector<T>
{
public:
void push(const T& x)
{
// 必须加vector<T>::类域限定
vector<T>::push_back(x);
}
void pop()
{
vector<T>::pop_back();
}
T& top()
{
return vector<T>::back();
}
};
}
底层原因 :
模板按需实例化,基类模板的成员在派生类实例化前不会被查找,直接写成员名会被编译器识别为全局标识符
二.基类与派生类的对象转换
2.1 向上转换(派生类 --> 基类)
派生类对象可以赋值给基类的指针、引用或对象,属于天然安全的转换
- 指针 / 引用转换:直接绑定派生类对象中的基类部分,无额外开销,不会创建新对象
cpp
Student s;
Person* p = &s; // 基类指针指向派生类对象
Person& r = s; // 基类引用绑定派生类对象
- 对象赋值(对象切片):调用基类拷贝构造,只拷贝基类部分的成员,派生类部分被 "切割" 掉,也叫对象切片
cpp
Student s;
Person p = s; // 发生切片,仅保留基类成员
2.2 向下转换(基类 --> 派生类)
- 不能隐式转换,强制转换语法可行但不安全,访问派生类成员会越界
- 只有当基类指针 / 引用实际指向派生类对象时,向下转换才安全
- 安全方案:多态场景下使用
dynamic_cast运行时类型识别
三.继承中的作用域与隐藏
3.1 隐藏规则
- 基类与派生类拥有独立的作用域
- 派生类定义与基类同名的成员,会隐藏基类的同名成员,直接访问默认使用派生类版本
- 成员函数的隐藏:只要函数名相同就构成隐藏,与参数列表无关
- 显式访问基类成员:
基类名::成员名
cpp
class Person
{
protected:
int _num = 111; // 身份证号
};
class Student : public Person
{
public:
void print()
{
cout << "身份证:" << Person::_num << endl; // 显式访问基类
cout << "学号:" << _num << endl; // 默认访问派生类
}
protected:
int _num = 999; // 学号,与基类同名,隐藏基类_num
};
3.2 重载与隐藏的核心区别(重点)
| 特性 | 函数重载 | 同名隐藏 |
|---|---|---|
| 作用域 | 同一个类作用域内 | 基类与派生类,不同作用域 |
| 函数名 | 相同 | 相同 |
| 参数列表 | 必须不同 | 可以相同也可以不同 |
| 调用规则 | 根据实参参数匹配 | 派生类直接屏蔽基类同名函数 |
3.3 经典例题解析
cpp
class A
{
public:
void fun() { cout << "A::fun()" << endl; }
};
class B : public A
{
public:
void fun(int i) { cout << "B::fun(int):" << i << endl; }
};
int main()
{
B b;
b.fun(10); // 正常,调用B类版本
b.fun(); // 编译报错!B类的fun(int)隐藏了A类的fun()
return 0;
}
派生类作用域内的 fun(int) 屏蔽了基类的 fun(),即使参数不同也不会构成重载,因为作用域不同
四.派生类的默认成员函数
4.1 构造函数
- 派生类构造函数必须先调用基类构造,初始化基类部分成员
- 调用阶段:初始化列表阶段,先执行基类构造,再执行派生类构造函数体
- 基类没有默认构造时,派生类必须在初始化列表显式调用基类带参构造
4.2 拷贝构造函数
- 派生类拷贝构造必须显式调用基类拷贝构造,完成基类部分的深拷贝
- 若不调用,编译器会默认调用基类默认构造,导致基类成员初始化错误
4.3 赋值运算符重载
派生类 operator= 会隐藏基类版本,必须显式调用基类赋值重载完成基类部分赋值
标准流程:
#mermaid-svg-PuoF2bHjNcDOzk4i{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-PuoF2bHjNcDOzk4i .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-PuoF2bHjNcDOzk4i .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-PuoF2bHjNcDOzk4i .error-icon{fill:#552222;}#mermaid-svg-PuoF2bHjNcDOzk4i .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PuoF2bHjNcDOzk4i .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-PuoF2bHjNcDOzk4i .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PuoF2bHjNcDOzk4i .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PuoF2bHjNcDOzk4i .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-PuoF2bHjNcDOzk4i .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PuoF2bHjNcDOzk4i .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PuoF2bHjNcDOzk4i .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PuoF2bHjNcDOzk4i .marker.cross{stroke:#333333;}#mermaid-svg-PuoF2bHjNcDOzk4i svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PuoF2bHjNcDOzk4i p{margin:0;}#mermaid-svg-PuoF2bHjNcDOzk4i .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-PuoF2bHjNcDOzk4i .cluster-label text{fill:#333;}#mermaid-svg-PuoF2bHjNcDOzk4i .cluster-label span{color:#333;}#mermaid-svg-PuoF2bHjNcDOzk4i .cluster-label span p{background-color:transparent;}#mermaid-svg-PuoF2bHjNcDOzk4i .label text,#mermaid-svg-PuoF2bHjNcDOzk4i span{fill:#333;color:#333;}#mermaid-svg-PuoF2bHjNcDOzk4i .node rect,#mermaid-svg-PuoF2bHjNcDOzk4i .node circle,#mermaid-svg-PuoF2bHjNcDOzk4i .node ellipse,#mermaid-svg-PuoF2bHjNcDOzk4i .node polygon,#mermaid-svg-PuoF2bHjNcDOzk4i .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-PuoF2bHjNcDOzk4i .rough-node .label text,#mermaid-svg-PuoF2bHjNcDOzk4i .node .label text,#mermaid-svg-PuoF2bHjNcDOzk4i .image-shape .label,#mermaid-svg-PuoF2bHjNcDOzk4i .icon-shape .label{text-anchor:middle;}#mermaid-svg-PuoF2bHjNcDOzk4i .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-PuoF2bHjNcDOzk4i .rough-node .label,#mermaid-svg-PuoF2bHjNcDOzk4i .node .label,#mermaid-svg-PuoF2bHjNcDOzk4i .image-shape .label,#mermaid-svg-PuoF2bHjNcDOzk4i .icon-shape .label{text-align:center;}#mermaid-svg-PuoF2bHjNcDOzk4i .node.clickable{cursor:pointer;}#mermaid-svg-PuoF2bHjNcDOzk4i .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-PuoF2bHjNcDOzk4i .arrowheadPath{fill:#333333;}#mermaid-svg-PuoF2bHjNcDOzk4i .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-PuoF2bHjNcDOzk4i .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-PuoF2bHjNcDOzk4i .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PuoF2bHjNcDOzk4i .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-PuoF2bHjNcDOzk4i .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PuoF2bHjNcDOzk4i .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-PuoF2bHjNcDOzk4i .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-PuoF2bHjNcDOzk4i .cluster text{fill:#333;}#mermaid-svg-PuoF2bHjNcDOzk4i .cluster span{color:#333;}#mermaid-svg-PuoF2bHjNcDOzk4i div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-PuoF2bHjNcDOzk4i .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-PuoF2bHjNcDOzk4i rect.text{fill:none;stroke-width:0;}#mermaid-svg-PuoF2bHjNcDOzk4i .icon-shape,#mermaid-svg-PuoF2bHjNcDOzk4i .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PuoF2bHjNcDOzk4i .icon-shape p,#mermaid-svg-PuoF2bHjNcDOzk4i .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-PuoF2bHjNcDOzk4i .icon-shape .label rect,#mermaid-svg-PuoF2bHjNcDOzk4i .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PuoF2bHjNcDOzk4i .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-PuoF2bHjNcDOzk4i .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-PuoF2bHjNcDOzk4i :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 判断自赋值
调用基类::operator
赋值派生类成员
返回*this
4.4 析构函数
- 析构顺序:先调用派生类析构,再自动调用基类析构,与构造顺序完全相反。
- 编译器会将所有析构函数名统一处理为
destructor,因此基类与派生类析构构成隐藏关系。 - 重要隐患:基类析构不加
virtual时,通过基类指针删除派生类对象,只会调用基类析构,造成派生类资源泄漏
4.5 完整执行顺序
构造顺序:
#mermaid-svg-su5OA1LMeao8GMpu{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-su5OA1LMeao8GMpu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-su5OA1LMeao8GMpu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-su5OA1LMeao8GMpu .error-icon{fill:#552222;}#mermaid-svg-su5OA1LMeao8GMpu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-su5OA1LMeao8GMpu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-su5OA1LMeao8GMpu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-su5OA1LMeao8GMpu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-su5OA1LMeao8GMpu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-su5OA1LMeao8GMpu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-su5OA1LMeao8GMpu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-su5OA1LMeao8GMpu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-su5OA1LMeao8GMpu .marker.cross{stroke:#333333;}#mermaid-svg-su5OA1LMeao8GMpu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-su5OA1LMeao8GMpu p{margin:0;}#mermaid-svg-su5OA1LMeao8GMpu .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-su5OA1LMeao8GMpu .cluster-label text{fill:#333;}#mermaid-svg-su5OA1LMeao8GMpu .cluster-label span{color:#333;}#mermaid-svg-su5OA1LMeao8GMpu .cluster-label span p{background-color:transparent;}#mermaid-svg-su5OA1LMeao8GMpu .label text,#mermaid-svg-su5OA1LMeao8GMpu span{fill:#333;color:#333;}#mermaid-svg-su5OA1LMeao8GMpu .node rect,#mermaid-svg-su5OA1LMeao8GMpu .node circle,#mermaid-svg-su5OA1LMeao8GMpu .node ellipse,#mermaid-svg-su5OA1LMeao8GMpu .node polygon,#mermaid-svg-su5OA1LMeao8GMpu .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-su5OA1LMeao8GMpu .rough-node .label text,#mermaid-svg-su5OA1LMeao8GMpu .node .label text,#mermaid-svg-su5OA1LMeao8GMpu .image-shape .label,#mermaid-svg-su5OA1LMeao8GMpu .icon-shape .label{text-anchor:middle;}#mermaid-svg-su5OA1LMeao8GMpu .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-su5OA1LMeao8GMpu .rough-node .label,#mermaid-svg-su5OA1LMeao8GMpu .node .label,#mermaid-svg-su5OA1LMeao8GMpu .image-shape .label,#mermaid-svg-su5OA1LMeao8GMpu .icon-shape .label{text-align:center;}#mermaid-svg-su5OA1LMeao8GMpu .node.clickable{cursor:pointer;}#mermaid-svg-su5OA1LMeao8GMpu .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-su5OA1LMeao8GMpu .arrowheadPath{fill:#333333;}#mermaid-svg-su5OA1LMeao8GMpu .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-su5OA1LMeao8GMpu .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-su5OA1LMeao8GMpu .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-su5OA1LMeao8GMpu .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-su5OA1LMeao8GMpu .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-su5OA1LMeao8GMpu .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-su5OA1LMeao8GMpu .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-su5OA1LMeao8GMpu .cluster text{fill:#333;}#mermaid-svg-su5OA1LMeao8GMpu .cluster span{color:#333;}#mermaid-svg-su5OA1LMeao8GMpu div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-su5OA1LMeao8GMpu .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-su5OA1LMeao8GMpu rect.text{fill:none;stroke-width:0;}#mermaid-svg-su5OA1LMeao8GMpu .icon-shape,#mermaid-svg-su5OA1LMeao8GMpu .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-su5OA1LMeao8GMpu .icon-shape p,#mermaid-svg-su5OA1LMeao8GMpu .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-su5OA1LMeao8GMpu .icon-shape .label rect,#mermaid-svg-su5OA1LMeao8GMpu .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-su5OA1LMeao8GMpu .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-su5OA1LMeao8GMpu .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-su5OA1LMeao8GMpu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 基类构造
成员对象构造
派生类构造函数体
析构顺序:派生类析构函数体 --> 成员对象析构 --> 基类析构
#mermaid-svg-c4ZZFIaQxv3dJbpz{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-c4ZZFIaQxv3dJbpz .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-c4ZZFIaQxv3dJbpz .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-c4ZZFIaQxv3dJbpz .error-icon{fill:#552222;}#mermaid-svg-c4ZZFIaQxv3dJbpz .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-c4ZZFIaQxv3dJbpz .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-c4ZZFIaQxv3dJbpz .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-c4ZZFIaQxv3dJbpz .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-c4ZZFIaQxv3dJbpz .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-c4ZZFIaQxv3dJbpz .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-c4ZZFIaQxv3dJbpz .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-c4ZZFIaQxv3dJbpz .marker{fill:#333333;stroke:#333333;}#mermaid-svg-c4ZZFIaQxv3dJbpz .marker.cross{stroke:#333333;}#mermaid-svg-c4ZZFIaQxv3dJbpz svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-c4ZZFIaQxv3dJbpz p{margin:0;}#mermaid-svg-c4ZZFIaQxv3dJbpz .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-c4ZZFIaQxv3dJbpz .cluster-label text{fill:#333;}#mermaid-svg-c4ZZFIaQxv3dJbpz .cluster-label span{color:#333;}#mermaid-svg-c4ZZFIaQxv3dJbpz .cluster-label span p{background-color:transparent;}#mermaid-svg-c4ZZFIaQxv3dJbpz .label text,#mermaid-svg-c4ZZFIaQxv3dJbpz span{fill:#333;color:#333;}#mermaid-svg-c4ZZFIaQxv3dJbpz .node rect,#mermaid-svg-c4ZZFIaQxv3dJbpz .node circle,#mermaid-svg-c4ZZFIaQxv3dJbpz .node ellipse,#mermaid-svg-c4ZZFIaQxv3dJbpz .node polygon,#mermaid-svg-c4ZZFIaQxv3dJbpz .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-c4ZZFIaQxv3dJbpz .rough-node .label text,#mermaid-svg-c4ZZFIaQxv3dJbpz .node .label text,#mermaid-svg-c4ZZFIaQxv3dJbpz .image-shape .label,#mermaid-svg-c4ZZFIaQxv3dJbpz .icon-shape .label{text-anchor:middle;}#mermaid-svg-c4ZZFIaQxv3dJbpz .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-c4ZZFIaQxv3dJbpz .rough-node .label,#mermaid-svg-c4ZZFIaQxv3dJbpz .node .label,#mermaid-svg-c4ZZFIaQxv3dJbpz .image-shape .label,#mermaid-svg-c4ZZFIaQxv3dJbpz .icon-shape .label{text-align:center;}#mermaid-svg-c4ZZFIaQxv3dJbpz .node.clickable{cursor:pointer;}#mermaid-svg-c4ZZFIaQxv3dJbpz .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-c4ZZFIaQxv3dJbpz .arrowheadPath{fill:#333333;}#mermaid-svg-c4ZZFIaQxv3dJbpz .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-c4ZZFIaQxv3dJbpz .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-c4ZZFIaQxv3dJbpz .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-c4ZZFIaQxv3dJbpz .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-c4ZZFIaQxv3dJbpz .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-c4ZZFIaQxv3dJbpz .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-c4ZZFIaQxv3dJbpz .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-c4ZZFIaQxv3dJbpz .cluster text{fill:#333;}#mermaid-svg-c4ZZFIaQxv3dJbpz .cluster span{color:#333;}#mermaid-svg-c4ZZFIaQxv3dJbpz div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-c4ZZFIaQxv3dJbpz .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-c4ZZFIaQxv3dJbpz rect.text{fill:none;stroke-width:0;}#mermaid-svg-c4ZZFIaQxv3dJbpz .icon-shape,#mermaid-svg-c4ZZFIaQxv3dJbpz .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-c4ZZFIaQxv3dJbpz .icon-shape p,#mermaid-svg-c4ZZFIaQxv3dJbpz .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-c4ZZFIaQxv3dJbpz .icon-shape .label rect,#mermaid-svg-c4ZZFIaQxv3dJbpz .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-c4ZZFIaQxv3dJbpz .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-c4ZZFIaQxv3dJbpz .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-c4ZZFIaQxv3dJbpz :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 派生类构造函数体
成员对象构造
基类构造
例如:
cpp
class Person
{
public:
Person(const char* name) : _name(name)
{ cout << "Person构造" << endl; }
Person(const Person& p) : _name(p._name)
{ cout << "Person拷贝构造" << endl; }
Person& operator=(const Person& p)
{
cout << "Person赋值重载" << endl;
if(this != &p)
_name = p._name;
return *this;
}
~Person() { cout << "Person析构" << endl; }
protected:
string _name;
};
class Student : public Person
{
public:
Student(const char* name, int id)
: Person(name), _stuId(id)
{ cout << "Student构造" << endl; }
Student(const Student& s)
: Person(s), _stuId(s._stuId)
{ cout << "Student拷贝构造" << endl; }
Student& operator=(const Student& s)
{
cout << "Student赋值重载" << endl;
if(this != &s)
{
Person::operator=(s); // 显式调用基类赋值
_stuId = s._stuId;
}
return *this;
}
~Student() { cout << "Student析构" << endl; }
protected:
int _stuId;
};
五.不能被继承的类
5.1 C++98实现方式
将基类构造函数设为 private,派生类无法调用基类构造,不能实例化对象
5.2 C++11实现方式
用 final 关键字修饰类,直接禁止被继承,语法更直观
cpp
class Base final // 被final修饰,不能被继承
{
public:
void func() {}
};
六.继承与友元
友元关系不能继承:基类的友元函数只能访问基类的私有 / 保护成员,无权访问派生类的私有 / 保护成员
cpp
class Student;
class Person
{
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);
protected:
int _stuNum;
};
void Display(const Person& p, const Student& s)
{
cout << p._name << endl; // 合法,Person的友元
cout << s._stuNum << endl; // 不加友元则编译报错
}
七.继承与静态成员
- 基类的静态成员在整个继承体系中只有一份实例,被基类和所有派生类共享
- 访问方式:
基类名::静态成员、派生类名::静态成员、对象访问 - public 继承下,派生类可以直接访问基类公有静态成员
cpp
class Person
{
public:
static int _count; // 统计总人数
};
int Person::_count = 0;
class Student : public Person {};
int main()
{
Person p;
Student s;
// 地址相同,说明共享同一份静态成员
cout << &Person::_count << endl;
cout << &Student::_count << endl;
return 0;
}
八.多继承与菱形继承
8.1 单继承与多继承
单继承 :一个派生类只有一个直接基类
多继承:一个派生类有两个及以上直接基类
8.2 多继承的对象模型
派生类对象内存布局:按继承顺序排列各个基类成员,最后放置派生类自身成员
指针偏移现象:不同基类指针指向同一个派生类对象时,地址值可能不同。第一个基类地址与派生类地址相同,后续基类地址有偏移
8.3 菱形继承
定义:
多继承的特殊场景:两个中间基类共同继承同一个顶层基类,最终派生类多继承两个中间基类,形成菱形结构。
两大核心问题:
- 数据冗余:顶层基类的成员在最终派生类中存在多份副本
- 二义性:访问顶层基类成员时,无法确定访问哪个基类的副本
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
{
// _name存在两份,直接访问a._name会报二义性错误
};
8.4 虚继承
作用 :
解决菱形继承的数据冗余与二义性问题,让公共基类只保留一份成员实例。
语法 :
在继承方式前加 virtual 关键字:
cpp
class Student : virtual public Person {};
class Teacher : virtual public Person {};
8.5 底层原理与构造规则
- 底层实现:通过虚基类指针与虚基类表,记录虚基类的内存偏移量,保证所有路径共享同一份虚基类成员,会带来轻微性能开销
- 构造规则 :虚基类由最派生类负责初始化。即最终派生类必须直接调用虚基类的构造函数,中间派生类不会重复调用虚基类构造,保证只初始化一次
8.6 标准库实例:IO流体系
C++ IO 流库是菱形虚继承的典型应用:
- 顶层基类:
ios_base - 中间层:
basic_istream、basic_ostream分别虚继承ios_base - 底层:
basic_iostream多继承输入输出流
通过虚继承保证公共基类只存在一份,避免冗余与二义性
九.继承与组合
9.1 核心对比
| 关系 | 语义 | 复用类型 | 封装性 | 耦合度 |
|---|---|---|---|---|
| 继承 | is-a(是一个) | 白箱复用,基类内部对派生类可见 | 弱,基类变化直接影响派生类 | 高 |
| 组合 | has-a(有一个) | 黑箱复用,仅通过公共接口交互 | 强,对象内部细节不可见 | 低 |
9.2 选择原则
- 优先使用组合:耦合度更低,封装性更好,代码维护成本低
- 类之间是明确的 is-a 关系,或需要实现多态时,使用继承
- 两种关系都适用时,优先选择组合
9.3 典型案例:stack与vector
stack 既可以通过继承 vector 实现,也可以通过组合 vector 实现。STL 标准库选择组合方式(容器适配器模式),更好地封装底层接口,只对外暴露栈语义
🎯总结
- 继承是类级别的代码复用机制,派生类继承基类成员并扩展自身功能,语义为 is-a
- 三种继承方式中 public 继承最常用,基类 private 成员在派生类中始终不可见
- 基类是类模板时,派生类访问基类成员必须加类域限定
- 派生类对象可向上转换为基类指针 / 引用,对象赋值会发生切片;向下转换不安全
- 派生类同名成员会隐藏基类成员,函数名相同即隐藏,区别于同一作用域的重载
- 派生类构造先调用基类构造,析构后调用基类析构;拷贝构造与赋值重载需显式调用基类版本
- final 关键字可修饰类禁止继承,友元关系不能继承,静态成员继承体系共享一份
- 多继承会引发菱形继承问题,通过虚继承解决,虚基类由最派生类负责初始化
- 继承是白箱复用耦合度高,组合是黑箱复用耦合度低,优先使用组合
⚠️易错点
- 基类 private 成员在派生类中不可见,误以为没有被继承
- 类模板继承时,访问基类成员漏写类域限定,导致找不到标识符
- 派生类对象赋值给基类对象,忽略对象切片问题
- 混淆函数重载与同名隐藏,以为不同参数就不会被隐藏
- 派生类拷贝构造 / 赋值重载不调用基类版本,导致基类部分浅拷贝
- 构造析构顺序记反,误以为派生类先构造、基类先析构
- 友元关系可以继承,试图通过基类友元访问派生类私有成员
- 菱形继承直接访问公共基类成员,出现二义性编译错误
- 虚继承时中间类负责初始化虚基类,不符合最派生类初始化规则
- 滥用继承,is-a 关系不明确时强行继承,导致耦合度过高维护困难
- 基类析构不加 virtual,通过基类指针删除派生对象造成内存泄漏
- 多继承基类指针偏移,误以为不同基类指针地址相同
👀 关注 我们一路同行,从入门到大师,慢慢沉淀、稳步成长
❤️ 点赞 鼓励原创,让优质内容被更多人看见
⭐ 收藏 收好核心知识点与实战技巧,需要时随时查阅
💬 评论 分享你的疑问或踩坑经历,一起交流避坑、共同进步