when someone abandons you,it is him that gets loss because he lost someone who truly loves him but you just lost one who doesn’t love you.
这是第二个 FB 了,从测试到 cpp 开发的过程,虽然依然 “兼职” 着测试方面的事,但是也开始了自己写 cpp 的路,这不,在写的过程中,发现了一个问题,或者说,发现了类用 {} 初始化参数跟之前的学到的不太一样
搜索一番,原来这是 C++11 的特性
首先,区分一下初始化和赋值,虽然对于内置类型,例如 int,初始化和赋值操作的差别是模糊的;但事实是,初始化不是赋值,初始化的含义是创建变量赋予其一个初始值,而赋值的含义是把当前值擦除,而以一个新值来替代。
对象初始化可以分为默认初始化、直接初始化、拷贝初始化以及值初始化
默认初始化
如果定义变量时没有指定初值,则变量被默认初始化。默认值到底是什么由变量类型决定,同时定义变量的位置也会对此有影响
如果是内置类型的变量未被显示初始化,它的值由定义的位置决定,定义在任何函数体之外的变量被初始化为 0
但是有一种例外,定义在函数体内部的内置类型变量将不被初始化。一个未被初始化的内置类型变量时未定义的,如果试图拷贝或以其他形式访问此变量将引发错误
每个类各自决定了其初始化对象的方式。绝大数类支持无须显示的初始化而定义对象。默认调用该类的默认构造方法
1 | int i1; // 默认初始化,在函数体之外 (初始化为 0) |
拷贝初始化
使用等号 = 初始化一个变量,实际上执行的是拷贝初始化,编译器把等号右侧的初始值拷贝到新创建的对象中去,拷贝初始化通常使用拷贝构造函数来完成
拷贝初始化不仅在使用 = 定义变量时会发生,在下列情况也会发生:
直接初始化
实际上是要求编译器使用普通的函数来选择与提供的参数最匹配的构造函数
1 | string str1(10,'9'); // 直接初始化 |
值初始化,仅限于容器类
1 | // 当只提供 vector 对象容纳的元素数量而去忽略元素的初始值,此时库会创建一个值初始化的元素初值,并把它赋予容器中的所有元素 |
C++11 之前,内置类型(int 为例)和自定义类主要有以下几种初始化形式:
1 | // 自定义 Test 类,下文不再重复声明 |
虽然 C++03 提供了多样的对象初始化方式,但自定义类型对象却无法使用大括号进行初始化,也不能在使用 new[] 的时候初始化纯数据(Plain of Data, POD)数组。所以,C++11 提出了统一初始化语法:一种至少在概念上可以用于表达任何值的语法 —— 大括号初始化
1 | int x{0}; // 初始值在大括号内 |
C++11 大括号初始化还可以应用于容器,终于可以摆脱 push_back() 调用了,C++11 中可以直观地初始化容器了:
1 | // C++11 container initializer |
此外,C++11 中,类的数据成员在申明时可以直接赋予一个默认值,大括号 {} 可以,等号 = 可以,但是,圆括号 () 不可以:
1 | class Test { |
另一方面,不可拷贝对象 (例如,std::atomic) 可以用大括号和圆括号初始化,但不能用等号:
1 | std::atomic<int> ai1{ 0 }; // 可以 |
并且,当大括号初始化用于内置类型的变量时,如果初始值存在丢失信息的风险,则编译器将报错:
1 | doubel ld = 3.14; |
大括号初始化这种语法这样一举多得,那为什么不用大括号初始化语法替代其他呢?因为缺点,它有时会显现令人惊讶的的行为,这些行为的出现是因为与 std::initializer_list 混淆了
在构造函数中,只要形参不带有 std::initializer_list,圆括号和大括号行为一致:
1 | class Test { |
但是,如果构造函数的形参带有 std::initializer_list,调用构造函数时大括号初始化语法会强制使用带 std::initializer_list 参数的重载构造函数:
1 | class Test { |
即使是正常的拷贝构造和赋值构造也可以被带有 std::initializer_list 的构造函数劫持:
1 | class Test { |
编译器用带有 std::initializer_list 构造函数匹配大括号初始值的决心是如此的坚定,即使带有 std::initializer_list 的构造函数是无法调用的,编译器也会忽略另外两个构造函数(第二个还是参数精确匹配的):
1 | class Test { |
只有当大括号内的值无法转换为 std::initializer_list 元素的类型时,编译器才会使用正常的重载选择方法:
1 | class Test { |
一个有趣的边缘情况:一个大括号内无参的实例化,是调用默认构造,还是调用带 std::initializer_list 的构造函数?
1 | class Test { |
be slow to promise and quick to perform.