乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • C++ 学习路径
  • 第1章 C++概述与开发环境

    • C++ 语言概述与编译模型
    • 第一个 C++ 程序与规范
    • 开发环境配置
    • 预处理指令详解
  • 第2章 基本语法与类型系统

    • 变量与基本类型
    • 类型转换
    • 枚举类型
    • 引用与指针
    • 数组与 stdarray
    • 字符串与原始字符串字面量
    • const 与 constexpr
    • nullptr 与空指针
    • 类型推导 auto 与 decltype
    • 基于范围的 for 循环
    • 值类别全面解析
    • char16_t 与 char32_t
    • static_assert 编译期断言
  • 第3章 函数与重载

    • 函数声明与定义
    • 函数重载
    • 默认参数与内联函数
    • Lambda 表达式
    • 函数对象与 stdfunction
    • 后置返回类型与 noexcept
  • 第4章 类与对象

    • 类的基本定义
    • 构造函数与析构函数
    • 拷贝控制
    • 移动构造函数与移动赋值
    • 列表初始化与类内初始化器
    • 静态成员与嵌套类
    • 友元
    • =default 与 =delete
  • 第5章 继承与多态

    • 继承基础
    • 虚函数与多态
    • 虚函数表与动态绑定原理
    • 虚析构函数
    • 抽象类与纯虚函数
    • 多重继承与虚继承
    • 继承构造函数
  • 第6章 运算符重载

    • 运算符重载基础
    • 算术与关系运算符重载
    • 赋值与移动运算符重载
    • 特殊运算符重载
  • 第7章 模板与泛型编程

    • 函数模板
    • 类模板
    • 模板特化与偏特化
    • 可变参数模板
    • 别名模板与模板模板参数
    • SFINAE 与类型萃取
    • 依赖名与 typename/template 关键字
  • 第8章 异常处理

    • 异常处理机制
    • noexcept 与异常安全
    • RAII 与异常安全实践
  • 第9章 内存管理与智能指针

    • 动态内存与内存分区
    • RAII 惯用法
    • unique_ptr
    • shared_ptr 与 weak_ptr
    • 内存管理最佳实践
  • 第10章 右值引用与移动语义

    • 右值引用与值类别深度解析
    • std::move 原理与使用
    • 完美转发与 std::forward
    • 移动语义性能对比与最佳实践
  • 第11章 STL容器

    • vector 深度剖析
    • deque 内部机制
    • list 与 forward_list
    • map 与 set 深度解析
    • unordered 容器与哈希原理
    • array 与 tuple
    • 容器适配器
    • 容器选择全景指南
  • 第12章 STL算法与迭代器

    • 迭代器体系全解
    • 非变异算法
    • 变异算法
    • 排序与二分算法
    • Lambda 与算法组合
    • std::random 随机数库
    • 自定义迭代器开发
  • 第13章 IO流与文件

    • 标准 IO 流
    • 格式化输出控制
    • 文件流操作
    • 字符串流
    • std::regex 正则表达式
  • 第14章 并发与多线程

    • thread 基础与线程管理
    • mutex 与 lock_guard
    • unique_lock 与 condition_variable
    • thread_local 线程局部存储
    • atomic 与内存序
    • future 与 async 异步编程
    • std::chrono 时间库
  • 第15章 现代C++新特性

    • 从 C++11 到 C++20 演进路线
    • C++14 关键新特性
    • C++17 关键新特性
    • C++20 核心特性速览
  • 第16章 面试考点与最佳实践

    • C++ 综合最佳实践清单
    • 高频面试题精讲
    • 多线程面试题与实战
    • 内存管理常见陷阱与排查
  • 附录

    • C++ 核心知识点
    • C++ 专业术语

多重继承与虚继承

定义与作用

多重继承(Multiple Inheritance)允许一个类同时继承多个基类:

class TeachingAssistant : public Student, public Employee {
    // 同时拥有 Student 和 Employee 的属性和行为
};

虚继承(Virtual Inheritance)解决多重继承中的"菱形问题"——当某个基类通过多条路径被继承时,虚继承保证只有一份共享的基类子对象:

class Student : virtual public Person { ... };
class Employee : virtual public Person { ... };
class TA : public Student, public Employee { ... };  // 只有一份 Person

核心原理

菱形继承问题

虚继承解决菱形继承

虚继承下的对象内存布局

完整示例

示例一:助教系统的菱形继承

场景说明:研究生助教同时是学生和员工,展示虚继承的必要性。

#include <iostream>
#include <string>

// 顶层基类
class Person {
public:
    Person(std::string name, int age)
        : name_(std::move(name)), age_(age) {
        std::cout << "  Person 构造: " << name_ << std::endl;
    }

    virtual ~Person() = default;

    const std::string& getName() const { return name_; }
    int getAge() const { return age_; }

    virtual void introduce() const {
        std::cout << "我叫 " << name_ << ",年龄 " << age_;
    }

protected:
    std::string name_;
    int age_;
};

// 学生分支:虚继承
class Student : virtual public Person {
public:
    Student(std::string name, int age, std::string student_id)
        : Person(std::move(name), age),
          student_id_(std::move(student_id)) {
        std::cout << "    Student 构造: " << student_id_ << std::endl;
    }

    const std::string& getStudentID() const { return student_id_; }

    void introduce() const override {
        Person::introduce();
        std::cout << ",学生,学号 " << student_id_;
    }

protected:
    std::string student_id_;
};

// 员工分支:虚继承
class Employee : virtual public Person {
public:
    Employee(std::string name, int age, std::string employee_id, double salary)
        : Person(std::move(name), age),
          employee_id_(std::move(employee_id)), salary_(salary) {
        std::cout << "    Employee 构造: " << employee_id_ << std::endl;
    }

    const std::string& getEmployeeID() const { return employee_id_; }
    double getSalary() const { return salary_; }

    void introduce() const override {
        Person::introduce();
        std::cout << ",员工,工号 " << employee_id_;
    }

protected:
    std::string employee_id_;
    double salary_;
};

// 助教:多重继承 + 虚继承
class TeachingAssistant : public Student, public Employee {
public:
    TeachingAssistant(std::string name, int age,
                      std::string student_id,
                      std::string employee_id,
                      double salary,
                      std::string course)
        : Person(std::move(name), age),       // TA 直接构造共享的 Person
          Student(name_, age_, std::move(student_id)),  // 不再初始化 Person
          Employee(name_, age_, std::move(employee_id), salary),
          course_(std::move(course)) {
        std::cout << "      TA 构造完成: " << name_
                  << " 助教课程 " << course_ << std::endl;
    }

    void introduce() const override {
        // 只有一个 name_,无二义性
        std::cout << "我是助教 " << name_ << ",学号 " << student_id_
                  << ",工号 " << employee_id_ << ",负责 " << course_;
    }

    void teach() const {
        std::cout << name_ << " 正在给 " << course_ << " 课程的学生答疑。" << std::endl;
    }

private:
    std::string course_;
};

int main() {
    std::cout << "===== 创建助教对象 =====" << std::endl;
    TeachingAssistant ta("张三", 24, "S2024001", "E10086", 3000.0, "C++ 程序设计");

    std::cout << "\n===== 信息输出 =====" << std::endl;
    ta.introduce();
    std::cout << std::endl;

    // 无二义性访问(虚继承保证只有一份 Person)
    std::cout << "姓名: " << ta.getName() << std::endl;
    std::cout << "年龄: " << ta.getAge() << std::endl;
    std::cout << "学号: " << ta.getStudentID() << std::endl;
    std::cout << "工号: " << ta.getEmployeeID() << std::endl;
    std::cout << "薪资: ¥" << ta.getSalary() << std::endl;
    ta.teach();

    // 多态使用
    std::cout << "\n===== 基类指针 =====" << std::endl;
    Person* p = &ta;
    p->introduce();
    std::cout << std::endl;

    Student* s = &ta;
    s->introduce();
    std::cout << std::endl;

    return 0;
}

预期输出:

===== 创建助教对象 =====
  Person 构造: 张三
    Student 构造: S2024001
    Employee 构造: E10086
      TA 构造完成: 张三 助教课程 C++ 程序设计

===== 信息输出 =====
我是助教 张三,学号 S2024001,工号 E10086,负责 C++ 程序设计
姓名: 张三
年龄: 24
学号: S2024001
工号: E10086
薪资: ¥3000
张三 正在给 C++ 程序设计 课程的学生答疑。

===== 基类指针 =====
我是助教 张三,学号 S2024001,工号 E10086,负责 C++ 程序设计
我是助教 张三,学号 S2024001,工号 E10086,负责 C++ 程序设计

逐段分析:

  • 虚继承:Student 和 Employee 都使用 virtual public Person——TA 中只有一份 Person
  • 最派生类负责构造虚基类:TA 的初始化列表必须直接调用 Person(name, age)。Student 和 Employee 中对 Person 的构造调用在 TA 创建时被忽略
  • 若不用虚继承,TA 会包含两份 Person——ta.getName() 将产生二义性编译错误
  • name_ 只有一份,无二义性访问
  • 所有 introduce() 最终都调用 TA 的版本——多态工作正常

示例二:接口类的多重继承

#include <iostream>
#include <memory>
#include <string>

// 接口 1:可序列化
class ISerializable {
public:
    virtual ~ISerializable() = default;
    virtual std::string serialize() const = 0;
};

// 接口 2:可比较
class IComparable {
public:
    virtual ~IComparable() = default;
    virtual int compare(const IComparable& other) const = 0;
};

// 接口 3:可克隆
class ICloneable {
public:
    virtual ~ICloneable() = default;
    virtual std::unique_ptr<ICloneable> clone() const = 0;
};

// 具体类:多重继承自三个接口(无菱形问题,因为接口无数据成员)
class Student : public ISerializable, public IComparable, public ICloneable {
public:
    Student(std::string name, double gpa)
        : name_(std::move(name)), gpa_(gpa) {}

    std::string serialize() const override {
        return "Student{name:" + name_ + ",gpa:" + std::to_string(gpa_) + "}";
    }

    int compare(const IComparable& other) const override {
        const auto& s = dynamic_cast<const Student&>(other);
        if (gpa_ > s.gpa_) return 1;
        if (gpa_ < s.gpa_) return -1;
        return 0;
    }

    std::unique_ptr<ICloneable> clone() const override {
        return std::make_unique<Student>(*this);
    }

    void print() const {
        std::cout << name_ << " (GPA: " << gpa_ << ")" << std::endl;
    }

private:
    std::string name_;
    double gpa_;
};

int main() {
    std::cout << "===== 多接口继承 =====" << std::endl;

    Student alice("张三", 3.8);
    Student bob("李四", 3.5);

    // 作为 ISerializable 使用
    ISerializable* ser = &alice;
    std::cout << "序列化: " << ser->serialize() << std::endl;

    // 作为 IComparable 使用
    IComparable* cmp = &alice;
    int result = cmp->compare(bob);
    std::cout << "比较结果: " << (result > 0 ? "张三 > 李四" :
                                 result < 0 ? "张三 < 李四" : "相等") << std::endl;

    // 克隆
    ICloneable* cln = &alice;
    auto cloned = cln->clone();
    std::cout << "克隆序列化: " << cloned->serialize() << std::endl;

    return 0;
}

预期输出:

===== 多接口继承 =====
序列化: Student{name:张三,gpa:3.800000}
比较结果: 张三 > 李四
克隆序列化: Student{name:张三,gpa:3.800000}

易错场景与面试考点

易错场景

场景错误表现正确做法
菱形继承未用虚继承成员访问二义性共享的基类用 virtual 继承
虚基类的构造参数被中间类覆盖使用了错误的构造参数最派生类直接指定虚基类构造参数
多重继承时类型转换歧义dynamic_cast 失败明确转换路径或使用 static_cast + 显式指定
虚继承下的 static_cast偏移计算复杂优先用 dynamic_cast
接口类有数据成员菱形继承问题接口类(纯虚类)不应有数据成员

常见面试问题

  1. 什么是菱形继承问题?虚继承如何解决?——当基类通过多条路径被继承时,最派生类有多个基类子对象,导致二义性。虚继承让该基类只保留一份子对象。

  2. 虚继承和普通继承在对象布局上的区别?——普通继承基类子对象内嵌在派生类中。虚继承通过 vptr 或偏移表间接定位共享的基类子对象,通常放在对象末尾。

  3. 虚基类由谁初始化?——最派生类负责调用虚基类的构造函数。中间类的构造调用在最派生类构造时被忽略。

  4. 什么时候应该使用多重继承?什么时候应该避免?——接口类(纯虚)的多重继承安全且推荐。有数据成员的多重继承需谨慎,容易引发菱形问题。

  5. 虚继承的性能代价?——访问虚基类成员需要间接寻址(额外指针解引用),对象大小略增加。在热路径中注意,一般场景影响可忽略。

小结

  • 菱形继承 → 虚继承:virtual public Base 让最派生类只保留一份共享基类
  • 虚基类由最派生类直接初始化——中间类的初始化调用被忽略
  • 接口类的多重继承是安全的——无数据成员,无菱形问题
  • 优先组合而非继承;必须多重继承时优先接口继承(纯虚类)
  • 虚继承有轻微性能代价(间接寻址),但在大多数场景可忽略
上一页
抽象类与纯虚函数
下一页
继承构造函数