C++中的构造函数

每个类都分别定义了它的对象被初始化的方式,类通过一个或多个特殊的成员函数来控制其对象的初始化过程,这些函数就叫做构造函数(constructor)。构造函数的任务是初始化类对象的数据成员,无论何时只要类的对象被创建,就会执行构造函数。

构造函数的名字和类名相同,但其没有返回类型。类可以包括多个构造函数,和其他重载函数差不多,不同的构造函数之间必须在参数数量或参数类型上有所区别。

构造函数不能被声明为const的,在常量对象的创建过程中,可以为其中的参数写值,知道对象创建完成才会取得const属性。

默认构造函数

当我们没有为类提供构造函数的时候,编译器会为我们隐式地定义一个构造函数,编译器创建的构造函数又被称为合成的默认构造函数,它将按照以下规则初始化类地数据成员

  • 如果存在类内的初始值,用它来初始化成员
  • 如果不存在则默认初始化该成员

合成的默认构造函数只适合非常简单的类,对于普通的类来说,必须定义它自己的构造函数,原因如下:

  1. 编译器只有在发现类不包含任何构造函数的情况下才会创建默认的构造函数
  2. 对于某些类,合成的默认构造函数可能会执行错误的操作,比如对数组或指针进行默认初始化,其值将是未定义的
  3. 编译器有时候不能为某些类创建默认构造函数,如果该类里面含有其他类的成员而该类成员没有默认构造函数,则编译器不能执行默认初始化

当且仅当我们既需要普通的构造函数也需要默认构造函数的时候,可以使用=default来定义

class Sales_data{
    Sales_data()=default;  //默认构造函数
    Sales_data(const std::string &s):bookNo(s){}
}

构造函数初始值列表

对于Sales_data类的其他构造函数

Sales_data(const std::string &s):bookNo(s){}  //s作为bookNo的初值
Sales_data(const std::string &s,unsigned n,double p):bookNo(s),units_sold(n),revenue(p*n){}
//s作为bookNo的初值,n作为units_sold的初值,p*n作为revenue的初值

在参数列表与大括号之间新出现的部分称为构造函数初始值列表,它负责为新创建对象的一个或几个数据成员赋初值,另外需要注意构造函数不应该轻易覆盖掉类内的初始值,除非新赋的值与原值不同。

需要注意,成员的初始化顺序与它们在类定义中出现的顺序一致:第一个成员先被初始化,然后第二个等等。构造函数初始值列表中初始值的前后位置关系不会影响实际的初始化顺序,因此最好令构造函数初始值的顺序与成员声明的顺序一致。

class X{
    int i;
    int j;
public:
    X(int val):j(val),i(j){}
    //会出现错误,因为i在j之前被初始化
};

委托构造函数

C++11新标准扩展了构造函数初始值的功能,使得我们能够定义委托构造函数,一个委托构造函数使用它所属类的其他构造函数执行它自己的初始化过程,或者说把它自己的一些或全部职责委托给了其他的构造函数。

class Sales_data{
public:
    Sales_data(std::string s,unsigned cnt,double price):bookNo(s),units_sold(cnt),revenue(cnt*price){}
    //下面的构造函数都委托给其他的构造函数
    Sales_data():Sales_data(" ",0,0){}
    Sales_data(std::string s):Sales_data(s,0,0){}
    Sales_data(std::istream &is):Sales_data(){read(is,*this);}
}

隐式的类类型转换

在某个类中,接受string的构造函数和接受istream的构造函数分别定义了这两种类型向该类的转换规则

string null_book="9999";
item.combine(null_book);
//编译器用给定的string自动创建了一个Sales_data的对象

注意,编译器只会执行一步类型转换,例如从字面量再到string对象再到Sales_data对象的转换是不被允许的。

在我们不需要进行类型转换的时候可以使用explicit关键字进行抑制

class Sales_data{
public:
    Sales_data()=default;
    explicit Sales_data(const std::string &s):bookNo(s){} //不能从string对象转换为Sales_data对象
    explicit Sales_data(std::istream&);
}

在不使用隐式转换的时候,另外可以使用显式转换

item.combine(static_cast<Sales_data>(cin));