对一般的二维数组,我们可以有两种列表初始化的方式:
int arr[2][2]{ {1, 2}, {3, 4} };
int arr[2][2]{1, 2, 3, 4};
C++ 11提供了一个新的容器array,它也是一个定长数组,当我们尝试对用array定义的二维数组进行列表初始化时,会发生一些问题:
array<array<int, 2>, 2> arr{ {1, 2}, {3, 4} }; // error: too many initializers for 'array<array<int, 2>, 2>'
原生二维数组按照上面这样写可以列表初始化成功,array二维数组却不可以,只有下面几种写法能列表初始化成功:
array<array<int, 2>, 2> arr{ { { {1, 2} }, { {3, 4} } } };
array<array<int, 2>, 2> arr{ { {1, 2}, {3, 4} } };
array<array<int, 2>, 2> arr{1, 2, 3, 4};
之所以出现上面的情况,原因在于C++中存在一个聚合类型,而array本身是两个聚合类型的嵌套。
所谓聚合类型,指的是一个class/struct,其内部只有public数据,换句话说,就是没有protected/private数据和任何函数/方法。值得一提的是,原生数组也是聚合类型(一维原生数组是一层聚合类型,二维原生数组是二层聚合类型)。
对于array来讲,我们可以认为下面两种定义是等价的,也就是说array本身就是两个聚合类型的嵌套,第一个聚合类型内部只有一个数据成员(为原生数组),而原生数组这个数据成员又是一个聚合类型:
struct array {
int data[2];
}arr;
array<int, 2> arr;
在列表初始化时,每有一层聚合的嵌套,就需要一个大括号,所以如果我们把大括号写全,array的列表初始化应该是:
array<int, 2> arr{ {1, 2} };
array<array<int, 2>, 2> arr{ { { {1, 2} }, { {3, 4} } } };
原生数组的列表初始化应该是这样的:
int arr[2]{1, 2};
int arr[2][2]{ {1, 2}, {3, 4} };
可是下面几种写法居然也是正确的:
int arr[2][2]{1, 2, 3, 4};
array<array<int, 2>, 2> arr{ { {1, 2}, {3, 4} } };
array<array<int, 2>, 2> arr{1, 2, 3, 4};
这就不得不提到C++在列表初始化时的一个语法糖:大括号省略。
C++允许在聚合的数据成员仍然是聚合时,只写最外层一层大括号,当有大括号被省略时,编译器会按照内层聚合所含的数据个数进行依次填充。
// 两层聚合类型,只写最外层大括号
int arr[2][2]{1, 2, 3, 4};
// 四层聚合类型,第三、四层只写最外层大括号,第一、二层大括号写全
array<array<int, 2>, 2> arr{ { {1, 2}, {3, 4} } };
// 四层聚合类型,只写最外层大括号
array<array<int, 2>, 2> arr{1, 2, 3, 4};
甚至,连这种写法都是正确的:
// 四层聚合类型,第二、三、四层只写最外层大括号,第一层大括号写全
array<array<int, 2>, 2> arr{ {1, 2, 3, 4} };
由于array容器内部也是定义了原生数组,而且列表初始化array容器的时候一不小心就会出错,所以说除非真的有必要,否则尽量不使用array容器,直接用原生数组即可。