赋值式初始化
尽管构造式初始化非常有格调,但对于简单变量,更常见的还是采用等号完成初始化:
int i1 = 1;
char c = 'C';
float f = 89.5F;
看起来很像在赋值,但因为是在定义的过程中执行的,因此采用等号来初始化,和使用圆括号的方式效果是一样的。
可以将“赋值式初始化”当成是初始化的“世俗”版本,在字面上,容易令人误解为赋值。
“赋值式初始化”还有一个问题:只允许使用一个数据作为初始化条件。
比如Point类有一个需要两个入参的构造函数,使用构造式初始化非常方便:
Point pt(45, 190);
没办法改为直接使用赋值式初始化的语法完成相同操作:
Point pt = 45, 190; //语法错误(再往后,会找到某种变通的解决方法)
另外,创建堆对象时,指针指向可以使用赋值式初始化,但所指向的数据本身,无法统一采用赋值式初始化,比如创建一个int类型指针,并初始化所指向的数据为5;
int* pI = new int(5);
非要将对int的初始化也使用“=”完成,语法应该是:
*(int pI = new int) = 5; //语法错误
这不仅语法错误,而且太丑了。
赋值式初始化语句的三个问题:
(1)看起来容易误解成赋值;
(2)不支持需要多个入参的构造;
(3)不支持堆对象的双重初始化。
对于类或结构体,“赋值式初始化”是否和“构造式初始化”完全等效还依赖于编译器的具体实现,比如有这样一个结构体:
struct A
{
A(int i){/*空*/}
};
接下来使用赋值式初始化创建一个新对象:
A a = 5;
编译之后,可能是两种效果,其对比如表7-11所列。
未优化(两步操作) | 优化(合并为一步) |
A tmp(5);//先构造一个临时对象 //再从临时对象复制到目标对象 A a(tmp); | //直接使用5构造出目标对象 A a(5); |
基本上主流编译器都默认使用优化方案,这确实是一个优化行为,对于g++,加上“-fno-elide-constructors”选项后,将回到未优化的低性能版本。
结论:赋值式初始化在内置类型上使用,有其世俗意义,但在类或结构体上使用,弊大于利。
有一个办法可以让类对象构造时,干脆不允许使用赋值式初始化的语法。只需为类的单入参构造函数加上“explicit”修饰:
struct A
{
explicit A(int i)
{}
};
接下来使用赋值式初始化创建一个新对象:
A a = 5; //ERROR
“explicit”意为“清楚明白,易于理解的”。反过来说,用着赋值的语法,行着构造函数的行为,是“不清不楚、不明不白、不易理解的”。加上explicit关键字修饰的构造函数,称为“显式的”构造函数。
现在我们知道,显式构造可用于避免和赋值操作符“=”之间不清不楚、背地里的关系。