针对于C++11的《C++ Primer 5th》相对于上一本书进行了比较大的修改,在整体内容的架构上确实更加合理。
但是对于拷贝初始化和直接初始化的介绍上并不如4th那么清楚。
P441,5th的叙述:
当使用直接初始化时,我们实际上是要求编译器使用普通的函数匹配来选择与我们提供的参数最匹配的构造函数。
当我们使用拷贝初始化时,我们要求编译器将右侧运算对象拷贝到正在创建的对象中,如果需要的话还要进行类型转换。
4th的叙述如下:
直接初始化直接调用与实参匹配的构造函数,复制初始化总是调用复制构造函数。
复制初始化首先使用指定构造函数创建一个临时对象,然后用复制构造函数将那个临时对象复制到正在创建的对象。
总结起来:
拷贝初始化并不一定只调用一个构造函数,但它必定会调用拷贝构造函数。
它首先根据等号右边的内容,参数匹配调用合适的构造函数,合成一个临时对象。
然后调用拷贝构造函数,将临时对象拷贝到正在创建的对象。
1.初始化方式
(1)默认初始化P40P262
①非static内置类型,局部变量
不被初始化,值是未定义的
②非static内置类型,全局变量
初始化为0
③类类型对象
调用该类型的默认构造函数
(2)值初始化P88P262
针对容器对象的初始化,且只给出了容器大小,没有给出具体值,或给出具体值的个数小于容器大小。
①容器元素是内置类型
初始化为0
②容器元素是类类型对象
对该对象使用默认初始化
(3)拷贝初始化P76
使用=的初始化方式
拷贝初始化并不一定只调用一个构造函数。
它首先根据等号右边的内容,参数匹配调用合适的构造函数,合成一个临时对象。
然后调用拷贝构造函数,将临时对象拷贝到正在创建的对象。
(4)直接初始化P76
使用()的初始化方式
(5)列表初始化P40
使用{}的初始化方式
注:①由(3)可知,如果=右边是()或者{}这也是拷贝初始化,只是通过使用直接初始化或者{}构造了一个临时对象,再拷贝初始化正在创建的对象。
②默认和值初始化都是没有任何表示。
拷贝=
直接()
列表{}
2.构造函数
(1)默认构造函数P235
(2)普通构造函数P237
(3)拷贝构造函数P440
(4)委托构造函数P261
(5)转换构造函数P263
3.相关概念
(1)类内初始值P64
(2)默认实参P40、P211
(3)explicit关键字P265
explicit的作用就是阻止在拷贝初始化的时候,使用本构造函数构造临时对象,再将此临时对象用于拷贝构造。
由于在拷贝初始化过程中的构造临时对象,就对应着隐式的类型转换过程,所以也可以说explicit的作用是防止隐式类型转换。
结果就是,如果进行拷贝初始化的时候用到这个构造函数就会通不过编译,
同样,在其他要用到拷贝初始化的地方,比如参数传递使用值传递的时候,就通不过编译。
(4)构造函数初始值列表P238
为了验证3中的(3)的结论,可以运行一下代码。
#include <iostream>
using namspace std;
class Test
{
public:
Test() = default; //默认构造函数
Test(const Test &tt):t(tt.t) {}
explicit Test(const int &a):t(a) {}
Test(const double &a):t(int(a)) {}
private:
int t = 0;
}
int main()
{
Test t2 = 5;
return 0;
}
编译可以通过,但是如果在类定义中,将Test(const double &a):t(int(a)) {}注释掉,则无法编译通过。
原因是,开始对于Test t2 = 5;是拷贝初始化,首先可以匹配一个 Test(const double &a):t(int(a)) {}来构造一个临时对象。
注释掉以后,由于explicit Test(const int &a):t(a) {}不能用于拷贝初始化构造临时对象,所以找不到对应的构造函数,无法初始化。
2016/3/6补充
几种初始化方式补充
以类X为例
(1)默认初始化
构造函数:X() = default;
详细是用情况见书P262
调用:X x;
(2)直接初始化
构造函数:X(各种): 各种{}
调用:X x(各种参数);
(3)值初始化
一般应用于容器对象,并且是在给的初始值数量少于容器大小,那些没有给初始值的就是值初始化,它与默认最大的不同就是内置类型初始化为0。
详细情况见书P262
调用:Vector<int> vec(10);
(4)拷贝初始化
构造函数:X(const X &x)
调用:
任何带有=的初始化都会是用等号右边内容构造的临时对象来调用构造函数。
(5)列表初始化
一般也用于容器对象
构造函数X(initializer_list<int>)
调用vector<int> vec{1,2,3,4,6};
注意:
并不是调用了拷贝构造函数就是拷贝初始化,相反,所有的拷贝初始化都只是隐式调用拷贝构造函数的。
拷贝初始化的定义是使用=作为初始化,这种初始化时隐式调用拷贝构造函数的。
其具体过程是,①用等号右边的东东参数匹配某一构造函数生成一个临时类对象,②用这个临时类对象作为参数隐式调用拷贝构造函数。
举个栗子,书P441
string dots(10, '.'); //直接初始化
string s(dots); //直接初始化
因为第二个式子,直接函数匹配调用了拷贝构造函数,所以它是直接初始化,而不是拷贝初始化。
同一个道理,
vector<int> vec({1,2,3,4}) //直接初始化
vector<int> vec{1,2,3,5} //列表初始化
vector<int> vec = {1,2,3,4} //拷贝初始化
当然此项注意只是一个完备性的说明,大可不必太认真。