当前位置: 首页 > article >正文

C++中类对象作为类成员(对象成员/成员对象)的一些注意事项

目录

一、对象成员的定义与特点

1.1 对象成员的定义 

1.2 对象成员的特点

二、单个对象成员的构造/析构顺序

三、多个对象成员的构造/析构顺序

四、对象成员的初始化

情况 1:成员对象有默认构造函数

情况 2:成员对象没有默认构造函数

五、总结


一、对象成员的定义与特点

1.1 对象成员的定义 

在 C++ 中,类的成员可以是另一个类的对象,这样的成员称为 对象成员(或者称为 成员对象)。这是一种 组合(Composition) 关系。

例如:

#include<iostream>

class A{};
class B{
    A a;
};

// B类中有对象 a 作为成员,a 为对象成员
// 那么当创建 b 对象时,a 与 b 的构造函数和析构函数调用顺序是什么样的呢?

1.2 对象成员的特点

  1. 成员对象在包含类构造时,按声明顺序自动构造,在析构时按声明顺序的逆序自动析构。
  2. 若成员对象无默认构造函数,必须通过初始化列表初始化。
  3. 成员对象的构造函数先于本类构造函数调用,析构顺序相反。
  4. 构造顺序(析构顺序)由声明顺序(声明顺序的逆序)决定,而非初始化列表顺序。

二、单个对象成员的构造/析构顺序

#include <iostream>

class Engine {
public:
    Engine() { std::cout << "Engine 构造函数调用" << std::endl; }
    ~Engine() { std::cout << "Engine 析构函数调用" << std::endl; }
};

class Car {
private:
    Engine engine; // Car 包含一个 Engine 对象

public:
    Car() { std::cout << "Car 构造函数调用" << std::endl; }
    ~Car() { std::cout << "Car 析构函数调用" << std::endl; }
};

int main() {
    Car myCar;
    return 0;
}

输出结果:

Engine 构造函数调用
Car 构造函数调用
Car 析构函数调用
Engine 析构函数调用

解释

  1. Engine 对象是 Car 的成员,所以在 Car 的对象 myCar 构造时,Engine 会先被构造
  2. 析构顺序与构造顺序相反,所以 Car 的析构函数先执行,然后才是 Engine 的析构函数。

总结:

✅ 对象成员的构造函数先于本类的构造函数执行。
✅ 对象成员的析构顺序与构造顺序相反。

🚀 掌握这个规则对于 C++ 组合关系、资源管理(RAII)等编程至关重要!

汽车(Car)和发动机(Engine)的构造与析构顺序

构造顺序(组装一辆汽车 🚗)

想象你在汽车工厂里造一辆汽车,主要涉及两个关键部件:

  1. 发动机(Engine)
  2. 汽车车身(Car)

🚀 造车的流程

  1. 先安装发动机
    你不能在没有发动机的情况下造一辆完整的汽车,所以发动机必须先安装
  2. 再组装车身
    车身是整个汽车的框架,它依赖于发动机,所以要等发动机装好后才能组装车身。
  3. 汽车完整并准备出厂

🔹 顺序:发动机 → 车身 → 汽车组装完成(对象成员 Engine 先构造,Car 后构造)


析构顺序(报废一辆汽车 🚗)

当汽车使用多年,最终到了报废的时候,拆解过程是:

  1. 先拆解车身
    你不会直接把发动机拆走,而是先把车身拆掉,以便暴露发动机。
  2. 再移除发动机
    车身拆掉后,才能安全地取出发动机。
  3. 汽车完全报废

🔹 顺序:车身 → 发动机 → 完全报废Car 先析构,Engine 后析构)


总结

  • 构造顺序:先装发动机,再装车身,最终汽车完成成员对象先构造,包含类后构造)。
  • 析构顺序:先拆车身,再拆发动机,最终汽车报废包含类先析构,成员对象后析构)。

这个顺序是固定的,因为汽车(Car)依赖于发动机(Engine),必须先有发动机,汽车才能存在! 🚗


三、多个对象成员的构造/析构顺序

 多个对象成员的构造顺序是按照它们在类中声明的顺序与初始化列表的顺序无关

#include <iostream>

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

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

class Car {
private:
    Engine engine;        // 先声明 Engine
    Transmission gearbox; // 后声明 Transmission

public:
    Car() : gearbox(), engine() {  // 初始化列表顺序:先 gearbox 再 engine(但不会影响构造顺序)
        std::cout << "Car 构造" << std::endl;
    }
    
    ~Car() {
        std::cout << "Car 析构" << std::endl;
    }
};

int main() {
    Car myCar;
    return 0;
}

🚗 输出结果:

Engine 构造
Transmission 构造
Car 构造
Car 析构
Transmission 析构
Engine 析构

🚀 解析

  1. 构造阶段

    • 尽管初始化列表顺序是 gearbox(), engine(),但 Engine 仍然会先构造,因为它在 Car 类中先声明
    • 然后 Transmission 才会构造。
    • 最后 Car 构造完成。
  2. 析构阶段

    • Car 先执行析构函数。
    • Transmission 先析构(因为它后构造)。
    • Engine 最后析构(因为它先构造)。

📌 关键点

成员对象的构造顺序与它们在类中声明的顺序一致而不是初始化列表的顺序
成员对象的析构顺序与构造顺序相反

这就像现实中造汽车:不管装配工人想先装变速箱还是发动机,组装流程是固定的——发动机必须先装,然后才是变速箱! 🚗

 

四、对象成员的初始化

情况 1:成员对象有默认构造函数

如果成员对象有默认构造函数,可以不使用初始化列表:

#include<iostream>
class Engine {
    public:
        Engine() { std::cout << "Engine 默认构造" << std::endl; }
    };
    
    class Car {
    private:
        Engine engine; // Engine 具有默认构造函数
    public:
        Car() { std::cout << "Car 构造" << std::endl; }
    };


int main(){
    Car mycar;
    return 0;
}

// 输出:
// Engine 默认构造
// Car 构造
    

会自动调用 Engine 的默认构造函数


情况 2:成员对象没有默认构造函数

如果 Engine 没有默认构造函数,换言之,假设Engine有一个有参构造函数,那么必须用 初始化列表为成员对象初始化

#include<iostream>
class Engine {
    private:
        int power;
    public:
        Engine(int p) : power(p) { std::cout << "Engine 初始化,功率:" << power << std::endl; }
    };
    
    class Car {
    private:
        Engine engine;  // 这里的 engine 需要初始化
    public:
        Car(int power) : engine(power) {  // 通过初始化列表初始化 engine
            std::cout << "Car 初始化" << std::endl;
        }
    };

int main(){
    Car mycar(150);
    return 0;
}
 
// 输出:
// Engine 初始化,功率:150
// Car 初始化

💡 初始化顺序与成员定义顺序一致,不管初始化列表的顺序如何。


五、总结

成员对象在包含类构造时,按声明顺序自动构造,在析构时按声明顺序的逆序自动析构。
若成员对象无默认构造函数,必须通过初始化列表初始化。
成员对象的构造函数先于本类构造函数调用,析构顺序相反。
构造顺序(析构顺序)由声明顺序(声明顺序的逆序)决定,而非初始化列表顺序。

这种方式常用于组合模式(Composition),在 C++ 面向对象设计中非常重要! 🚀


http://www.kler.cn/a/590649.html

相关文章:

  • 微信开发者工具内建终端使用不了npm,但是cmd可以
  • 如何设置爬虫的延时避免被封禁
  • LeetCode Hot 100:1.两数之和、49.字母异位词分组、128.最长连续序列、283.移动零、11.盛水最多的容器
  • DNA语言模型GROVER学习人类基因组中的序列上下文
  • C/C++都有哪些开源的Web框架?
  • go语言的包使用,以及错误处理
  • 欧拉降幂-乘积幂次
  • 深入理解 IP、子网掩码、端口号和协议
  • Spring Cloud Config - 动态配置管理与高可用治理
  • 大型语言模型(LLM):解码人工智能的“语言基因“
  • Qt中打开windows的cmd窗口并显示
  • TypeScript接口 interface 高级用法完全解析
  • 深度学习-服务器训练SparseDrive过程记录
  • 文件包含与下载漏洞
  • JavaScript 元编程革命:Proxy 如何重塑语言本质
  • LLM对齐方法作用:主要解决大型语言模型(LLMs)输出与人类价值观、需求和安全规范不一致的问题
  • 【华为OD机考真题】- 用户调度问题(Java)
  • 使用zenodo-upload进行zenodo大文件上传
  • 【力扣】2666. 只允许一次函数调用——认识高阶函数
  • CellOracle|基因扰动研究基因功能|基因调控网络+虚拟干预