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

    • 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++ 专业术语

继承基础

定义与作用

继承允许一个类(派生类)复用另一个类(基类)的成员,是面向对象编程中实现代码复用和"is-a"关系的基础机制:

class Person { /* 姓名、年龄 */ };
class Student : public Person { /* 学号、GPA */ };  // Student is-a Person

继承的核心价值:代码复用 + 多态接口。

核心原理

三种继承方式下的访问权限变化

派生类对象的构造与析构过程

构造顺序:基类 → 派生类成员(声明顺序) → 派生类构造函数体 析构顺序:完全相反

完整示例

示例一:学生信息系统的继承层次

场景说明:设计 Person → Student → GraduateStudent 的继承链,展示属性和行为的复用。

#include <iostream>
#include <string>
#include <vector>

class Person {
public:
    Person(std::string name, int age)
        : name_(std::move(name)), age_(age) {}

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

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

    virtual ~Person() = default;

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

class Student : public Person {
public:
    Student(std::string name, int age, std::string student_id, std::string major)
        : Person(std::move(name), age),  // 调用基类构造函数
          student_id_(std::move(student_id)),
          major_(std::move(major)) {}

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

    void introduce() const override {
        std::cout << "我是学生 " << name_ << ",学号 " << student_id_
                  << ",专业 " << major_ << "。" << std::endl;
    }

protected:
    std::string student_id_;
    std::string major_;
};

class GraduateStudent : public Student {
public:
    GraduateStudent(std::string name, int age, std::string student_id,
                    std::string major, std::string advisor)
        : Student(std::move(name), age, std::move(student_id), std::move(major)),
          advisor_(std::move(advisor)) {}

    void introduce() const override {
        std::cout << "我是研究生 " << name_ << ",学号 " << student_id_
                  << ",专业 " << major_ << ",导师 " << advisor_ << "。" << std::endl;
    }

    void doResearch() const {
        std::cout << name_ << " 正在导师 " << advisor_ << " 指导下做研究。" << std::endl;
    }

private:
    std::string advisor_;
};

int main() {
    std::cout << "===== 继承层次演示 =====" << std::endl;

    Person person("张老师", 45);
    person.introduce();

    Student student("张三", 20, "2024001", "计算机科学");
    student.introduce();

    GraduateStudent grad("李四", 24, "2024002", "人工智能", "刘教授");
    grad.introduce();
    grad.doResearch();

    // 基类指针指向派生类对象
    std::cout << "\n===== 多态调用 =====" << std::endl;
    const Person& ref = grad;
    ref.introduce();  // 动态绑定:调用 GraduateStudent::introduce

    return 0;
}

预期输出:

===== 继承层次演示 =====
我叫 张老师,今年 45 岁。
我是学生 张三,学号 2024001,专业 计算机科学。
我是研究生 李四,学号 2024002,专业 人工智能,导师 刘教授。
李四 正在导师 刘教授 指导下做研究。

===== 多态调用 =====
我是研究生 李四,学号 2024002,专业 人工智能,导师 刘教授。

逐段分析:

  • Student 通过 public Person 继承,复用 name_、age_、getName()、getAge()
  • 派生类构造函数必须在初始化列表中调用基类构造函数:Person(std::move(name), age)
  • introduce() 是虚函数——const Person& ref = grad; ref.introduce(); 调用的是 GraduateStudent 的版本(动态绑定)
  • name_、student_id_、major_ 声明为 protected——派生类可以访问,外部不可访问
  • 继承层次:Person → Student → GraduateStudent,每层增加新的属性和行为

示例二:继承构造函数(C++11)

场景说明:使用 C++11 的 using 声明继承基类的构造函数,避免手写转发构造。

#include <iostream>
#include <string>

class Employee {
public:
    Employee(std::string name) : name_(std::move(name)) {
        std::cout << "Employee 构造: " << name_ << std::endl;
    }

    Employee(std::string name, int id)
        : name_(std::move(name)), id_(id) {
        std::cout << "Employee 构造: " << name_ << " (ID:" << id_ << ")" << std::endl;
    }

    virtual ~Employee() = default;

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

protected:
    std::string name_;
    int id_ = 0;
};

class Engineer : public Employee {
public:
    using Employee::Employee;  // C++11 继承构造函数

    void code() const {
        std::cout << name_ << " 正在写代码..." << std::endl;
    }
};

int main() {
    std::cout << "===== 继承构造函数 =====" << std::endl;

    // 直接使用继承来的基类构造函数
    Engineer e1("张三");
    e1.code();

    Engineer e2("李四", 10086);
    e2.code();

    return 0;
}

预期输出:

===== 继承构造函数 =====
Employee 构造: 张三
张三 正在写代码...
Employee 构造: 李四 (ID:10086)
李四 正在写代码...

逐段分析:

  • using Employee::Employee; 将基类的所有构造函数"提升"到派生类——不必手写转发
  • 编译器为每个基类构造函数生成一个对应的派生类构造函数,参数完全一致
  • 派生类自己的成员变量(如果有)会使用其类内初始化器
  • 这是 C++11 引入的语法糖,替代了 C++98 中手写 Engineer(string name) : Employee(name) {} 的样板代码

易错场景与面试考点

易错场景

场景错误表现正确做法
派生类构造函未调用基类构造函数基类部分未初始化在初始化列表中显式调用基类构造函数
private 继承后外部无法访问基类接口编译错误用 public 继承(默认 is-a 关系)或 using 声明
派生类中定义了与基类同名的非虚函数隐藏(非覆盖)用 override 标记虚函数覆盖
继承构造函数的参数与派生类成员初始化冲突派生类成员使用默认值明确 NSDMI 或重写构造函数
基类没有默认构造函数时派生类省略基类初始化编译错误必须显式调用基类的有参构造函数

常见面试问题

  1. 三种继承方式的区别?各自的使用场景?——public 继承保持基类访问权限不变(最常用,表达 is-a)。protected 继承将 public 降为 protected(表达"实现复用但非 is-a")。private 继承将所有基类成员变为 private(表达"用...实现",优先用组合替代)。

  2. 派生类对象的构造和析构顺序?——构造:基类 → 成员(声明顺序) → 构造函数体。析构:完全相反。这保证了派生类析构时可以安全使用基类资源。

  3. 继承构造函数做了什么?什么场景使用?——using Base::Base(C++11)让编译器自动为派生类生成转发到基类的构造函数。用于派生类没有新增需要初始化参数的成员时。

  4. 派生类中定义与基类同名的非虚函数会怎样?——隐藏基类同名函数,即使是不同参数签名。要覆盖虚函数必须用 override 标记。

  5. "is-a" 和 "has-a" 的区别?分别用什么表达?——is-a 是继承关系(Student is-a Person → public 继承)。has-a 是组合关系(Car has-a Engine → 成员对象)。优先组合而非继承。

小结

  • public 继承表达 is-a 关系,是最常用的继承方式
  • 派生类构造时必须先在初始化列表调用基类构造函数
  • 构造顺序:基类 → 成员 → 函数体;析构顺序完全相反
  • using Base::Base(C++11)继承构造函数,消除转发样板代码
  • 优先组合而非继承——继承是 C++ 中最强的耦合关系
下一页
虚函数与多态