构造函数


在〈定义类〉中谈过,如果没有定义任何构造函数,编译器会自动产生没有参数的默认构造函数,那么默认构造函数做了什么呢?如果就以下的类来说:

class Account { 
public: 
    string id;  
    string name; 
    double balance;
};

默认构造函数对每个值域进行默认初始化化,对基本类型来说,会初始化为各类型的空值,就类类型来说,使用其无参数构造函数来初始化化,例如string来说,会初始化为空字符串。

如果定义了类内初始化化(in-class initializer),那么默认构造函数会使用初始化化,例如:

class Account { 
public: 
    string id = "000-000-000";  
    string name = "Anonymous"; 
    double balance;
};

id以定义的初始化化初始化为string("000-000-000")name以定义的初始化化初始化为string("Anonymous"),而double默认初始化为 0.0。

默认构造函数只在没有自定义任何构造函数时,编译器才会产生,若自定义了构造函数,就算定义了参数构造函数,也不称为默认构造函数。例如:

class Account { 
    string id;  
    string name; 
    double balance;

public: 
    Account() {
        this->id = "000-000-000";
        this->name = "Anonymous";
        this->balance = 0.0;
    };
};

在 C++ 中,绝大多数的情况下,可能不用区分初始化化与指定,然而实际上是有分别的,就以上的类来说,若实例化Accountidnamebalance会进行默认初始化化,之后执行构造函数,将"000-000-000""Anonymous"、0.0 指定给对应的值域。

默认构造函数只在没有自定义任何构造函数时,编译器才会产生,构造函数可以重载,如果自定义了构造函数,也想提供无参构造函数,并希望其行为与默认构造函数相同,可以加上default。例如:

class Account { 
    string id;  
    string name; 
    double balance;

public: 
    Account() = default;
    Account(string id, string name, double balance);
};

在〈定义类〉中看过Account的构造函数是这么定义的:

Account::Account(string id, string name, double balance) {
    this->id = id;
    this->name = name;
    this->balance = balance;
}

如果构造函数中想要指定某个值域的值,可以定义初始化化清单(constructor initializer list),就上例来说,可以直接在定义类时编写:

class Account { 
    string id;  
    string name; 
    double balance;

public: 
    Account(string id, string name, double balance) : 
        id(id), name(name), balance(balance) {};
};

这么一来,id值域就会用参数id的值初始化化,name值域就会用参数name的值初始化化,balance值域就会用参数balance的值初始化化,括号中指定不一定要是参数,也可以是表达式,如果初始化化清单省略了某个值域,那就会使用默认初始化化;在这边,初始化化清单的顺序并不代表值域初始化化的顺序,值域初始化化的顺序是依类中值域定义的顺序而定。

绝大多数的情况下,不区分初始化化与指定不会有什么问题,然而底下不区分的话,就会有问题:

class Foo {
    const int wat;
    Foo(int wat) {
        this->wat = wat;
    }
};

若以Foo(1)实例化,wat会默认初始化为 0,之后执行构造函数流程,然而watconst修饰过,不可以在构造函数中被指定值了,因此会编译失败,然而以下可以通过编译:

class Foo {
    const int wat;
    Foo(int wat) : wat(wat) {}
}

如果构造过程,想要委由另一个版本的构造函数,可以在:后指定。例如:

class Account { 
    string id;  
    string name; 
    double balance;

public: 
    Account(string id, string name, double balance) : 
        id(id), name(name), balance(balance) {};
    Account(string id, string name) : Account(id, name, 0.0) {}
};

若以Account acct("123-456-789", "Justin Lin")构造实例,那么会先执行Account(string id, string name, double balance)的流程,接着才是Account(string id, string name)的流程。

在〈定义类〉中看过,可以使用以下的方式来构造Account实例:

Account acct = {"123-456-789", "Justin Lin", 1000};

在〈不定长度实参〉看过,{"123-456-789", "Justin Lin", 1000}实际上会创建initializer_list,可是〈定义类〉中并没有定义可接受initializer_list的构造函数啊?这其实是隐含地类型转换,默认会寻找符合初始化化清单的构造函数来进行实例构造。

实际上string也是如此,string name = "Justin Lin"时,"Justin Lin"const *char类型,隐含地会使用对应的构造函数来构造string实例。

如果不希望有这种行为,可以在对应的构造函数上加上explicit,例如〈定义类〉中的类若定义为:

class Account { 
    string id;  
    string name; 
    double balance;

public: 
    explicit Account(string id, string name, double balance);
    void deposit(double amount);
    void withdraw(double amount);
    string to_string();
};

编译的时候就会看到以下的错误消息:

error: converting to 'Account' from initializer list would use explicit constructor




展开阅读全文