- 1.统一的初始化:列表初始化
- 2.列表初始化细节
- 3.std::initializer_list
在 C++98/03 中,对应普通数组和可以直接进行内存拷贝(memcpy ())的对象是可以使用列表初始化 来初始化数据的
// 数组的初始化
int array[] = { 1,3,5,7,9 };
double array1[3] = { 1.2, 1.3, 1.4 };
// 对象的初始化
struct Person
{
int id;
double salary;
}zhang3{ 1, 3000 };
在 C++11 中,列表初始化变得更加灵活了,来看一下下面这段初始化类对象的代码
- eg:
#includeusing namespace std; class Test { public: Test(int) {} private: Test(const Test &); }; int main(void) { //t1:最中规中矩的初始化方式,通过提供的带参构造进行对象的初始化 Test t1(520); Test t2 = 520; Test t3 = { 520 }; Test t4{ 520 }; //t1、arr1 和 t2、arr2:这两个是基础数据类型的列表初始化方式,可以看到,和对象的初始化方式是统一的。 int a1 = { 1314 }; int a2{ 1314 }; int arr1[] = { 1, 2, 3 }; int arr2[]{ 1, 2, 3 }; //使用列表初始化对new 操作符创建新对象进行初始化 int * p = new int{520}; double b = double{52.134}; int * array = new int[3]{1,2,3}; return 0; }
- 测试:
除此之外,列表初始化还可以直接用在函数返回值上:
- eg:
#include#include using namespace std; class Person { public: Person(int id, string name) { cout << "id: " << id << ", name: " << name << endl; } }; Person func() { //代码中的 return { 9527, "华安" }; 就相当于 return (9527, "华安" );,直接返回了一个匿名对象 return { 9527, "华安" }; } int main(void) { Person p = func(); return 0; }
- 测试:
聚合体
- 在 C++11 中,列表初始化的使用范围被大大增强了,但是一些模糊的概念也随之而来,在前面的例子可以得知,列表初始化可以用于自定义类型的初始化,但是对于一个自定义类型,列表初始化可能有两种执行结果
- eg:
#include#include using namespace std; struct T1 { int x; int y; }a = { 123, 321 }; struct T2 { int x; int y; T2(int, int) : x(10), y(20) {} }b = { 123, 321 }; int main(void) { cout << "a.x: " << a.x << ", a.y: " << a.y << endl; cout << "b.x: " << b.x << ", b.y: " << b.y << endl; return 0; }
- 测试:
在上边的程序中都是用列表初始化的方式对对象进行了初始化,但是得到结果却不同,对象 b 并没有被初始化列表中的数据初始化,这是为什么呢?
- 对象 a 是对一个自定义的聚合类型进行初始化,它将以拷贝的形式使用初始化列表中的数据来初始化 T1 结构体中的成员。
- 在结构体 T2 中自定义了一个构造函数,因此实际的初始化是通过这个构造函数完成的。
同样是自定义结构体并且在创建对象的时候都使用了列表初始化来初始化对象,为什么在类内部对对象的初始化方式却不一样呢?
- 因为如果使用列表初始化对对象初始化时,还需要判断这个对象对应的类型是不是一个聚合体,如果是初始化列表中的数据就会拷贝到对象中。
使用列表初始化时,对于什么样的类型 C++ 会认为它是一个聚合体呢?
- 普通数组本身可以看做是一个聚合类型
int x[] = {1,2,3,4,5,6};
double y[3][3] = {
{1.23, 2.34, 3.45},
{4.56, 5.67, 6.78},
{7.89, 8.91, 9.99},
};
char carry[] = {'a', 'b', 'c', 'd', 'e', 'f'};
std::string sarry[] = {"hello", "world", "nihao", "shijie"};
满足以下条件的类(class、struct、union)可以被看做是一个聚合类型:
无用户自定义的构造函数。
无私有或保护的非静态数据成员。
- 场景 1: 类中有私有成员,无法使用列表初始化进行初始化
C++
1
2
3
4
5
6
7
struct T1
{
int x;
long y;
protected:
int z;
}t{ 1, 100, 2}; // error, 类中有私有成员, 无法使用初始化列表初始化
- 场景 2:类中有非静态成员可以通过列表初始化进行初始化,但它不能初始化静态成员变量。
struct T2
{
int x;
long y;
protected:
//结构体中的静态变量 z 不能使用列表初始化进行初始化,它的初始化遵循静态成员的初始化方式。
static int z;
}t{ 1, 100, 2}; // error
无基类。
无虚函数。
类中不能有使用 {} 和 = 直接初始化的非静态数据成员(从 c++14 开始就支持了)。
- 从C++14开始,使用列表初始化也可以初始化在类中使用{}和=初始化过的非静态数据成员。
#include#include using namespace std; struct T2 { int x; long y; protected: static int z; }t1{ 1, 100 }; // ok // 静态成员的初始化 int T2::z = 2; struct T3 { int x; double y = 1.34; int z[3]{1,2,3}; }; int main(void) { T3 t{520, 13.14, {6,7,8}}; // error, c++11不支持,从c++14开始就支持了 return 0; }
非聚合体
对于聚合类型的类可以直接使用列表初始化进行对象的初始化,如果不满足聚合条件还想使用列表初始化其实也是可以的
- 需要在类的内部自定义一个构造函数, 在构造函数中使用初始化列表对类成员变量进行初始化:
#include#include using namespace std; struct T1 { int x; double y; // 在构造函数中使用初始化列表初始化类成员 T1(int a, double b, int c) : x(a), y(b), z(c){} virtual void print() { cout << "x: " << x << ", y: " << y << ", z: " << z << endl; } private: int z; }; int main(void) { T1 t{ 520, 13.14, 1314 }; // ok, 基于构造函数使用初始化列表初始化类成员 t.print(); return 0; }
- 测试:
另外,需要额外注意的是聚合类型的定义并非递归的,也就是说当一个类的非静态成员是非聚合类型时,这个类也可能是聚合类型,比如下面的这个例子:
#include#include using namespace std; struct T1 { int x; double y; private: int z; }; struct T2 { T1 t1; long x1; double y1; }; int main(void) { T2 t2{ {}, 520, 13.14 }; return 0; }
对于一个聚合类型,使用列表初始化相当于对其中的每个元素分别赋值,而对于非聚合类型,则需要先自定义一个合适的构造函数,此时使用列表初始化将会调用它对应的构造函数。
3.std::initializer_list- 在 C++ 的 STL 容器中,可以进行任意长度的数据的初始化,使用初始化列表也只能进行固定参数的初始化,如果想要做到和 STL 一样有任意长度初始化的能力,可以使用 std::initializer_list 这个轻量级的类模板来实现。
- std::initializer_list类模板的特点:
它是一个轻量级的容器类型,内部定义了迭代器 iterator 等容器必须的概念,遍历时得到的迭代器是只读的。
对于 std::initializer_list 而言,它可以接收任意长度的初始化列表,但是要求元素必须是同种类型 T
在 std::initializer_list 内部有三个成员接口:size(), begin(), end()。
std::initializer_list 对象只能被整体初始化或者赋值。
作为普通函数参数
- 接收某一种相同类型的任意多个参数
- 如果想要自定义一个函数并且接收任意个数的参数(变参函数),只需要将函数参数指定为 std::initializer_list,使用初始化列表 { } 作为实参进行数据传递即可。
- eg:
#include#include using namespace std; void traversal(std::initializer_list a) { for (auto it = a.begin(); it != a.end(); ++it) { cout << *it << " "; } cout << endl; } int main(void) { initializer_list list; cout << "current list size: " << list.size() << endl; traversal(list); list = { 1,2,3,4,5,6,7,8,9,0 }; cout << "current list size: " << list.size() << endl; traversal(list); cout << endl; list = { 1,3,5,7,9 }; cout << "current list size: " << list.size() << endl; traversal(list); cout << endl; // 直接通过初始化列表传递数据,初始化列表可以看成是一个对象 // traversal({ 2, 4, 6, 8, 0 }); cout << endl; traversal({ 11,12,13,14,15,16 }); cout << endl; return 0; }
- 测试:
作为构造函数参数
-自定义的类如果在构造对象的时候想要接收任意个数的实参,可以给构造函数指定为 std::initializer_list 类型,在自定义类的内部还是使用容器来存储接收的多个实参。
- eg:
#include#include #include using namespace std; class Test { public: Test(std::initializer_list list) { for (auto it = list.begin(); it != list.end(); ++it) { cout << *it << " "; m_names.push_back(*it); } cout << endl; } private: vector m_names; }; int main(void) { Test t({ "jack", "lucy", "tom" }); Test t1({ "hello", "world", "nihao", "shijie" }); return 0; }
-
测试:
-
参考:列表初始化



