# C++ 类的继承与派生 学习文档

# C++ 类的继承与派生 学习文档

本文章由尚方咸鱼编写，后经是天创呀修改

本文档系统整理 C++ 面向对象编程中基类与派生类、派生类实现、访问控制、构造析构、成员标识、虚基类等核心知识点，包含定义、语法、代码示例与核心规则，适合学习与复习使用。

# 1、基类与派生类

## 1.1 类的继承与派生

类的继承：面向对象的核心机制，在保持原有类特性的基础上，进行更具体、更详细的定义。

核心用途：代码重用性（继承） + 可扩充性（派生）。

示例：植物大战僵尸中，普通僵尸为基类，路障僵尸、铁门僵尸为派生类。

继承：新类从已有类获取原有特性（站在已有类角度）。

派生：从已有类创建新类，并新增自身特性（站在新类角度）。

关系：继承与派生是同一过程，仅观察角度不同。

目的：

继承：实现代码重用；

派生：改造原有程序，解决新问题。

## 1.2 核心概念

基类 / 父类：被继承的已有类；

派生类 / 子类：通过派生得到的新类；

一个基类可以派生多个派生类；

一个派生类可以继承多个基类。

## 1.3 基类分类

直接基类：直接参与派生当前类的基类；

间接基类：基类的基类及更高层级的基类。

## 1.4 继承分类

单继承：派生类仅有1 个基类（多继承的特例）；

多继承：派生类拥有多个基类（多个单继承的组合）。

# 2、派生类

## 2.1 派生类定义语法

// 多继承语法

class 派生类名: 继承方式 基类名1, 继承方式 基类名2,... //默认私有继承

{

派生类成员声明；

};

示例：

// c1公有继承c2，私有继承c3，保护继承c4

class c1:public c2,private c3,protected c4

{

private:

int c1\_mem=0; // 新增数据成员

public:

int fun(); // 新增函数成员

};

## 2.2 代码示例：Rectangle 公有继承 Point

```cpp
#include <iostream>
using namespace std;
// 基类：点
class Point
{
public:
    Point(float xx = 0, float yy = 0) : x(xx), y(yy) {}
    void initPoint(float x = 0, float y = 0)
    {
        this->x = x;
        this->y = y;
    }
    // 移动坐标
    void move(float offX, float offY)
    {
        x += offX;
        y += offY;
    }
    void show() { cout << "this is Point's Show:" << x << y << endl; }
    float getX() const { return x; }
    float getY() const { return y; }

private:
    float x, y;
};
// 派生类：矩形，公有继承Point
class Rectangle : public Point
{
public:
    void initRectangle(float x = 0, float y = 0, float w = 0, float h = 0)
    {
        initPoint(x, y); // 调用基类成员函数
        this->w = w;
        this->h = h;
    }
    void show() { cout << "this is Rectangle's Show:" << w << h << endl; }
    float getW() const { return w; }
    float getH() const { return h; }

private:
    float w, h; // 派生类新增成员
};
int main()
{
    Rectangle r1;
    r1.initRectangle(1, 1, 3.0, 4.0);
    r1.move(2, 1); // 调用基类成员函数
    cout << r1.getX() << ',' << r1.getY() << ',' << r1.getW() << ',' << r1.getH() << endl;
    r1.show();
    return 0;
}
```

规则：派生类会继承基类所有非静态成员（构造函数、析构函数除外）。

## 2.3 访问控制

### 2.3.1 继承访问规则总表

| 基类成员 | 公有继承 (public) | 私有继承 (private) | 保护继承 (protected) |
| --- | --- | --- | --- |
| public | public | private | protected |
| protected | protected | private | protected |
| private | 不可访问 | 不可访问 | 不可访问 |

详细说明：

基类public成员：

公有继承：子类内 / 外均可访问；

私有 / 保护继承：子类内可访问，类外不可访问。

基类protected成员：三种继承方式下，子类内可访问，类外不可访问。

基类private成员：所有继承方式下，子类均不可直接访问。

简易编程建议： 测试程序中，成员声明为public/protected，使用公有继承，简化访问逻辑。

### 2.3.2 记忆技巧

基类private成员 → 派生类永远无法直接访问；

基类非private成员 → 访问权限向保守方向变化（public→protected→private）；

仅基类 public 成员 + 公有继承，可通过子类对象在类外访问。

### 2.3.3 成员访问权限基础

| 成员类型 | 类内访问 | 类外访问（通过对象） |
| --- | --- | --- |
| public | 允许 | 允许 |
| protected | 允许 | 禁止 |
| private | 允许 | 禁止 |

## 2.4 类型兼容性规则

核心定义

在需要基类对象的任何位置，都可以使用公有派生类对象替代，且派生类仅表现出基类的功能。

### 2.4.1 三大规则

派生类对象 → 隐式转换为基类对象

A a1;

B b1;

a1 = b1;

派生类对象 → 初始化基类引用

B b1;

A &rf = b1;

派生类对象地址 → 转换为基类指针

A \*p;

B b;

p = &b;

### 2.4.2 核心特性：仅发挥基类作用

示例：基类指针指向派生类对象，调用的是基类成员函数。

```cpp
#include <iostream>
using namespace std;
class Base1{
public:
    void display() const{ cout << "Base1::display()" << endl; }
};
class Base2 : public Base1{
public:
    void display() const{ cout << "Base2::display()" << endl; }
};
class Derived : public Base2{
public:
    void display() const{ cout << "Derived::display()" << endl; }
};
// 函数参数为基类指针
void fun(Base1 *p){ p->display(); }

int main()
{
    Base1 b1; Base2 b2; Derived d1;
    fun(&b1); fun(&b2); fun(&d1);
    return 0;
}
// 运行结果：全部输出 Base1::display()
```

# 3、派生类的构造函数和析构函数

## 3.1 派生类构造函数语法

// 派生类构造函数完整语法（可主动调用基类有参构造）

派生类名::派生类名(形参表):

基类名1(基类1实参), 基类名2(基类2实参), // 主动调用基类有参构造

成员对象名(对象实参), // 初始化内嵌对象有参构造

普通成员(初始值) // 初始化普通数据成员

{

其他初始化操作;

}

// 核心写法示例：基类含带参构造，派生类主动调用

```cpp
class Base{
public:
    // 仅定义带参构造，无默认无参构造
    Base(int a){ cout << "基类有参构造调用" << endl; }
};

class Derived : public Base{
public:
    // 必须在初始化列表主动调用基类有参构造，否则编译报错
    Derived(int x) : Base(x) {
        cout << "派生类构造调用" << endl;
    }
};
```

默认构造函数使用条件：

无需调用基类带参构造函数；

无需调用内嵌对象带参构造函数。

系统自动生成默认构造函数，调用基类 / 对象的默认构造函数（无参构造函数）。

核心补充规则（高频易错）：

默认调用规则：派生类默认构造函数，会自动调用所有直接基类、内嵌成员对象的无参构造函数，无需手动书写。

报错场景：若基类只定义了带参构造函数、未定义无参构造函数（系统不会自动生成默认无参构造），派生类默认构造时找不到可用的无参构造，直接编译报错。

解决方案1：基类手动补充无参构造函数。

解决方案2：派生类显式定义构造函数，在初始化列表中主动调用基类的有参构造函数，强制覆盖默认调用逻辑。

## 3.2 构造函数调用顺序 & 核心调用规则

基类构造函数：严格按照类继承声明顺序调用，与构造函数初始化列表书写顺序无关；未手动指定则默认调用无参构造，需传参则必须在初始化列表显式调用。

内嵌对象构造函数：按照类内成员声明先后顺序调用。

派生类自身构造函数：最后执行派生类构造函数函数体内部逻辑。

## 3.3 代码示例：多继承 + 内嵌对象构造

```cpp
#include <iostream>
using namespace std;
class Base1{public: Base1(int i) { cout << "Constructing Base1 " << i << endl; }};
class Base2{public: Base2(int i) { cout << "Constructing Base2 " << i << endl; }};
class Base3{public: Base3() { cout << "Constructing Base3 *" << endl; }};

// 继承顺序：Base2 → Base1 → Base3
class Derived : public Base2, public Base1, public Base3
{
public:
    Derived(int a,int b,int c,int d):Base1(a),m2(d),m1(c),Base2(b) {}
private:
    Base1 m1;
    Base2 m2;
    Base3 m3;
};
int main(){ Derived obj(1,2,3,4); return 0; }
```

运行结果：

> `Constructing Base2 2`
> 
> `Constructing Base1 1`
> 
> `Constructing Base3 *`
> 
> `Constructing Base1 3`
> 
> `Constructing Base2 4`
> 
> `Constructing Base3 *`

## 3.4 派生类复制构造函数

// 语法

`Derived::Derived(const Derived &v):Base(v){}`

系统默认生成复制构造函数，自动调用基类复制构造函数；

基类delete复制构造函数，派生类也无法拷贝。

## 3.5 派生类析构函数

语法：`Derived::~Derived(){}`，与普通类写法一致；

自动调用基类和内嵌对象的析构函数；

执行顺序：与构造函数严格相反。

析构顺序：派生类析构 → 内嵌对象析构 → 基类析构。

## 3.6 析构函数示例

对 3.3 代码添加析构函数，运行结果中析构顺序与构造完全相反。

# 4、派生类成员的标识和访问

## 4.1 作用域分辨符 ::

用于区分同名成员，格式：类名::成员名

## 4.2 同名成员隐藏规则

派生类定义与基类同名函数（无论参数是否相同），基类所有重载函数被隐藏；

同名隐藏≠函数重载（重载要求同一作用域）；

调用基类同名函数：必须加基类名::限定。

示例：

```cpp
class Base{
public:
    void print();
    void print(int i);
};
class Derived:public Base{
public:
    void print(int i,int j,int k); // 隐藏基类所有print
};
// 错误用法：d.print() / d.print(1)
// 正确用法：d.Base::print()
```

## 4.3 多继承同名二义性

多个基类有同名成员，派生类未定义 → 访问产生二义性；

解决方案：

方式 1：基类名::限定；

方式 2：派生类定义同名成员，隐藏基类成员。

## 4.4 多层继承同名问题

多个派生类继承同一个基类，会导致成员冗余、赋值不一致 → 虚基类解决。

# 5、虚基类

## 5.1 解决问题

消除多层多继承中共同基类带来的成员冗余、数据不一致问题。

直白来说：在多层 + 多重继承场景下，当多个父类又继承了同一个公共父类时，普通继承会让最终子类里重复保留多份公共父类的成员，造成数据冗余、数据错乱。

## 5.2 声明语法

class 派生类: virtual 继承方式 基类名{};

关键：第一级继承就必须声明为虚基类。

## 5.3 代码示例

```cpp
#include <iostream>
using namespace std;
// 虚基类
class Base0
{
public:
    int var0;
    void fun0() { cout << "Base0 var0=" << var0 << endl; }
};
// 虚继承
class Base1 : virtual public Base0 {};
class Base2 : virtual public Base0 {};
// 最终派生类
class Derived : public Base1, public Base2 {};

int main()
{
    Derived d;
    d.var0 = 2; // 直接访问，无冗余、无二义性
    d.fun0();
    return 0;
}
```

## 5.4 核心规则

虚基类成员由最远派生类的构造函数初始化；

仅最远派生类调用虚基类构造函数，其他类调用被忽略；

虚基类有带参构造函数时，所有派生类必须在初始化列表中初始化。

# 6、虚函数、多态与纯虚函数

承接前文类型兼容性，解决的核心问题，是继承体系的终极用途。

## 6.1 虚函数 virtual

### 6.1.1 核心作用

实现：基类指针/引用指向派生类对象时，优先调用派生类的重写方法，而非基类方法。

### 6.1.2 语法规则

基类中声明：`virtual 返回值 函数名(参数) const;`

核心：虚函数支持子类重写，实现不同子类的差异化逻辑。

## 6.2 重写与 override 关键字

### 6.2.1 重写（覆盖）条件

1、公有继承：`class 子类 : public 基类`（默认私有继承无法重写）；

2、函数签名**必须完全一致**：返回值、函数名、参数列表、**const 常量修饰**，四项缺一不可；

3、基类函数必须是 virtual 虚函数。

### 6.2.2 override 核心作用

强制编译器校验：当前函数是否**正确重写匹配基类的虚函数（含const匹配）**，不满足条件直接报错，杜绝隐性错误。

常见报错原因（实战高频）：

① 缺少 public 公有继承；② 子类函数少写/多写 const；③ 函数参数、返回值不匹配；④ 基类非虚函数。

### 6.2.3 正确重写示例

```cpp
class Shape {
public:
    // 基类虚函数，带const
    virtual double area() const = 0;
    virtual void print() const = 0;
    virtual ~Shape() {}
};

class Circle : public Shape {
public:
    double r;
    Circle(double r):r(r) {}
    // 必须带const + override，完全匹配基类签名
    void print() const override {
        cout << "Circle(" << r << ")";
    }
    double area() const override {
        return 3.14 * r * r;
    }
};
```

## 6.3 纯虚函数

### 6.3.1 定义语法

`virtual 返回值 函数名(参数) const = 0;`

### 6.3.2 核心特性

1、无函数体，仅做接口声明；

2、包含纯虚函数的类为**抽象类**；

3、抽象类，只能作为基类被继承；

4、派生类必须**全部重写所有纯虚函数，且严格匹配 const 修饰**，否则依然是抽象类，无法创建对象。

## 6.4 示例（图形计算面积）

结合继承、虚函数、重写、override，实现统一接口调用不同子类逻辑

```cpp
#include <iostream>
using namespace std;

class Shape {
public:
    virtual double area() const = 0;   // 纯虚函数：计算面积
    virtual void print() const = 0;    // 纯虚函数：打印图形信息
    virtual ~Shape() {}                // 虚析构：避免内存泄漏
};

const double PI = 3.14;
class Circle:public Shape {
public:
    double r;
    Circle(double r):r(r) {}
    void print() const override {
        cout << "Circle(" << r << ")";
    }
    double area() const override {
        return PI * r * r;
    }
};

class Rectangle:public Shape {
public:
    double l, w;
    Rectangle(double len, double wid):l(len), w(wid) {}
    void print() const override {
        cout << "Rectangle(" << l << "," << w << ")";
    }
    double area() const override {
        return l * w;
    }
};

int main() {
    double r, l, w;
    cin >> r >> l >> w;
    Circle c(r);
    Rectangle rect(l, w);

    Shape* shapes[2] = { &c, &rect };
    for (int i = 0; i < 2; ++i) {
        shapes[i]->print();
        cout << " area=" << shapes[i]->area() << endl;
    }
    return 0;
}
```

## 6.5 虚析构函数

### 6.5.1 作用

基类指针删除派生类对象时，保证**先调用派生类析构，再调用基类析构**，彻底避免内存泄漏。若不使用虚析构，仅会调用基类析构，派生类资源无法释放。

### 6.5.2 代码对比示例（有无虚析构区别）

#### 错误示例：普通析构函数（内存泄漏）

```cpp
#include <iostream>
using namespace std;

class Base {
public:
    // 普通析构，非虚析构
    ~Base() {
        cout << "基类析构函数调用" << endl;
    }
    virtual void test() {}
};

class Derived : public Base {
public:
    ~Derived() {
        cout << "派生类析构函数调用" << endl;
    }
};

int main() {
    // 基类指针指向派生类对象
    Base* p = new Derived();
    delete p; 
    // 仅调用基类析构，派生类析构不执行，内存泄漏
    return 0;
}
```

**错误运行结果**：仅输出「基类析构函数调用」，派生类析构失效。

#### 正确示例：virtual 虚析构函数（完全析构）

```cpp
#include <iostream>
using namespace std;

class Base {
public:
    // 虚析构函数
    virtual ~Base() {
        cout << "基类析构函数调用" << endl;
    }
    virtual void test() {}
};

class Derived : public Base {
public:
    ~Derived() {
        cout << "派生类析构函数调用" << endl;
    }
};

int main() {
    Base* p = new Derived();
    delete p; 
    // 先析构派生类、再析构基类，资源完全释放
    return 0;
}
```

**正确运行结果**：

派生类析构函数调用

基类析构函数调用

### 6.5.3 核心规则（必考）

1、只要类中**声明了任意虚函数**，该类析构函数必须加 virtual；

2、多态场景下（基类指针/引用操作派生类对象），必须使用虚析构；

3、虚析构不影响构造函数、不影响多态逻辑，只负责安全释放内存。

### 6.5.2 规则

只要类存在虚函数，必须将析构函数声明为 virtual。

# 总结

继承实现代码重用，派生实现功能扩展，公有继承是最常用方式；

派生类构造顺序：基类→内嵌对象→自身，析构顺序相反；

同名成员用::区分，多继承二义性通过虚基类解决；

类型兼容性规则：公有派生类对象可替代基类对象，仅表现基类功能；

虚函数实现多态，override 强制校验重写合法性，纯虚函数定义抽象类接口，是面向对象核心考点。
