Skip to main content

Command Palette

Search for a command to run...

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

Updated
•7 min read•View as Markdown
C++ 类的继承与派生 学习文档
T
确定性世界里,一个被允许的异常

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

#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 核心特性:仅发挥基类作用

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

#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实参), // 主动调用基类有参构造

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

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

{

其他初始化操作;

}

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

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 代码示例:多继承 + 内嵌对象构造

#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 同名成员隐藏规则

派生类定义与基类同名函数(无论参数是否相同),基类所有重载函数被隐藏;

同名隐藏≠函数重载(重载要求同一作用域);

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

示例:

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 代码示例

#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 正确重写示例

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,实现统一接口调用不同子类逻辑

#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 代码对比示例(有无虚析构区别)

错误示例:普通析构函数(内存泄漏)

#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 虚析构函数(完全析构)

#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 强制校验重写合法性,纯虚函数定义抽象类接口,是面向对象核心考点。

5 views

More from this blog

离散数学5.1-二元关系一

一、有序对和笛卡尔积 1、有序对(序偶):由两个元素 x 和 y 按照确定顺序排列组成的二元组,记作⟨x, y⟩ 2、笛卡尔积:以 A 中元素为第一元、B 中元素为第二元,构造所有有序对⟨x,y⟩; 由全部这类有序对构成的集合,称为 A 与 B 的笛卡尔积,记作 AXB 例题: 已知 A={a,b}, B={0,1,2},求笛卡尔积 A × B、B × A A × B(前元取自 A,后元取自 B

Jun 25, 20261 min read14
离散数学5.1-二元关系一
天

天创域

37 posts

欢迎来到天创的博客