一、Primer中的说法
首先我们来看看经典是怎么说的:
“当用于类类型对象时,初始化的复制形式和直接形式有所不同:直接初始化直接调用与实参匹配的构造函数,复制初始化总是调用复制构造函数。复制初始化首先使用指定构造函数创建一个临时对象,然后用复制构造函数将那个临时对象复制到正在创建的对象”
还有一段这样说,
“通常直接初始化和复制初始化仅在低级别优化上存在差异,然而,对于不支持复制的类型,或者使用非explicit构造函数的时候,它们有本质区别:
ifstream file1("filename")://直接初始化
ifstream file2 = "filename";//拷贝初始化,但是如果构造函数是非explicit修饰,则编译器会进行优化转成 “file2("filename")”
(注:关键字explicit意思是“明显的”,它修饰于类的构造函数,作用是阻止调用构造函数时的隐式转换。C++类默认是具有隐式转换的,比如构造函数形参是int类型,实参传char,则可以编译通过。)
举例说明:
class ClassTest
{
public:
ClassTest()
{
c[0] = '\0';
cout << "ClassTest()" << endl;
}
ClassTest& operator=(const ClassTest &ct)
{
strcpy(c, ct.c);
cout << "ClassTest& operator=(const ClassTest &ct)" << endl;
return *this;
}
ClassTest(const char *pc)
{
strcpy(c, pc);
cout << "ClassTest (const char *pc)" << endl;
}
// private:
ClassTest(const ClassTest& ct)//注:拷贝构造函数,函数的名称必须和类名称一致,没有返回值,只有唯一的一个参数,而且参数类型是类本身的引用,需要声明成const
{
strcpy(c, ct.c);
cout << "ClassTest(const ClassTest& ct)" << endl;
}
private:
char c[256];
};
int main()
{
cout << "ct1: ";
ClassTest ct1("ab");//直接初始化
cout << "ct2: ";
ClassTest ct2 = "ab";//复制初始化,但编译器进行了优化成为:ct2(“ab”)
cout << "ct3: ";
ClassTest ct3 = ct1;//复制初始化,但编译器进行了优化成为:ct3(ct1)
cout << "ct4: ";
ClassTest ct4(ct1);//直接初始化
cout << "ct5: ";
ClassTest ct5 = ClassTest();//复制初始化
system("pause");
return 0;
}
这样的结果是:
接下来我们放开private,看一下结果:
如图所示:ct3、ct4、ct5编译报错。这里可能有些小伙伴有个疑惑:明明对“=”运算符进行了重载,为啥ct3和ct5也不通过?
这里要说明一下,复制初始化(拷贝初始化)和拷贝是有本质区别。初始化是对象创建过程,拷贝(复制)是赋值过程。比如ClassTest c1= c2,这个叫拷贝初始化,调用拷贝构造函数;而c1 =c2则调用的是重载了运算符“=”的函数
我们再做一个实验:对拷贝构造函数进行explicit声明,恢复屏蔽private:
显示ct3报错,再一步证实explicit会阻止编译器对构造函数的优化。
各个语句是否调用复制构造函数,做一个总结:
1、ClassTest ct1("ab");这条语句属于直接初始化,它不需要调用拷贝构造函数,直接调用构造函数ClassTest(const char *pc)。
2、ClassTest ct2 = "ab";这条语句为复制初始化,它首先调用构造函数ClassTest(const char *pc)函数创建一个临时对象,然后调用拷贝构造函数,把这个临时对象作为参数,构造对象ct2。(可能是VS编译器进行了优化,优化后发现最匹配的是ClassTest(const char *pc),并不需要调用过拷贝构造函数)
3、ClassTest ct3 = ct1;这条语句为复制初始化,因为ct1本来已经存在,所以不需要调用相关的构造函数,而直接调用拷贝构造函数,把它值复制给对象ct3;所以当拷贝构造函数变为私有时,该语句不能编译通过。
4、ClassTest ct4(ct1);这条语句为直接初始化,因为ct1本来已经存在,直接调用拷贝构造函数,生成对象ct3的副本对象ct4。所以当拷贝构造函数变为私有时,该语句不能编译通过。
注:第4个对象ct4与第3个对象ct3的创建所调用的函数是一样的,但是本人却认为,调用复制函数的原因却有所不同。因为直接初始化是根据参数来调用构造函数的,如ClassTest ct4(ct1),它是根据括号中的参数(一个本类的对象),来直接确定为调用复制构造函数ClassTest(const ClassTest& ct),这跟函数重载时,会根据函数调用时的参数来调用相应的函数是一个道理;而对于ct3则不同,它的调用并不是像ct4时那样,是根据参数来确定要调用复制构造函数的,它只是因为初始化必然要调用复制构造函数而已。它理应要创建一个临时对象,但只是这个对象却已经存在,所以就省去了这一步,然后直接调用复制构造函数,因为复制初始化必然要调用复制构造函数,所以ct3的创建仍是复制初始化。
5、ClassTest ct5 = ClassTest();这条语句为复制初始化,首先调用默认构造函数产生一个临时对象,然后调用复制构造函数,把这个临时对象作为参数,构造对象ct5。所以当复制构造函数变为私有时,该语句不能编译通过。