C/C++ 基础笔记(十三)继承

本篇核心知识:继承与派生、继承方式、成员权限变化、构造 / 析构顺序、同名成员处理、多继承、菱形继承、虚继承、对象赋值兼容规则


一、继承与派生(概念)

概念

继承是从已有类(父类 / 基类) 快速创建**新类(子类 / 派生类)**的机制,子类会获得基类成员,并可扩展新成员。

特性

目的:代码复用、快速扩展、层次化设计。

子类 = 基类成员 + 新增成员。

不能被继承:构造函数、析构函数、赋值运算符重载、友元。

语法

复制代码
class 子类 : 继承方式 基类1, 继承方式 基类2...
{
    新增成员;
};

三种继承分类

  1. 单继承:一个子类只有一个直接基类。

  2. 多继承:一个子类有多个直接基类。

  3. 多级继承:子类再派生子类(爷→父→子)。


二、继承方式与成员权限

概念

继承方式决定基类成员在子类中的最终访问权限

三种继承方式

  1. public(公有继承)

    基类 public → 子类 public

    基类 protected → 子类 protected

    基类 private → 子类不可访问

  2. protected(保护继承)

    基类 public → 子类 protected

    基类 protected → 子类 protected

    基类 private → 子类不可访问

  3. private(私有继承)

    基类 public → 子类 private

    基类 protected → 子类 private

    基类 private → 子类不可访问

核心结论

基类private成员无论如何继承,子类都无法直接访问

公有继承最常用,不改变基类权限结构。

权限只限制外部访问基类私有成员依然会被继承下来(占内存)


三、继承后的内存布局

概念

子类对象内存 = 基类部分内存 + 子类新增成员内存

特性

基类所有数据成员都会被复制到子类中(包括 private)。

内存大小 = sizeof (基类) + sizeof (子类新增)。

内存对齐规则不变。

示例

复制代码
class Base { int a; };
class Son : public Base { int b; };
// sizeof(Son) = 4 + 4 = 8

四、子类构造与析构顺序(重点)

概念

创建子类对象时,必须先构造基类部分;销毁时先析构自身,再析构基类。

构造顺序

  1. 虚基类优先(按声明顺序)

  2. 非虚基类(按继承声明顺序,从左到右)

  3. 对象成员(按类内声明顺序)

  4. 子类自身构造

析构顺序

与构造完全相反

代码示例

复制代码
class A { public: A(){cout<<"A构造";} ~A(){cout<<"~A";} };
class B { public: B(){cout<<"B构造";} ~B(){cout<<"~B";} };
class C : public A, public B { public: C(){cout<<"C构造";} ~C(){cout<<"~C";} };
​
int main(){
    C c;
    // 输出:A构造 B构造 C构造 ~C ~B ~A
}

子类初始化基类成员

必须在初始化列表调用基类构造:

复制代码
// 基类
class Base {
public:
    int a;
    // 基类构造函数
    Base(int x) : a(x) {}
};
​
// 子类
class Sub : public Base {
public:
    int b;
    // 子类构造:基类没有无参构造函数就必须在 初始化列表 调用基类构造
    // 子对象的初始化也必须在初始化列表中进行
    Sub(int x, int y) : Base(x), b(y) {
        // 不能初始化基类成员,基类成员的初始化、给值只能在基类的构造函数中
    }
};
​
int main()
{
    Sub obj(100,10);
    // obj.a = 100; obj.b = 10;
}

拓展

一、必须在初始化列表调用基类构造的情况

只要 基类没有无参构造函数(默认构造)必须在初始化列表手动调用基类构造

必须写的 3 种场景

  1. 基类只有带参构造,没有无参构造

  2. 基类写了构造函数,但都是带参的

  3. 基类构造需要传参数才能初始化

只要满足上面任意一条 → 必须在初始化列表调用基类构造

必须写的代码样例

复制代码
class Base {
public:
    // 只有带参构造 → 子类必须手动调用
    Base(int x) {}
};
​
class Son : public Base {
public:
    // 必须写:Base(x)
    Son(int x) : Base(x) { }
};

二、不用在初始化列表写基类构造的情况

只要 基类有无参构造函数(默认构造)不用写,编译器会自动调用

不用写的 2 种场景

  1. 基类完全没写构造函数(编译器自动生成无参)

  2. 基类自己写了无参构造

满足任意一条 → 子类不用写初始化列表调用基类

不用写的代码样例

复制代码
class Base {
public:
    // 有无参构造 → 子类不用手动调用
    Base() {}
};
​
class Son : public Base {
public:
    // 不用写 Base(),编译器自动加
    Son() { }
};

五、赋值兼容规则(对象 / 指针 / 引用)

概念

公有继承下,子类对象可以当作基类对象使用(安全向上转换)。

规则

  1. 子类对象 → 赋值给 基类对象(切片,只复制基类部分,自己的部分被浪费)

  2. 子类对象 → 初始化 基类引用(无切片,无拷贝,仅仅是别名,安全)

  3. 子类地址 → 赋值给 基类指针(存储子类首地址)

反向不允许

基类不能自动转为子类(不安全,会越界)。

示例

复制代码
Father objFa;
Father *pFa = &objFa;
Self objSelf;
Self *pSe = &objSelf;
​
pFa = &objSelf;      // 合法
pSe = &objFa;        // 非法
​
objFa = objSelf;     // 合法
objSelf = objFa;     // 非法

六、同名成员处理

概念

子类与基类成员同名时,优先访问子类成员,基类成员被隐藏。

访问基类同名成员

必须加作用域::

代码示例

复制代码
class Base {
public:
    int num = 10;
};
class Son : public Base {
public:
    int num = 20;
    void show() {
        cout << num;       // 子类 20
        cout << Base::num; // 基类 10
    }
};

多继承同名冲突

多个基类有同名成员 → 必须用基类名::明确指定。


七、多继承

概念

一个子类继承多个基类

语法

复制代码
class 子类 : 继承方式 基类1, 继承方式 基类2...
{};

问题

可能出现同名成员歧义

可能出现菱形继承(重复继承祖父类)。


八、菱形继承与虚继承(虚基类)

概念

多继承中出现:A 派 B、A 派 C,B 和 C 派 D → D 中会有两份 A 成员,造成冗余与歧义。

问题

内存浪费

成员歧义

数据不一致

解决方案:虚继承 virtual

让多个子类共享同一份基类成员

语法

复制代码
class B : virtual public A {};
class C : public virtual A {}; // virtual前后两种写法都可以
class D : public B, public C {};

原理

虚继承会给子类加一个虚基类指针(占用一个指针的内存)。

最终派生类中只保留一份虚基类成员

虚继承构造规则

虚基类构造优先于同一层所有非虚基类。

最终子类必须直接初始化虚基类


九、重点

  1. 继承是为了复用 + 扩展

  2. 三种继承方式只改变权限,不改变内存。

  3. 基类私有成员会被继承,但子类不能访问

  4. 构造顺序:虚基类 → 基类 → 对象成员 → 自己

  5. 析构与构造相反。

  6. 子类可自动转基类,反之不行。

  7. 同名成员用::区分。

  8. 多继承容易歧义,菱形继承用虚继承解决。

相关推荐
ao-weilai3 小时前
C++:哈希表
c++·哈希算法·散列表
汉克老师3 小时前
GESP7级C++考试语法知识(二、指数函数(1、pow() 函数)
c++·指数函数·pow·gesp7级·精度误差
旖-旎3 小时前
FloodFill(图像渲染)(1)
c++·算法·深度优先·力扣
汉克老师4 小时前
GESP2026年3月认证C++六级真题与解析(编程题1 选数)
c++·动态规划·线性dp·gesp六级·状态转移·选与不选
有点。4 小时前
C++倍增法(练习题)
c++·算法
凡人叶枫4 小时前
Effective C++ 条款23:宁以 non-member、non-friend 替换 member 函数
linux·开发语言·c++·嵌入式开发
2601_950526435 小时前
程序设计语言(C)
c语言·数据类型·实验教学·编译预处理·程序设计语言(c)
不会C语言的男孩5 小时前
Linux 系统编程 · 第 4 章:文件属性与元数据
linux·c语言·开发语言
C语言小火车5 小时前
什么时候用智能指针?什么时候用裸指针?
c语言·c++·学习·指针