C++ primer 查漏补缺十一:构造函数

每个类都分别定义了它的对象被初始化的方式,类通过一个或几个特殊的成员函数来控制其对象的初始化过程,这些函数叫做构造函数(constructor)。

构造函数的任务是初始化类对象的数据成员,无论何时只要类的对象被创建,就会执行构造函数。构造函数的名字和类名相同。和其他函数不一样的是,构造函数没有返回类型:除此之外类似于其他的函数,构造函数也有一个(可能为空的)参数列表和一个(可能为空的)函数体。类可以包含多个构造函数,和其他重载函数差不多,不同的构造函数之间必须在参数数量或参数类型上有所区别。

不同于其他成员函数,构造函数不能被声明成const的。当我们创建类的一个cost对象时,直到构造函数完成初始化过程,对象才能真正取得其“常量”属性。因此,构造函数在cost对象的构造过程中可以向其写值。

默认构造函数

类通过一个特殊的构造函数来控制默认初始化过程,这个函数叫做默认构造函数(default constructor)。默认构造函数无须任何实参。

如果我们的类没有显式地定义构造函数,那么编译器就会为我们隐式地定义一个默认构造函数。编译器创建的构造函数又被称为合成的默认构造函数(synthesized defaultconstructor)。
对于大多数类来说,这个合成的默认构造函数将按照如下规则初始化类的数据成员:如果存在类内的初始值,用它来初始化成员。·否则,默认初始化该成员。

不能依赖合成的默认构造函数

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

  1. 编译器只有在发现类不包含任何构造函数的情况下才会替我们生成一个默认的构造函数。一旦我们定义了一些其他的构造函数,那么除非我们再定义一个默认的构造函数,否则类将没有默认构造函数。这条规则的依据是,如果一个类在某种情况下需要控制对象初始化,那么该类很可能在所有情况下都需要控制。
  2. 合成的默认构造函数可能执行错误的操作。如果定义在块中的内置类型或复合类型(比如数组和指针)的对象被默认初始化,则它们的值将是未定义的。含有内置类型或复合类型成员的类应该在类的内部初始化这些成员,或者定义一个自己的默认构造函数。否则,用户在创建类的对象时就可能得到未定义的值。
  3. 有的时候编译器不能为某些类合成默认的构造函数。例如,如果类中包含一个其他类类型的成员且这个成员的类型没有默认构造函数,那么编译器将无法初始化该成员。对于这样的类来说,我们必须自定义默认构造函数,否则该类将没有可用的默认构造函数。
  4. 对于具有引用成员或无法默认构造的cost成员的类,编译器不会为其合成默认构造函数

默认构造函数的作用

Sales_data构造函数

class Sales_data {

public:
	Sales_data() = default;
	Sales_data(const std::string &s): bookNo(s) { }
	Sales_data(const std::string &s, unsigned n, double p):bookNo(s), units_sold(n), revenue(p*n) { }
	Sales_data(std::istream &);
private:
	std::string bookNo;
	unsigned units_sold = 0;
	double revenue = 0.0;
};

= default

Sales_data() = default;

在C++11新标准中,如果我们需要默认的行为,那么可以通过在参数列表后面写上=default来要求编译器生成构造函数。

=default既可以和声明一起出现在类的内部,也可以作为定义出现在类的外部。

  1. 如果=default在类的内部,则默认构造函数是内联的;
  2. 如果它在类的外部,则该成员默认情况下不是内联的。

上面的默认构造函数之所以对Sales data有效,是因为我们为内置类型的数据成员提供了初始值。如果你的编译器不支持类内初始值,那么你的默认构造函数就 应该使用构造函数初始值列表 来初始化类的每个成员

构造函数初始值列表

	Sales_data(const std::string &s): bookNo(s) { }
	Sales_data(const std::string &s, unsigned n, double p):bookNo(s), units_sold(n), revenue(p*n) { }

花括号定义了(空的)函数体。

冒号以及冒号和花括号之间的代码称为构造函数初始值列表(constructor initialize list)
它负责为新创建的对象的一个或几个数据成员赋初值。构造函数初始值是成员名字的一个列表,每个名字后面紧跟括号括起来的(或者在花括号内的)成员初始值。不同成员的初始化通过逗号分隔开来。

只有一个string类型参数的构造函数使用这个string对象初始化bookNo,对于units sold和revenue则没有显式地初始化。当某个数据成员被构造函数初始值列表忽略时,它将以与合成默认构造函数相同的方式隐式初始化。

Sales_data(const std::string &s): bookNo(s) { }
//等价于
Sales_data(const std::string &s, unsigned n, double p):bookNo(s), units_sold(0), revenue(0) { }

赋值和初始化

构造函数

Sales_data(const std::string &s, unsigned n, double p):bookNo(s), units_sold(n), revenue(p*n) {}

和构造函数

Sales_data(const std::string &s, unsigned n, double p)
{
	bookNo = s;
	units_sold =n;
	revenue = p*n;
}

的效果是相同的,区别在于上面的构造函数初始化了它的成员变量
下面的构造函数对成员变量进行赋值操作
初始化和赋值的区别
.

必须初始化的成员变量

有时我们可以忽略数据成员初始化和赋值之间的差异,但并非总能这样。
如果成员是const或者是引用的话,必须将其初始化,必须使用构造函数初始值列表。
类似的,当成员属于某种类类型且该类没有定义默认构造函数时,也必须将这个成员初始化。

成员变量初始化顺序

成员的初始化顺序与它们在类定义中的出现顺序一致:第一个成员先被初始化,然后第二个,以此类推。
构造函数初始值列表中初始值的前后位置关系不会影响实际的初始化顺序。般来说,初始化的顺序没什么特别要求。

不过如果一个成员是用另一个成员来初始化的,那么这两个成员的初始化顺序就很关键了。

class X{
	int i;
	int j;
public:
	//未定义的:i在j之前被初始化
	X(int val):j(val), i(j){}
}

在类的外部定义构造函数

Sales_data::Sales_data(std::istream &is) 
{
	// read will read a transaction from is into this object
	read(is, *this);
}

istream& read(istream &is, Sales_data &item)
{
	double price = 0;
	is >> item.bookNo >> item.units_sold >> price;
	item.revenue = price * item.units_sold;
	return is;
}

调用方式

#include "Sales_data.h"
#include 

using std::cin;

int main() {
    Sales_data test1(cin);
    
    return 0;
}

委托构造函数

一个委托构造函数使用它所属的类的其他构造函数执行自己的初始化过程,或者说它把自己的一些(或者全部)职责委托给了其他构造函数。和其他构造函数一样,一个委托构造函数也有一个成员初始值的列表和一个函数体。在委托构造函数内,成员的初始值列表只有一个唯一的入口,就是类名本身。和其他成员初始值一样,类名后面紧跟圆括号括起来的参数列表,参数列表必须与类中另外一个构造函数匹配。

C++ primer 查漏补缺十一:构造函数_第1张图片

关于使用C++11中委托构造函数

explicit

关键字explicit,可以阻止不应该允许的经过转换构造函数进行的隐式转换的发生,声明为explicit的构造函数不能在隐式转换中使用。explicit只对一个实参的构造函数有效。需要多个实参的构造函数不能用于执行隐式转换,所以无须将这些构造函数指定为explicit的。只能在类内声明构造函数时使用explicit关键字,在类外部定义时不应重复

class Sales_data {

public:
	Sales_data() = default;
	Sales_data(const std::string &s): bookNo(s) { }
	explicit Sales_data(const std::string &s, unsigned n, double p):bookNo(s), units_sold(n), revenue(p*n) { }
	explicit Sales_data(std::istream &);
	//其他的同上
}


Sales_data&  Sales_data::combine(const Sales_data &rhs)
{
	units_sold += rhs.units_sold; // add the members of rhs into 
	revenue += rhs.revenue;       // the members of ``this'' object
	return *this; // return the object on which the function was called
}
 

编译器只会执行一步隐式转换
下面这个代码是错误的

item.combine("9-999-99999-9");

这里执行了两步隐式转换

  1. "9-999-99999-9"转化为String
  2. String转化为Sales_data
    所以不可以

如果构造函数没有explicit

Sales_data(const std::string &s, unsigned n, double p):bookNo(s), units_sold(n), revenue(p*n) { }

那上面的代码可以改成

item.combine(string("9-999-99999-9"));

显示转换string,隐式转换Sales_data

如果构造函数有explicit

explicit  Sales_data(const std::string &s, unsigned n, double p):bookNo(s), units_sold(n), revenue(p*n) { }

那上面的代码可以改成

item.combine(Sales_data(string("9-999-99999-9")));

explicit 也不能用于拷贝构造函数,只能初始化

String null_book = "9-999-99999-9";
Sales_data item1(null_book );//可以,直接初始化
Sales_data item2 = null_book ;//不能将explicit用于拷贝构造函数的初始化过程

同理这个

item.comnine(cin);

这段代码隐式地把cin转换成Sales_data ,这个转换执行了接受一个istream的Sales_data 构造函数。该构造函数通过读取标准输入创建了一个(临时的)Sales_data 对象,随后将得到的对象传递给combine。

应该改成

item.comnine(static_cast<Sales_data > cin);

static_cast可以使用explicit 的构造函数

你可能感兴趣的:(C++查漏补缺,c++)