一、统一初始化(Uniform Initialization)
(一)C++四种初始化方式
1. 小括号:int x(0); //C++98
2. 等号:int x = 0; //C++98
3. 大括号:int x{0}; //C++98成功,C++11成功
4. 等号和大括号:int x = {0}; //C++98失败,C++11成功
(二)统一初始化(也叫大括号初始化)
1、聚合类型定义与大括号初始化
(1)聚合类型的定义
①类型是一个普通类型的数组(如int[10]、char[]、long[2][3])
②类型是一个类(class、struct或union),且:
A.无基类、无虚函数以及无用户自定义的构造函数。
B.无private或protected的非静态数据成员。
C.不能有{}和=直接初始化的非静态数据成员“就地”初始化。(C++14开始己允许这种行为)。
(2)初始化方式:将{t1,t2…tn}内的元素逐一分解并赋值给被初始化的对象,相当于为该对象每个元素/字段分别赋值。(注意不会调用构造函数)
2、非聚合类型的大括号初始化:调用相应的构造函数。
3、注意事项
(1)聚合类型的定义是非递归的。简单来说,当一个类的普通成员是非聚合类型时,这个类也有可能是聚合类型,也就是说可以直接用列表初始化。
(2)对于一个聚合类型,可以直接使用{}进行初始化,这时相当于对其中每个元素分别赋值;而对于非聚合类型,则需要先自定义一个合适的构造函数才能使用{}进行初始化,此时使用初始化列表将调用它对应的构造函数。
(三)大括号初始化的使用场景
1、为类非静态成员指定默认值。//成员变量不支持使用小括号初始化
2、为数组或容器赋值,如vector<int> vec = {1,2,3,4}; //不支持=()初始化。
3、对不支持拷贝操作的对象赋值。如std::unique_ptr<int> p{};//不支持等号。
【编程实验】统一初始化聚合类型与非聚合类型的区别
#include <iostream> #include <vector> #include <map> #include <atomic> using namespace std; //聚合类型(用{}初始化,相当于分别为各成员直接赋值,不会调用构造函数) struct ST { int x; double y = 0.0; //C++11失败,C++14通过 } st = { 1, 2 }; struct Foo { int x; struct ST { int i; int j; } st; public: int k = 0; private: Foo() = default; //C++14允许用default声明为默认构造函数,仍为POD类型。 Foo(const Foo& foo) = default; }; //非聚合类型(用{}初始化时,会调用相应的构造函数) class Base{}; class Bar : Base //1. 有基类,为非聚合类型 { double x; //2. 有private普通成员,为非聚合类型 static int k; //允许静态成员,但必须在类外定义用int Bar::k =0的方式初始化 public: int z; int y{ 0 }; //3. 通过=或{}来就地“就地”初始化,为非聚合类型 public: //4. 类中自定义构造函数,为非聚合类型 Bar(double x, int y, int z): x(x), y(y), z(z) { cout << "Bar(double x, int y, int z)" << endl; } Bar(const Bar& bar) //自定义拷贝构造函数,为非聚合类型 { x = bar.x; y = bar.y; z = bar.z; } virtual void func() {}; //5. 存在虚函数,为非聚合类型 }; int Bar::k = 0; //x,y究竟为0,0还是123,321? //由于非聚合类型,是调用构造函数初始化的。即会将实参123、321传入Test(int,int) //中,但该函数未使用这个实参,而是直接用0来初始化x和y。 struct Test { int x; int y; Test(int, int):x(0),y(0){} } t = { 123, 321 }; //t.x = ?, t.y=? int main() { //1.四种初始化方式对比 int a = 0; //等号=初始化 C++98 int b(2 + 3); //小括号直接初始化 C++98 int c = { 0 }; //大括号等号初始化 C++98、C++11 int d{ 0 }; //大括号直接初始化 C++11 int i; //未初始化 int j{}; //j被初始化为0 int* p; //未初始化 int* q{}; //j被初始化为nullptr int x[] = { 1, 3, 5 }; // C++98通过,C++11通过 float y[4][3] = { {1, 3, 5},{2, 4, 6},{3, 5, 7},{4, 6, 8} }; // C++98通过,C++11通过 int z[]{ 1, 3, 5 }; // C++98失败,C++11通过 vector<int> v{ 1, 3, 5 }; // C++98失败,C++11通过 map<int, double> m = { {1, 1.0f}, {2, 2.0f}, {3, 3.0f} };// C++98失败,C++11通过 //2.聚合类型和非聚合类型的初始化的对比 Foo foo = { 1,{2, 3} }; //POD类型,相当于从大括号中的值逐个赋值给foo对应的成员,不会调用构造函数。 Foo foo2{ 4, 5, 6 }; cout << "st.x = " << st.x << ", st.y = " << st.y << endl; //st.x=1, st.y=2; Bar bar = { 1, 2, 3 }; //非聚合类型,调用构造函数初始化: Bar(double,int,int) Bar bar2{ 1,2,3 }; //非聚合类型,调用Bar(double,int,int)构造函数 //x,y究竟为0,0还是123,321? cout << "t.x = " << t.x << ", t.y = " << t.y << endl; //t.x = 0, t.y = 0,当统一初始化遇到构造函数时,优先调 //用构造函数初始化 return 0; } /*输出结果 st.x = 1, st.y = 2 Bar(double x, int y, int z) Bar(double x, int y, int z) t.x = 0, t.y = 0 */
二、初始化列表
(一)initializer_list的实现细节
template <class _Elem> class initializer_list { // list of pointers to elements public: using value_type = _Elem; using reference = const _Elem&; using const_reference = const _Elem&; using size_type = size_t; using iterator = const _Elem*; using const_iterator = const _Elem*; constexpr initializer_list() noexcept : _First(nullptr), _Last(nullptr) { // empty list } constexpr initializer_list(const _Elem* _First_arg, const _Elem* _Last_arg) noexcept : _First(_First_arg), _Last(_Last_arg) { // construct with pointers } _NODISCARD constexpr const _Elem* begin() const noexcept { // get beginning of list return _First; } _NODISCARD constexpr const _Elem* end() const noexcept { // get end of list return _Last; } _NODISCARD constexpr size_t size() const noexcept { // get length of list return static_cast<size_t>(_Last - _First); } private: const _Elem* _First; const _Elem* _Last; };