构造函数与默认构造函数 赋值与初始化 explicit关键字

构造函数 默认构造函数

构造函数是为了保证对象的每个数据成员都被正确初始化,通常情况下构造函数应声明为公有函数,一般被隐式地调用。构造函数被声明为私有有特殊的用途,比如单例模式

栈上的对象生存期到了会自动调用析构函数
new operator做了两件事,一个是创建了对象内存,一个是调用构造函数;
堆上的内存需要delete释放,做了两件事,一是调用析构函数,二是释放内存。堆上new的对象,不delete的话,不会自动调用析构函数

#ifndef _TEST_H_
#define _TEST_H_

class Test
{
public:
    // 如果类不提供任何一个构造函数,系统将为我们提供一个不带参数的
    // 默认的构造函数
    Test();
    Test(int num);
    void Display();

    Test &operator=(const Test &other);

    ~Test();
private:
    int num_;
};
#endif // _TEST_H_
#include "Test.h"
#include <iostream>
using namespace std;

// 不带参数的构造函数称为默认构造函数
Test::Test()
{
    num_ = 0;
    cout << "Initializing Default" << endl;
}

Test::Test(int num)
{
    num_ = num;
    cout << "Initializing " << num_ << endl;
}

Test::~Test()
{
    cout << "Destroy " << num_ << endl;
}

void Test::Display()
{
    cout << "num=" << num_ << endl;
}

Test &Test::operator=(const Test &other)
{
    cout << "Test::operator=" << endl;
    if (this == &other)
        return *this;

    num_ = other.num_;
    return *this;
}

全局对象的构造先于main函数执行,在return 0时全局变量的生存期也到了,故也会自动调用析构函数

#include "Test.h"
#include <iostream>
using namespace std;

Test t(10);

int main(void)
{
    cout << "Entering main ..." << endl;
    cout << "Exiting main ..." << endl;
    return 0;
}

运行结果

Initializing 10
Entering main ...
Exiting main ...
Destroy 10

默认构造函数

不带参数的构造函数称为默认构造函数
如果程序中未声明,则系统自动产生出一个默认构造函数,是空函数
如果程序实现任何一个构造函数(包括拷贝构造函数),那么编译器将不再提供默认构造函数

int main(void)
{
    Test t;
    t.Display();

    Test t2(10);
    t2.Display();

    Test *t3 = new Test(20);    // new operator
    t3->Display();

    delete t3;

    return 0;
}

运行结果

Initializing Default
num=0
Initializing 10
num=10
Initializing 20
num=20
Destroy 20
Destroy 10
Destroy 0

转换构造函数

将其它类型转换为类类型,类的构造函数只有一个参数是非常危险的,因为编译器可以使用这种构造函数把参数的类型隐式转换为类类型

单个参数的构造函数不一定是转换构造函数

#include "Test.h"

int main(void)
{
    Test t(10);     //
    带一个参数的构造函数,充当的是普通构造函数的功能

    t = 20;         // 将20这个整数赋值给t对象
    // 1、调用转换构造函数将20这个整数转换成类类型 (生成一个临时对象)
    // 2、将临时对象赋值给t对象(调用的是=运算符)

    Test t2;

    return 0;
}

运行结果

Initializing 10
Initializing 20
Test::operator=
Destroy 20
Initializing Default
Destroy 0
Destroy 20

调用转换构造函数将20这个整数转换成类类型,生成一个临时对象,然后调用赋值运算符operator=,然后释放临时对象

Test t[2] = {10, 20}; 中10,20是当作参数传递给每个对象的构造函数的。如果没有对应的构造函数,比如只有2个参数的构造函数,那么编译是失败的

赋值与初始化的区别

在初始化语句中的等号不是运算符

#include "Test.h"

int main(void)
{
    Test t = 10;        // 等价于Test t(10); 这里的=不是运算符,表示初始化。

    t = 20;             // 赋值操作

    Test t2;
    t = t2;             // 赋值操作 t.operator=(t2);

    return 0;
}

运行结果

Initializing 10
Initializing 20
Test::operator=
Destroy 20
Initializing Default
Test::operator=
Destroy 0
Destroy 0

explicit关键字

只提供给类的构造函数使用的关键字。编译器不会把声明为explicit的构造函数用于隐式转换,它只能在程序代码中显示创建对象

假设在Test类的构造函数Test(int num); 前面加上explicit关键字,那么Test t = 10; 或者 t = 20;这种语句都是编译不通过的,因为不允许隐式转换。