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

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

一、对象数组

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

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

请看下面的例子:

#include
using namespace std;

class CSample{
public:
    CSample(){  //构造函数 1
        cout<<"Constructor 1 Called"<
stepl
Constructor 1 Called
Constructor 1 Called
step2
Constructor 2 Called
Constructor 2 Called
step3
Constructor 2 Called
Constructor 1 Called
step4
Constructor 1 Called
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、在构造函数有多个参数时,数组的初始化列表中要显式地包含对构造函数的调用。

例如下面的程序:

class CTest{
public:
    CTest(int n){ }  //构造函数(1)
    CTest(int n, int m){ }  //构造函数(2)
    CTest(){ }  //构造函数(3)
};
int main(){
    //三个元素分别用构造函数(1)、(2)、(3) 初始化
    CTest arrayl[3] = { 1, CTest(1,2) };//像{1, (1,2)}这样行不行呢?

    //三个元素分别用构造函数(2)、(2)、(1)初始化
    CTest array2[3] = { CTest(2,3), CTest(1,2), 1};

    //两个元素指向的对象分别用构造函数(1)、(2)初始化
    CTest* pArray[3] = { new CTest(4), new CTest(1,2) }; //第13行

    return 0;
}

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

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

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

二、成员对象与封闭类

1、一些概念理解

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

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

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

一个简单的示例如下: 

#include 
using namespace std;

//1、轮胎类
class Tyre{
public:
    Tyre(int radius, int width);//轮胎类构造函数的声明
    void show() const;
private:
    int m_radius;  //半径
    int m_width;  //宽度
};
//轮胎类构造函数的定义
Tyre::Tyre(int radius, int width) : m_radius(radius), m_width(width){ }
void Tyre::show() const {
    cout << "轮毂半径:" << this->m_radius << "吋" << endl;
    cout << "轮胎宽度:" << this->m_width << "mm" << endl;
}

//2、引擎类
class Engine{
public:
    Engine(float displacement = 2.0);//引擎类构造函数的声明
    void show() const;
private:
    float m_displacement;
};
//引擎类构造函数的定义
Engine::Engine(float displacement) : m_displacement(displacement) {}
void Engine::show() const {
    cout << "排量:" << this->m_displacement << "L" << endl;
}

//3、汽车类
class Car{
public:
    Car(int price, int radius, int width);//汽车类构造函数的声明
    void show() const;
private:
    int m_price;  //价格
    Tyre m_tyre;
    Engine m_engine;
};
//汽车类构造函数的定义                                                     
Car::Car(int price, int radius, int width): m_price(price), m_tyre(radius, width){ };
                                                          //指明m_tyre对象的初始化方式
void Car::show() const {
    cout << "价格:" << this->m_price << "¥" << endl;
    this->m_tyre.show();
    this->m_engine.show();
}

int main()
{
    Car car(200000, 19, 245);//第51行
    car.show();
    return 0;
}
价格:200000¥
轮毂直径:19吋
轮胎宽度:245mm
排量:2L

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

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

对于封闭类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++ 处理此类次序问题的一般规律。

#include
using namespace std;

class Tyre {
public:
    Tyre() { cout << "Tyre constructor" << endl; }
    ~Tyre() { cout << "Tyre destructor" << endl; }
};

class Engine {
public:
    Engine() { cout << "Engine constructor" << endl; }
    ~Engine() { cout << "Engine destructor" << endl; }
};

class Car {
private:
    Engine engine;
    Tyre tyre;
public:
    Car() { cout << "Car constructor" << endl; }
    ~Car() { cout << "Car destructor" << endl; }
};

int main() {
    Car car;
    return 0;
}
Engine constructor
Tyre constructor
Car constructor
Car destructor
Tyre destructor
Engine destructor

你可能感兴趣的:(#,C,与,C++,c++)