C++的各种初始化方式(2)

对于类类型来说,用户提供自定义的默认构造函数有一些额外的“副作用”。比如,对于缺少用户提供的自定义默认构造函数的类,是无法定义该类的const对象的。示例如下:

class exec { int i; }; const exec e;  //错误!缺少用户自定义默认构造函数,不允许定义const类对象

通过开头的例子,我们已经对C++的一些初始化方式有了直观的感受。 C++中的初始化分为6种:零初始化、默认初始化、值初始化、直接初始化、拷贝初始化、列表初始化。

零初始化和变量的类型和位置有关系,比如是否static,是否aggregate聚合类型。能进行0初始化的类型的对象的值都是0,比如int为0,double为0.0,指针为nullptr;

现在我们已经了解了几种初始化的规则,下面则是几种初始化方式的使用形式:

1. 默认初始化是定义对象时,没有使用初始化器,也即没有做任何初始化说明时的行为。典型的:

int i; vector<int> v;

2. 值初始化是定义对象时,要求初始化,但没有给出初始值的行为。典型的:

int i{}; new int(); new int{}; //C++11

3. 直接初始化和拷贝初始化主要是相对于我们自定义的对象的初始化而言的,对于内置类型,这两者没有区别。对于自定义对象,直接初始化和拷贝初始化区别是直接调用构造函数还是用"="来进行初始化。典型的:

vector<int> v1(10); //直接初始化,匹配某一构造函数 vector<string> v2(10); //直接初始化,匹配某一构造函数 vector<int> v3=v1; //拷贝初始化,使用=进行初始化

对于书本中给出的示例:

string dots(10, '.'); //直接初始化 string s(dots);      //直接初始化

这里s的初始化书本说是直接初始化,看起来似乎像是拷贝初始化,其实的确是直接初始化,因为直接初始化是用参数来直接匹配某一个构造函数,而拷贝构造函数和其他构造函数形成了重载,以至于刚好调用了拷贝构造函数。

事实上,C++语言标准规定复制初始化应该是先调用对应的构造函数创建一个临时对象,然后拷贝构造函数再将构造的临时对象拷贝给要创建的对象。例如:

string a = "hello";

上面代码中,因为“hello"的类型是const char *,所以string类的string(const char *)构造函���会被首先调用,创建一个临时对象,然后拷贝构造函数将这个临时对象复制到a。但是标准还规定,为了提高效率,允许编译器跳过创建临时对象这一步,直接调用构造函数构造要创建的对象,从而忽略调用拷贝构造函数进行优化,这样就完全等价于直接初始化了,当然可以使用-fno-elide-constructors选项来禁用优化。

如果我们将string类型的拷贝构造函数定义为private或者定义为delete,那么就无法通过编译,虽然能够进行优化省略拷贝构造函数的调用,但是拷贝构造函数在语法上还是要能正常访问的,这也是为什么C++ primer第五版第13章拷贝控制13.1.1节末尾442页最后一段话中说:

“即使编译器略过了拷贝/移动构造函数,但在这个程序点上,拷贝/移动构造函数必须是存在且可访问的(例如,不能是priviate的)。

拷贝初始化不仅在使用=定义变量时会发生,在以下几种特殊情况中也会发生:

1.将一个对象作为实参传递给一个非引用的形参;

2.从一个返回类型为非引用的函数返回一个对象;

3.用花括号列表初始化一个数组中的元素或一个聚合类中的成员。

其实还有一个情况,比如:当以值抛出或捕获一个异常时。

另外还有比较让人迷惑的地方在于vector<string> v2(10),在《C++ Primer 5th》中说这是值初始化的方式,但是仔细看书本,这里的值初始化指的是容器中string元素,也就是说v2本身是直接初始化的,而v2中的10个string元素,由于没有给出初始值,因此标准库对容器中的元素采用了值初始化的方式进行初始化。

结合来说:

只要使用了括号(圆括号或花括号)但没有给出具体初始值,就是值初始化。可以简单理解为括号告诉编译器你希望该对象初始化。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/80815bb8a00ef25e77c25c7279903033.html