【c++面向对象编程】第12篇:继承(二):构造与析构顺序,继承中的构造函数

目录

一、派生类对象的内存布局

二、构造顺序:从根到叶

基本规则

三、向基类构造函数传参

错误示例

正确写法

带参数的完整例子

四、多级继承的构造链

五、构造函数执行顺序的细节

[1. 基类构造函数的调用位置](#1. 基类构造函数的调用位置)

[2. 如果没有显式调用基类构造](#2. 如果没有显式调用基类构造)

[3. 初始化列表的顺序不影响构造顺序](#3. 初始化列表的顺序不影响构造顺序)

六、完整例子:游戏角色继承体系

七、三个常见错误

[1. 基类没有默认构造,派生类忘记在初始化列表中调用](#1. 基类没有默认构造,派生类忘记在初始化列表中调用)

[2. 在派生类构造函数体中给基类成员赋值](#2. 在派生类构造函数体中给基类成员赋值)

[3. 构造函数和析构函数中调用虚函数](#3. 构造函数和析构函数中调用虚函数)

八、这一篇的收获


一、派生类对象的内存布局

先看一个简单的继承关系:

cpp

复制代码
class Base {
public:
    int baseData;
    Base() { cout << "Base构造" << endl; }
    ~Base() { cout << "Base析构" << endl; }
};

class Derived : public Base {
public:
    int derivedData;
    Derived() { cout << "Derived构造" << endl; }
    ~Derived() { cout << "Derived析构" << endl; }
};

当创建一个Derived对象时,内存里发生了什么?

text

复制代码
Derived对象内存布局:
┌─────────────────┐
│   Base子对象    │  ← 包含baseData
├─────────────────┤
│   derivedData   │
└─────────────────┘

关键理解 :派生类对象不是"独立的一块",它里面完整地包含了一个基类子对象。这意味着:

  • 构造派生类时,必须先构造好基类子对象

  • 析构派生类时,基类子对象最后才被析构


二、构造顺序:从根到叶

基本规则

派生类对象构造的顺序是:

  1. 先构造基类子对象(调用基类构造函数)

  2. 再构造成员变量(按声明顺序)

  3. 最后执行派生类构造函数体

cpp

复制代码
#include <iostream>
using namespace std;

class Base {
public:
    Base() { cout << "1. Base构造函数" << endl; }
    ~Base() { cout << "6. Base析构函数" << endl; }
};

class Member {
public:
    Member() { cout << "2. Member构造函数" << endl; }
    ~Member() { cout << "5. Member析构函数" << endl; }
};

class Derived : public Base {
private:
    Member m;
public:
    Derived() { cout << "3. Derived构造函数体" << endl; }
    ~Derived() { cout << "4. Derived析构函数体" << endl; }
};

int main() {
    Derived d;
    return 0;
}

输出:

text

复制代码
1. Base构造函数
2. Member构造函数
3. Derived构造函数体
4. Derived析构函数体
5. Member析构函数
6. Base析构函数

构造顺序 :基类 → 成员 → 派生类自身
析构顺序:正好相反:派生类自身 → 成员 → 基类


三、向基类构造函数传参

如果基类没有默认构造函数(无参构造),派生类必须显式在初始化列表中调用基类构造函数。

错误示例

cpp

复制代码
class Base {
public:
    int x;
    Base(int val) : x(val) {   // 只有带参构造,没有默认构造
        cout << "Base带参构造: " << x << endl;
    }
};

class Derived : public Base {
public:
    Derived() {   // ❌ 编译错误!Base没有默认构造函数
        // 编译器不知道如何构造Base子对象
    }
};

正确写法

cpp

复制代码
class Derived : public Base {
public:
    Derived() : Base(100) {   // ✅ 显式调用基类构造函数
        cout << "Derived构造" << endl;
    }
    
    Derived(int val) : Base(val) {   // 也可以把参数传进去
        cout << "Derived带参构造: " << val << endl;
    }
};

带参数的完整例子

cpp

复制代码
#include <iostream>
#include <string>
using namespace std;

class Person {
private:
    string name;
    int age;
public:
    Person(string n, int a) : name(n), age(a) {
        cout << "Person构造: " << name << ", " << age << "岁" << endl;
    }
    ~Person() {
        cout << "Person析构: " << name << endl;
    }
    void introduce() {
        cout << "我是" << name << ", " << age << "岁" << endl;
    }
};

class Student : public Person {
private:
    string studentId;
    string major;
public:
    // 派生类构造函数:先调用基类构造函数初始化基类部分
    Student(string name, int age, string id, string m) 
        : Person(name, age), studentId(id), major(m) {
        cout << "Student构造: " << id << ", " << major << "专业" << endl;
    }
    
    ~Student() {
        cout << "Student析构: " << studentId << endl;
    }
    
    void study() {
        introduce();  // 可以调用基类的方法
        cout << "学号" << studentId << "正在学习" << major << endl;
    }
};

int main() {
    cout << "=== 创建Student对象 ===" << endl;
    Student s("张三", 20, "2021001", "计算机科学");
    cout << "\n=== 调用学生方法 ===" << endl;
    s.study();
    cout << "\n=== 程序结束,对象析构 ===" << endl;
    return 0;
}

输出:

text

复制代码
=== 创建Student对象 ===
Person构造: 张三, 20岁
Student构造: 2021001, 计算机科学专业

=== 调用学生方法 ===
我是张三, 20岁
学号2021001正在学习计算机科学

=== 程序结束,对象析构 ===
Student析构: 2021001
Person析构: 张三

四、多级继承的构造链

继承可以有多层:GrandChildChildParent

cpp

复制代码
#include <iostream>
using namespace std;

class GrandParent {
public:
    GrandParent() { cout << "GrandParent构造" << endl; }
    ~GrandParent() { cout << "GrandParent析构" << endl; }
};

class Parent : public GrandParent {
public:
    Parent() { cout << "Parent构造" << endl; }
    ~Parent() { cout << "Parent析构" << endl; }
};

class Child : public Parent {
public:
    Child() { cout << "Child构造" << endl; }
    ~Child() { cout << "Child析构" << endl; }
};

int main() {
    Child c;
    return 0;
}

输出:

text

复制代码
GrandParent构造
Parent构造
Child构造
Child析构
Parent析构
GrandParent析构

构造链规则:从最顶层的基类开始,一层一层往下构造,就像一个多层的蛋糕。析构时从最顶层(派生类本身)开始,一层一层往下析构。


五、构造函数执行顺序的细节

1. 基类构造函数的调用位置

基类构造函数只能在派生类构造函数的初始化列表中调用,不能在函数体中调用。

cpp

复制代码
Derived::Derived() {
    Base();   // ❌ 这不是调用基类构造,而是创建一个临时Base对象
}

2. 如果没有显式调用基类构造

如果基类有默认构造函数(无参或全缺省参数),编译器会自动调用它。

cpp

复制代码
class Base {
public:
    Base() { cout << "Base默认构造" << endl; }  // 有默认构造
};

class Derived : public Base {
public:
    Derived() {   // 不用写Base(),编译器自动调用
        cout << "Derived构造" << endl;
    }
};

3. 初始化列表的顺序不影响构造顺序

这和组合类一样:构造顺序只由继承/声明顺序决定,不是由初始化列表的书写顺序决定。

cpp

复制代码
class Derived : public Base1, public Base2 {
    Member m1, m2;
public:
    Derived() : m2(), m1(), Base2(), Base1() {
        // 实际构造顺序:Base1 → Base2 → m1 → m2
        // 初始化列表的书写顺序被忽略
    }
};

六、完整例子:游戏角色继承体系

用继承来实现一个简单的游戏角色系统:

cpp

复制代码
#include <iostream>
#include <string>
using namespace std;

// 基类:角色
class Character {
protected:
    string name;
    int health;
    int level;
    
public:
    Character(string n, int hp, int lv) 
        : name(n), health(hp), level(lv) {
        cout << "角色【" << name << "】创建,生命值" << health 
             << ",等级" << level << endl;
    }
    
    ~Character() {
        cout << "角色【" << name << "】销毁" << endl;
    }
    
    void takeDamage(int damage) {
        health -= damage;
        if (health < 0) health = 0;
        cout << name << "受到" << damage << "点伤害,剩余生命" << health << endl;
    }
    
    void heal(int amount) {
        health += amount;
        cout << name << "恢复" << amount << "生命,当前生命" << health << endl;
    }
    
    virtual void attack() const {  // virtual:下一讲详解
        cout << name << "发动普通攻击!" << endl;
    }
};

// 派生类:战士
class Warrior : public Character {
private:
    int rage;      // 怒气值
public:
    Warrior(string n, int hp, int lv) 
        : Character(n, hp, lv), rage(0) {
        cout << "  战士【" << n << "】获得铠甲和战斧" << endl;
    }
    
    ~Warrior() {
        cout << "  战士【" << name << "】的武器碎裂" << endl;
    }
    
    void charge() {
        rage += 30;
        cout << name << "冲锋!怒气+" << 30 << ",当前怒气" << rage << endl;
    }
    
    void attack() const override {
        cout << name << "使用旋风斩!" << endl;
    }
};

// 派生类:法师
class Mage : public Character {
private:
    int mana;      // 法力值
public:
    Mage(string n, int hp, int lv) 
        : Character(n, hp, lv), mana(100) {
        cout << "  法师【" << n << "】获得法杖和魔法书" << endl;
    }
    
    ~Mage() {
        cout << "  法师【" << name << "】的法杖断裂" << endl;
    }
    
    void castFireball() {
        if (mana >= 30) {
            mana -= 30;
            cout << name << "施放火球术!法力剩余" << mana << endl;
        } else {
            cout << name << "法力不足,无法施法" << endl;
        }
    }
    
    void attack() const override {
        cout << name << "施放魔法飞弹!" << endl;
    }
};

int main() {
    cout << "=== 创建角色 ===" << endl;
    Warrior w("亚瑟", 300, 10);
    Mage m("甘道夫", 180, 10);
    
    cout << "\n=== 战斗中 ===" << endl;
    w.charge();
    w.attack();
    m.castFireball();
    m.attack();
    w.takeDamage(50);
    m.heal(30);
    
    cout << "\n=== 战斗结束,角色即将销毁 ===" << endl;
    return 0;
}

输出:

text

复制代码
=== 创建角色 ===
角色【亚瑟】创建,生命值300,等级10
  战士【亚瑟】获得铠甲和战斧
角色【甘道夫】创建,生命值180,等级10
  法师【甘道夫】获得法杖和魔法书

=== 战斗中 ===
亚瑟冲锋!怒气+30,当前怒气30
亚瑟使用旋风斩!
甘道夫施放火球术!法力剩余70
甘道夫施放魔法飞弹!
亚瑟受到50点伤害,剩余生命250
甘道夫恢复30生命,当前生命210

=== 战斗结束,角色即将销毁 ===
  法师【甘道夫】的法杖断裂
角色【甘道夫】销毁
  战士【亚瑟】的武器碎裂
角色【亚瑟】销毁

注意析构顺序:先销毁后创建的对象(Mage先析构),然后再销毁先创建的Warrior


七、三个常见错误

1. 基类没有默认构造,派生类忘记在初始化列表中调用

cpp

复制代码
class Base {
public:
    Base(int x) {}   // 只有带参构造
};

class Derived : public Base {
public:
    Derived() {}   // ❌ 编译错误
};

2. 在派生类构造函数体中给基类成员赋值

cpp

复制代码
Derived::Derived() {
    baseData = 10;   // 虽然能编译,但效率低
}

更好的做法是在初始化列表中调用基类构造函数:

cpp

复制代码
Derived::Derived() : Base(10) {}  // ✅ 直接构造时初始化

3. 构造函数和析构函数中调用虚函数

在构造函数和析构函数中调用虚函数,不会产生多态行为------调用的是当前类的版本,而不是派生类的版本。这是一个容易踩的坑。

cpp

复制代码
class Base {
public:
    Base() { func(); }   // 调用Base::func,不是Derived::func
    virtual void func() { cout << "Base" << endl; }
};

class Derived : public Base {
public:
    virtual void func() override { cout << "Derived" << endl; }
};

八、这一篇的收获

你现在应该理解:

  • 派生类对象包含一个完整的基类子对象

  • 构造顺序:基类 → 成员变量 → 派生类构造函数体

  • 析构顺序:派生类析构函数体 → 成员变量 → 基类

  • 如果基类没有默认构造,派生类必须在初始化列表中显式调用基类构造函数

  • 多级继承的构造链从最顶层开始,一层层往下

💡 小作业:设计一个Vehicle基类,有brandspeed属性,构造函数接收这两个参数。再设计CarMotorcycle继承它。Car额外有doorCountMotorcycle额外有hasSidecar。测试创建这些对象时构造函数的调用顺序。


下一篇预告:第13篇《继承(三):同名隐藏与作用域覆盖》------如果派生类定义了和基类同名的成员,基类的成员会被"隐藏"而不是"重载"。什么是同名隐藏?怎么访问被隐藏的基类成员?下篇讲清楚作用域的规则。

相关推荐
知识分享小能手1 小时前
R语言入门学习教程,从入门到精通,R语言获取数据 (7)
开发语言·学习·r语言
原来是猿2 小时前
网络计算器:理解序列化与反序列化(下)
linux·开发语言·网络·网络协议·json·php
_waylau2 小时前
“Java+AI全栈工程师”问答02:Spring Boot 自动配置原理
java·开发语言·spring boot·后端·spring
JAVA面经实录9172 小时前
Java架构师最终完整版学习路线图
java·开发语言·学习
雪度娃娃2 小时前
结构型设计模式——享元模式
c++·设计模式·享元模式
勤自省2 小时前
ROS2从入门到“重启解决”:21讲8~12章踩坑血泪史与核心总结
linux·开发语言·ubuntu·ssh·ros
TIEM_692 小时前
C++string|遍历、模拟实现、赋值拷贝现代写法
开发语言·c++
计算机安禾2 小时前
【c++面向对象编程】第14篇:多态(一):虚函数——实现“一个接口,多种方法”
开发语言·c++
tellmewhoisi2 小时前
单独抽取用户服务(请求不通):feign添加拦截器(添加token)
java·开发语言