继承与多态_继承篇

个人主页 : 流年如梦

专栏 : 《零基础轻松入门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 成员 派生类中不可见 派生类中不可见 派生类中不可见

补充说明:

  1. private 成员始终不可见:基类私有成员会被继承到派生类对象中,但语法限制派生类直接访问
  2. 权限变化口诀 :派生类中成员访问权限 = min(基类成员权限, 继承方式),权限优先级 public > protected > private
  3. 默认继承方式 :class 关键字默认 private 继承,struct 关键字默认 public 继承;工程中几乎都使用 public 继承
  4. 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 向上转换(派生类 --> 基类)

派生类对象可以赋值给基类的指针、引用或对象,属于天然安全的转换

  1. 指针 / 引用转换:直接绑定派生类对象中的基类部分,无额外开销,不会创建新对象
cpp 复制代码
Student s;
Person* p = &s; // 基类指针指向派生类对象
Person& r = s;  // 基类引用绑定派生类对象
  1. 对象赋值(对象切片):调用基类拷贝构造,只拷贝基类部分的成员,派生类部分被 "切割" 掉,也叫对象切片
cpp 复制代码
Student s;
Person p = s; // 发生切片,仅保留基类成员

2.2 向下转换(基类 --> 派生类)

  1. 不能隐式转换,强制转换语法可行但不安全,访问派生类成员会越界
  2. 只有当基类指针 / 引用实际指向派生类对象时,向下转换才安全
  3. 安全方案:多态场景下使用 dynamic_cast 运行时类型识别

三.继承中的作用域与隐藏

3.1 隐藏规则

  1. 基类与派生类拥有独立的作用域
  2. 派生类定义与基类同名的成员,会隐藏基类的同名成员,直接访问默认使用派生类版本
  3. 成员函数的隐藏:只要函数名相同就构成隐藏,与参数列表无关
  4. 显式访问基类成员:基类名::成员名
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 构造函数

  1. 派生类构造函数必须先调用基类构造,初始化基类部分成员
  2. 调用阶段:初始化列表阶段,先执行基类构造,再执行派生类构造函数体
  3. 基类没有默认构造时,派生类必须在初始化列表显式调用基类带参构造

4.2 拷贝构造函数

  1. 派生类拷贝构造必须显式调用基类拷贝构造,完成基类部分的深拷贝
  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 析构函数

  1. 析构顺序:先调用派生类析构,再自动调用基类析构,与构造顺序完全相反。
  2. 编译器会将所有析构函数名统一处理为 destructor,因此基类与派生类析构构成隐藏关系。
  3. 重要隐患:基类析构不加 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; // 不加友元则编译报错
}

七.继承与静态成员

  1. 基类的静态成员在整个继承体系中只有一份实例,被基类和所有派生类共享
  2. 访问方式:基类名::静态成员、派生类名::静态成员、对象访问
  3. 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 菱形继承

定义:

多继承的特殊场景:两个中间基类共同继承同一个顶层基类,最终派生类多继承两个中间基类,形成菱形结构。

两大核心问题:

  1. 数据冗余:顶层基类的成员在最终派生类中存在多份副本
  2. 二义性:访问顶层基类成员时,无法确定访问哪个基类的副本
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 底层原理与构造规则

  1. 底层实现:通过虚基类指针与虚基类表,记录虚基类的内存偏移量,保证所有路径共享同一份虚基类成员,会带来轻微性能开销
  2. 构造规则 :虚基类由最派生类负责初始化。即最终派生类必须直接调用虚基类的构造函数,中间派生类不会重复调用虚基类构造,保证只初始化一次

8.6 标准库实例:IO流体系

C++ IO 流库是菱形虚继承的典型应用:

  • 顶层基类:ios_base
  • 中间层:basic_istream、basic_ostream 分别虚继承 ios_base
  • 底层:basic_iostream 多继承输入输出流
    通过虚继承保证公共基类只存在一份,避免冗余与二义性

九.继承与组合

9.1 核心对比

关系 语义 复用类型 封装性 耦合度
继承 is-a(是一个) 白箱复用,基类内部对派生类可见 弱,基类变化直接影响派生类 高
组合 has-a(有一个) 黑箱复用,仅通过公共接口交互 强,对象内部细节不可见 低

9.2 选择原则

  1. 优先使用组合:耦合度更低,封装性更好,代码维护成本低
  2. 类之间是明确的 is-a 关系,或需要实现多态时,使用继承
  3. 两种关系都适用时,优先选择组合

9.3 典型案例:stack与vector

stack 既可以通过继承 vector 实现,也可以通过组合 vector 实现。STL 标准库选择组合方式(容器适配器模式),更好地封装底层接口,只对外暴露栈语义

🎯总结

  • 继承是类级别的代码复用机制,派生类继承基类成员并扩展自身功能,语义为 is-a
  • 三种继承方式中 public 继承最常用,基类 private 成员在派生类中始终不可见
  • 基类是类模板时,派生类访问基类成员必须加类域限定
  • 派生类对象可向上转换为基类指针 / 引用,对象赋值会发生切片;向下转换不安全
  • 派生类同名成员会隐藏基类成员,函数名相同即隐藏,区别于同一作用域的重载
  • 派生类构造先调用基类构造,析构后调用基类析构;拷贝构造与赋值重载需显式调用基类版本
  • final 关键字可修饰类禁止继承,友元关系不能继承,静态成员继承体系共享一份
  • 多继承会引发菱形继承问题,通过虚继承解决,虚基类由最派生类负责初始化
  • 继承是白箱复用耦合度高,组合是黑箱复用耦合度低,优先使用组合

⚠️易错点

  1. 基类 private 成员在派生类中不可见,误以为没有被继承
  2. 类模板继承时,访问基类成员漏写类域限定,导致找不到标识符
  3. 派生类对象赋值给基类对象,忽略对象切片问题
  4. 混淆函数重载与同名隐藏,以为不同参数就不会被隐藏
  5. 派生类拷贝构造 / 赋值重载不调用基类版本,导致基类部分浅拷贝
  6. 构造析构顺序记反,误以为派生类先构造、基类先析构
  7. 友元关系可以继承,试图通过基类友元访问派生类私有成员
  8. 菱形继承直接访问公共基类成员,出现二义性编译错误
  9. 虚继承时中间类负责初始化虚基类,不符合最派生类初始化规则
  10. 滥用继承,is-a 关系不明确时强行继承,导致耦合度过高维护困难
  11. 基类析构不加 virtual,通过基类指针删除派生对象造成内存泄漏
  12. 多继承基类指针偏移,误以为不同基类指针地址相同

👀 关注 我们一路同行,从入门到大师,慢慢沉淀、稳步成长

❤️ 点赞 鼓励原创,让优质内容被更多人看见

⭐ 收藏 收好核心知识点与实战技巧,需要时随时查阅

💬 评论 分享你的疑问或踩坑经历,一起交流避坑、共同进步

相关推荐
沫璃染墨1 小时前
从零入门计算机网络系列(十):HTTP协议进阶——从资源路径解析到状态码与请求头详解
linux·运维·服务器·网络·c++·http
xiaoye-duck2 小时前
《C++ 工具库实战》高性能日志 spdlog:从基础使用到工程化封装
c++·第三方库
努力努力再努力wz2 小时前
【Lua入门系列】从 C++ 迭代器到 Lua 迭代器:一文深入理解 Lua 有状态以及无状态迭代器与泛型for、闭包、ipairs、pairs 和 next
开发语言·c++·lua
努力实干2 小时前
第二阶段 · 第 7 课:C++ 多线程与线程安全
开发语言·c++·安全
小菜要扣篮3 小时前
Ubuntu 下使用 vscode 打开 C++ 项目后,无法通过 F12 定位到定义问题解决
c++·vscode·ubuntu
wuminyu3 小时前
JVM规范学习总结:class执行性能差异
java·linux·c语言·jvm·c++
旖旎夜光4 小时前
LCR 170:交易逆序对的总数(归并排序) —— 题解
c++·学习·算法·leetcode·排序算法
C++ 老炮儿的技术栈4 小时前
Linux 动态库踩坑:明明重新编译了 .so,调用函数返回值却一直不变(libfjrobot.so 版本号异常排查实录)
linux·c语言·网络·c++·visual studio
dtq042413 小时前
C++小案例 - 日志实战
开发语言·c++