文章目录

  • ​​1.统一的初始化:列表初始化​​
  • ​​2.列表初始化细节​​
  • ​​3.std::initializer_list​​

1.统一的初始化:列表初始化

在 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:
#include <iostream>
using namespace std;

class Test
{
public:
Test(int) {}
private:
Test(const Test &);
};

int main(void)
{
//t1:最中规中矩的初始化方式,通过提供的带参构造进行对象的初始化
Test t1(520);

/*语法错误,因为提供的拷贝构造函数是私有的。
如果拷贝构造函数是公共的,520 会通过隐式类型转换被 Test(int) 构造成一个匿名对象,
然后再通过对这个匿名对象进行拷贝构造得到 t2
这个错误在 VS 中不会出现,在 Linux 中使用 g++ 编译会提示描述的这个错误,截图如下
ps:vs中就算将拷贝构造和等号运算符定义为私有的也没用(vs中这里的赋值等价于初始化),除非使用explicit
*/
Test t2 = 520;

/*
t3 和 t4:使用了 C++11 的初始化方式来初始化对象,效果和 t1 的方式是相同的。

在初始时,{} 前面的等号是否书写对初始化行为没有任何影响。
t3 虽然使用了等号,但是它仍然是列表初始化,因此私有的拷贝构造对它没有任何影响。
*/
Test t3 = { 520 };
Test t4{ 520 };

//t1、arr1 和 t2、arr2:这两个是基础数据类型的列表初始化方式,可以看到,和对象的初始化方式是统一的。
int a1 = { 1314 };

/*
t4、a2、arr2 的写法,是 C++11 中新添加的语法格式,使用这种方式可以直接在变量名后边跟上初始化列表,来进行变量或者对象的初始化。
*/
int a2{ 1314 };
int arr1[] = { 1, 2, 3 };
int arr2[]{ 1, 2, 3 };

//使用列表初始化对new 操作符创建新对象进行初始化
/*
指针p 指向了一个 new 操作符返回的内存,通过列表初始化将内存数据初始化为了 520
变量b 是对匿名对象使用列表初始之后,再进行拷贝初始化。
数组array 在堆上动态分配了一块内存,通过列表初始化的方式直接完成了多个元素的初始化。
*/
int * p = new int{520};
double b = double{52.134};
int * array = new int[3]{1,2,3};
return 0;
}
  • 测试:

除此之外,列表初始化还可以直接用在函数返回值上:

  • eg:
#include <iostream>
#include <string>
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;
}
  • 测试:

2.列表初始化细节

聚合体

  • 在 C++11 中,列表初始化的使用范围被大大增强了,但是一些模糊的概念也随之而来,在前面的例子可以得知,列表初始化可以用于自定义类型的初始化,但是对于一个自定义类型,列表初始化可能有两种执行结果
  • eg:
#include <iostream>
#include <string>
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: 类中有私有成员,无法使用列表初始化进行初始化
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 <iostream>
#include <string>
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 <iostream>
#include <string>
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 <iostream>
#include <string>
using namespace std;

struct T1
{
int x;
double y;
private:
int z;
};

struct T2
{
/*
可以看到,T1 并非一个聚合类型,因为它有一个 Private 的非静态成员。
但是尽管 T2 有一个非聚合类型的非静态成员 t1,T2 依然是一个聚合类型,可以直接使用列表初始化的方式进行初始化。
*/
T1 t1;
long x1;
double y1;
};

int main(void)
{
/*
最后强调一下 t2 对象的初始化过程,对于非聚合类型的成员 t1 做初始化的时候,
可以直接写一对空的大括号 {},这相当于调用是 T1 的无参构造函数。
*/
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 <iostream>
#include <string>
using namespace std;

void traversal(std::initializer_list<int> a)
{
for (auto it = a.begin(); it != a.end(); ++it)
{
cout << *it << " ";
}
cout << endl;
}

int main(void)
{
initializer_list<int> 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 <iostream>
#include <string>
#include <vector>
using namespace std;

class Test
{
public:
Test(std::initializer_list<string> list)
{
for (auto it = list.begin(); it != list.end(); ++it)
{
cout << *it << " ";
m_names.push_back(*it);
}
cout << endl;
}
private:
vector<string> m_names;
};

int main(void)
{
Test t({ "jack", "lucy", "tom" });
Test t1({ "hello", "world", "nihao", "shijie" });
return 0;
}