继续整理第二章节剩下的内容。注:以下图片来自原书
1. 对于明确的初始化操作:
拷贝构造函数会被调用,以上代码会被转化成:
2. 关于参数的初始化,参数按值传递到函数中会有临时对象产生:
以上并没有结束,在调用foo( __temp0 )时,参数的值传递会被改为引用传递void foo(X& x0),否则又会产生临时对象,这样就无法终止了。
3. 关于返回值的初始化,左侧的代码会转化为右边的代码。
同时,对于所有函数调用也会进行相应的转化:X xx = bar(); 会转化为X xx; bar(xx);对于函数指针也会有相应的转化:X (*pf) ();转化为void (*pf) (X& )。
4. 对于下面三个初始化语句的实际转化之后的语句见右侧,注意第一句和后面二三句是不一样的。
5. 对于数据成员的初始化操作,有四种情况需要使用初始化列表(member initialization list):
某些情况虽然可以不用初始化列表,但是会导致效率不佳,会有临时对象的开销:
若使用初始化列表,会更有效率,省去了临时对象的开销:
初始化列表到底发生了什么:编译器会一一操作初始化列表,以适当的次序在构造函数中安插初始化操作,并且在任何构造函数已有的代码之前,例如上图中的例子。
关于初始化列表的一个陷阱就是初始化列表并不是按照列表的顺序进行初始化的,而是根据数据成员在类中的声明顺序来定的,例如:
上述代码原意是要使用val初始化j,然后使用j初始化i,但实际情况并不是这样,因为在了类X中,i的声明先与j,所以初始化列表会先使用j初始化i,而此时j并未初始化,所以i得到的是无意义的值。改成如下,就没有问题了。
但是这里有一个比较有趣的问题,按照声明的顺序i先被初始化,还是有为题啊,其实不然,注意上面的描述,实在初始化列表中,初始化按照声明顺序,还有一点就是初始化的代码被安插到构造函数中时,一定会按照到已有代码的前面,并不会有问题:
X::X(int val)
{
j = val;
i = j;
}
到次篇为止,第二章的内容就整理完毕了,之后会继续整理第三章的内容。