Skip to content

这部分来了解一下什么是成员对象和封闭类。

一、成员对象与封闭类

1. 基本概念

一个类的成员变量如果是另一个类的对象,就称之为“成员对象”,包含成员对象的类叫封闭类(enclosed class)。简单来说就是一个类的成员是一个对象,这个成员叫成员对象,这些类就叫封闭类,例如:

cpp
// 轮胎类
class tyre_t {
  private:
    int m_radius; // 半径
    int m_width;  // 宽度
  public:
    tyre_t(int radius, int width);
    void show() const;
};

// 引擎类
class engine_t {
  private:
    float m_displacement;

  public:
    engine_t(float displacement = 2.0);
    void show() const;
};

// 汽车类
class care_t {
  private:
    int      m_price;  // 价格
    tyre_t   m_tyre;   // 轮胎
    engine_t m_engine; // 引擎
  public:
    care_t(int price, int radius, int width);
    void show() const;
};

上面定义了两个类:轮胎类tyre_t和引擎类engine_t。我们有定义了一个汽车类care_t,汽车含有轮胎和引擎成员,而这两个成员分别是轮胎类tyre_t和引擎类engine_t的对象,这两个成员就叫成员对象。而这个汽车类就称之为封闭类。

二、怎么初始化成员对象

创建封闭类的对象时,它包含的成员对象也需要被创建,这就会引发成员对象构造函数的调用。如何让编译器知道,成员对象到底是用哪个构造函数初始化的呢?这就需要借助封闭类构造函数的初始化列表。

前面我们知道,构造函数初始化列表的写法如下:

cpp
类名::构造函数名(参数表): 成员变量1(参数表), 成员变量2(参数表), ...
{
  //TODO:
}

对于基本类型的成员变量,“参数表”中只有一个值,就是初始值,在调用构造函数时,会把这个初始值直接赋给成员变量。但是对于成员对象,“参数表”中存放的是构造函数的参数,它可能是一个值,也可能是多个值,它指明了该成员对象如何被初始化。例如:

cpp
#include <iostream>
using namespace std;
// 轮胎类
class tyre_t {
  private:
    int m_radius; // 半径
    int m_width;  // 宽度
  public:
    tyre_t(int radius, int width);
    void show() const;
};

tyre_t::tyre_t(int radius, int width) : m_radius(radius), m_width(width)
{
}
void tyre_t::show() const
{
    cout << "轮毂半径:" << this->m_radius << "inch" << endl;
    cout << "轮胎宽度:" << this->m_width << "mm" << endl;
}

// 引擎类
class engine_t {
  private:
    float m_displacement;
  public:
    engine_t(float displacement = 2.0);
    void show() const;
};
engine_t::engine_t(float displacement) : m_displacement(displacement)
{
}
void engine_t::show() const
{
    cout << "排量:" << this->m_displacement << "L" << endl;
}
// 汽车类
class care_t {
  private:
    int      m_price;  // 价格
    tyre_t   m_tyre;   // 轮胎
    engine_t m_engine; // 引擎
  public:
    care_t(int price, int radius, int width);
    void show() const;
};
/*指明m_tyre对象的初始化方式*/
care_t::care_t(int price, int radius, int width) : m_price(price), m_tyre(radius, width)
{
}
void care_t::show() const
{
    cout << "价格:" << this->m_price << "¥" << endl;
    this->m_tyre.show();
    this->m_engine.show();
}
int main()
{
    care_t car(200000, 19, 245);
    car.show();
    return 0;
}

care_t 是一个封闭类,它有两个成员对象:m_tyre 和 m_engine。在编译第59行时,编译器需要知道 car 对象中的 m_tyre 和 m_engine 成员对象该如何初始化。

编译器已经知道这里的 car 对象是用第 48 行的 care_t(int price, int radius, int width) 构造函数初始化的,那么 m_tyre 和 m_engine 该如何初始化,就要看第 48 行后面的初始化列表了。该初始化列表表明:

  • m_tyre 应以 radius 和 width 作为参数调用 tyre_t(int radius, int width) 构造函数初始化。
  • 但是这里并没有说明 m_engine 该如何处理。在这种情况下,编译器就认为 m_engine 应该用 Engine 类的无参构造函数初始化。而 Engine 类确实有一个无参构造函数(因为设置了默认参数,可以算作无参构造函数),因此,整个 car 对象的初始化问题就都解决了。

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

三、成员对象的消亡

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

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

cpp
#include <iostream>
using namespace std;
// 轮胎类
class tyre_t {
  private:
    int m_radius; // 半径
    int m_width;  // 宽度
  public:
    tyre_t(int radius, int width);
    ~tyre_t();
    void show() const;
};

tyre_t::tyre_t(int radius, int width) : m_radius(radius), m_width(width)
{
    cout << "tyre_t() constructor" << endl;
}
tyre_t::~tyre_t()
{
    cout << "~tyre_t() destructor" << endl;
}
void tyre_t::show() const
{
    cout << "轮毂半径:" << this->m_radius << "inch" << endl;
    cout << "轮胎宽度:" << this->m_width << "mm" << endl;
}

// 引擎类
class engine_t {
  private:
    float m_displacement;

  public:
    engine_t(float displacement = 2.0);
    ~engine_t();
    void show() const;
};
engine_t::engine_t(float displacement) : m_displacement(displacement)
{
  cout << "engine_t() constructor" << endl;
}
engine_t::~engine_t()
{
  cout << "~engine_t() destructor" << endl;
}
void engine_t::show() const
{
    cout << "排量:" << this->m_displacement << "L" << endl;
}
// 汽车类
class care_t {
  private:
    int      m_price;  // 价格
    tyre_t   m_tyre;   // 轮胎
    engine_t m_engine; // 引擎
  public:
    care_t(int price, int radius, int width);
    ~care_t();
    void show() const;
};
/*指明m_tyre对象的初始化方式*/
care_t::care_t(int price, int radius, int width) : m_price(price), m_tyre(radius, width)
{
  cout << "care_t() constructor" << endl;
}
care_t::~care_t()
{
  cout << "~care_t() destructor" << endl;
}
void care_t::show() const
{
    cout << "价格:" << this->m_price << "¥" << endl;
    this->m_tyre.show();
    this->m_engine.show();
}

/*
(1)封闭类对象生成时,先执行所有成员对象的构造函数,然后才执行封闭类自己的构造函数。成员对象构造函数的执行次序和成员对象在类定义中的次序一致,
   与它们在构造函数初始化列表中出现的次序无关。
(2)当封闭类对象消亡时,先执行封闭类的析构函数,然后再执行成员对象的析构函数,成员对象析构函数的执行次序和构造函数的执行次序相反,即先构造的后析构,
   这是 C++ 处理此类次序问题的一般规律。
*/
int main()
{
    care_t car(200000, 19, 245);
    car.show();
    return 0;
}

编译运行可以看到如下内容:

bash
tyre_t() constructor
engine_t() constructor
care_t() constructor
价格:200000¥
轮毂半径:19inch
轮胎宽度:245mm
排量:2L
~care_t() destructor
~engine_t() destructor
~tyre_t() destructor

莫道桑榆晚 为霞尚满天.