• C++学习——对象数组、成员对象与封闭类


    以下内容源于C语言中文网的学习与整理,非原创,如有侵权请告知删除。

    一、对象数组

    对象数组,即数组的每个元素都是某个类的对象。

    1、对象数组中的每个元素都需要用构造函数初始化,具体哪些元素用哪些构造函数初始化,取决于定义数组时的写法。

    请看下面的例子:

    1. #include
    2. using namespace std;
    3. class CSample{
    4. public:
    5. CSample(){ //构造函数 1
    6. cout<<"Constructor 1 Called"<
    7. }
    8. CSample(int n){ //构造函数 2
    9. cout<<"Constructor 2 Called"<
    10. }
    11. };
    12. int main(){
    13. cout<<"stepl"<
    14. CSample arrayl[2]; //1,1
    15. //类似于CSample arrayl[0](),arrayl[1]() ? 有这种写法吗?
    16. cout<<"step2"<
    17. CSample array2[2] = {4, 5};//2,2
    18. //类似于CSample arrayl[0](4),arrayl[1](5) ? 有这种写法吗?
    19. cout<<"step3"<
    20. CSample array3[2] = {3};//2,1
    21. cout<<"step4"<
    22. CSample* array4 = new CSample[2];//1,1
    23. delete [] array4;
    24. return 0;
    25. }
    1. stepl
    2. Constructor 1 Called
    3. Constructor 1 Called
    4. step2
    5. Constructor 2 Called
    6. Constructor 2 Called
    7. step3
    8. Constructor 2 Called
    9. Constructor 1 Called
    10. step4
    11. Constructor 1 Called
    12. Constructor 1 Called

    step1的 array1 数组中的两个元素没有指明如何初始化,那么默认调用无参构造函数初始化,因此输出两行 Constructor 1 Called。

    step2的 array2 数组进行了初始化,初始化列表 {4, 5} 可以看作用来初始化两个数组元素的参数,所以 array2[0] 以 4 为参数,调用构造函数 2 进行初始化;array2[1] 以 5 为参数,调用构造函数 2 进行初始化。这导致输出两行 Constructor 2 Called。

    step3的 array3 指出了 array3[0] 的初始化方式,没有指出 array3[1] 的初始化方式,因此分别用构造函数 2 和构造函数 1 进行初始化。

    step4动态分配了一个 CSample 数组,其中有两个元素,没有指出和参数有关的信息,因此这两个元素都用无参构造函数初始化。

    2、在构造函数有多个参数时,数组的初始化列表中要显式地包含对构造函数的调用。

    例如下面的程序:

    1. class CTest{
    2. public:
    3. CTest(int n){ } //构造函数(1)
    4. CTest(int n, int m){ } //构造函数(2)
    5. CTest(){ } //构造函数(3)
    6. };
    7. int main(){
    8. //三个元素分别用构造函数(1)、(2)、(3) 初始化
    9. CTest arrayl[3] = { 1, CTest(1,2) };//像{1, (1,2)}这样行不行呢?
    10. //三个元素分别用构造函数(2)、(2)、(1)初始化
    11. CTest array2[3] = { CTest(2,3), CTest(1,2), 1};
    12. //两个元素指向的对象分别用构造函数(1)、(2)初始化
    13. CTest* pArray[3] = { new CTest(4), new CTest(1,2) }; //第13行
    14. return 0;
    15. }

    上面程序中比较容易令初学者困惑的是第 13 行。

    pArray 数组是一个指针数组,其元素不是 CTest 类的对象,而是 CTest 类的指针。第 13 行对 pArray[0] 和 pArray[1] 进行了初始化,把它们初始化为指向动态分配的 CTest 对象的指针,而这两个动态分配出来的 CTest 对象又分别是用构造函数(1)和构造函数(2)初始化的。pArray[2] 没有初始化,其值是随机的,不知道指向哪里。

    第 13 行生成了两个 CTest 对象,而不是三个,所以也只调用了两次 CTest 类的构造函数。

    二、成员对象与封闭类

    1、一些概念理解

    如果一个类的成员变量是另一个类的对象,则称该成员变量为“成员对象”,而包含成员对象的类叫封闭类(enclosed class)。

    2、封闭类的对象的初始化

    创建封闭类的对象时,它包含的成员对象也需要被创建,这就会引发成员对象构造函数的调用。成员对象的构造函数可能有很多个(即函数重载),那如何让编译器知道,成员对象到底是用哪个构造函数进行初始化?这就需要借助封闭类构造函数的初始化列表。

    一个简单的示例如下: 

    1. #include
    2. using namespace std;
    3. //1、轮胎类
    4. class Tyre{
    5. public:
    6. Tyre(int radius, int width);//轮胎类构造函数的声明
    7. void show() const;
    8. private:
    9. int m_radius; //半径
    10. int m_width; //宽度
    11. };
    12. //轮胎类构造函数的定义
    13. Tyre::Tyre(int radius, int width) : m_radius(radius), m_width(width){ }
    14. void Tyre::show() const {
    15. cout << "轮毂半径:" << this->m_radius << "吋" << endl;
    16. cout << "轮胎宽度:" << this->m_width << "mm" << endl;
    17. }
    18. //2、引擎类
    19. class Engine{
    20. public:
    21. Engine(float displacement = 2.0);//引擎类构造函数的声明
    22. void show() const;
    23. private:
    24. float m_displacement;
    25. };
    26. //引擎类构造函数的定义
    27. Engine::Engine(float displacement) : m_displacement(displacement) {}
    28. void Engine::show() const {
    29. cout << "排量:" << this->m_displacement << "L" << endl;
    30. }
    31. //3、汽车类
    32. class Car{
    33. public:
    34. Car(int price, int radius, int width);//汽车类构造函数的声明
    35. void show() const;
    36. private:
    37. int m_price; //价格
    38. Tyre m_tyre;
    39. Engine m_engine;
    40. };
    41. //汽车类构造函数的定义
    42. Car::Car(int price, int radius, int width): m_price(price), m_tyre(radius, width){ };
    43. //指明m_tyre对象的初始化方式
    44. void Car::show() const {
    45. cout << "价格:" << this->m_price << "¥" << endl;
    46. this->m_tyre.show();
    47. this->m_engine.show();
    48. }
    49. int main()
    50. {
    51. Car car(200000, 19, 245);//第51行
    52. car.show();
    53. return 0;
    54. }
    1. 价格:200000¥
    2. 轮毂直径:19吋
    3. 轮胎宽度:245mm
    4. 排量:2L

    封闭类构造函数的初始化列表,其写法如下:

    1. 封闭类的类名::封闭类的构造函数名(参数表): 成员变量名(参数表),…,成员对象名(参数表)
    2. {
    3.     //TODO:
    4. }

    对于封闭类Car,它有一个成员变量m_price,两个成员对象m_tyre 和 m_engine。而且编译器知道第 51 行的 car 这个对象是用 Car(int price, int radius, int width) 这个构造函数进行初始化的。

    (1)对于基本类型的成员变量,比如成员变量 m_price 是 int 类型,其“参数表”中只有一个值,就是初始值,在调用构造函数时,会把这个初始值直接赋给成员变量。比如Car类构造函数的初始化列表中,将初始值 price 赋值给成员变量 m_price。

    (2)对于成员对象,“参数表”中存放的是构造函数的参数,它可能只有一个值,也可能有多个值,它指明了该成员对象如何被初始化。比如Car类构造函数的初始化列表中,m_tyre(radius, width)告诉编译器,应该以 radius 和 width 作为参数传入构造函数 Tyre(int radius, int width) 中,来初始化m_tyre这个对象。

    (3)这里没有说明 m_engine 该如何处理,此时编译器就认为 m_engine 应该用 Engine 类的无参构造函数初始化,而 Engine 类确实有一个无参构造函数(设置了固定参数而视为无参)。

    总之,生成封闭类对象的语句,一定要让编译器能够弄明白其成员对象是如何初始化的,否则就会编译错误。在上面的程序中,如果 Car 类的构造函数没有初始化列表,那么第 51 行就会编译出错,因为编译器不知道该如何初始化 car.m_tyre 对象,因为 Tyre 类没有无参构造函数,而编译器又找不到用来初始化 car.m_tyre 对象的参数。

    3、成员对象的消亡

    封闭类对象生成时,先执行所有成员对象的构造函数,然后才执行封闭类自己的构造函数。成员对象构造函数的执行次序和成员对象在类定义中的次序一致,与它们在构造函数初始化列表中出现的次序无关。

    当封闭类对象消亡时,先执行封闭类的析构函数,然后再执行成员对象的析构函数,成员对象析构函数的执行次序和构造函数的执行次序相反,即先构造的后析构,这是 C++ 处理此类次序问题的一般规律。

    1. #include
    2. using namespace std;
    3. class Tyre {
    4. public:
    5. Tyre() { cout << "Tyre constructor" << endl; }
    6. ~Tyre() { cout << "Tyre destructor" << endl; }
    7. };
    8. class Engine {
    9. public:
    10. Engine() { cout << "Engine constructor" << endl; }
    11. ~Engine() { cout << "Engine destructor" << endl; }
    12. };
    13. class Car {
    14. private:
    15. Engine engine;
    16. Tyre tyre;
    17. public:
    18. Car() { cout << "Car constructor" << endl; }
    19. ~Car() { cout << "Car destructor" << endl; }
    20. };
    21. int main() {
    22. Car car;
    23. return 0;
    24. }
    1. Engine constructor
    2. Tyre constructor
    3. Car constructor
    4. Car destructor
    5. Tyre destructor
    6. Engine destructor

  • 相关阅读:
    从零手写实现 nginx-13-nginx.conf 是 HOCON 的格式吗?
    uni-app实现点击复制按钮 复制内容
    Windows 系统服务器部署jar包时,推荐使用winsw,将jar包注册成服务,并设置开机启动。
    java MessageDigest 实现加密算法
    【工具】typora的一些配置
    1.BERT
    python_data_analysis_and_mining_action-master-5
    【安全】网络安全态势感知
    git使用全解析 | git的原理 配置 基础使用 分支 合并
    Pytorch模型训练实用教程学习笔记:一、数据加载和transforms方法总结
  • 原文地址:https://blog.csdn.net/oqqHuTu12345678/article/details/133818180