logo头像

野渡's小小知识乐园

c++11特性之列表初始化

最近注意到c++11的列表初始化特性,发现还是挺方便的,这里总结一下,以便之后进行使用。


1、 C++11的各种初始化方式

c++11主要提供了如下6种初始化方式:零初始化、默认初始化、值初始化、直接初始化、拷贝初始化、列表初始化
几种初始化之间的主要区别如下:

  • 默认初始化:如果是一个类,那么调用默认构造函数进行初始化;如果是一个数组,那么每个元素默认初始化;除了前面的情况之外,不进行初始化,其值未定义。
  • 值初始化:如果是一个类,那么类的对象进行默认初始化,如果用户没有定义默认构造函数,则会进行零值初始化再进行默认初始化;如果是一个数组,每个元素值初始化,否则进行零初始化。如果你不想要你的默认构造函数是用户自定义的,那么必须在类的内部声明处使用”=default”,而不是在类外部定义处使用,后者会使得编译器认为用户定义了默认构造函数。
  • 零初始化:对于static或者thread_local变量将会在其他类型的初始化之前先初始化。如果T是算数、指针、枚举类型,将会初始化为0;如果是类类型,基类和数据成员会零初始化;如果是数组,数组元素也零初始化。

2、 几种初始化的实例

下面给出几种上述初始化方式的实例,从代码上来体现他们的区别:

1
2
3
4
5
6
7
8
9
10
11
int i;         //默认初始化
int j{}; //值初始化(C++11),要求初始化,但没有给出初始值的行为
int k = 5; //直接初始化
int l(); //值初始化
int m{4}; //列表初始化
new int; //默认初始化
new int(); //值初始化
new int{}; //值初始化(C++11)

string str{"dengwen"}; //直接初始化
string str1=str; //拷贝初始化

只要使用了括号(圆括号或花括号)但没有给出具体初始值,就是值初始化。可以简单理解为括号告诉编译器你希望该对象初始化。

没有使用括号,就是默认初始化。可以简单理解成,你放任不管,允许编译器使用默认行为。通常这是糟糕的行为,除非你真的懂自己在干什么。

3、 列表初始化的特殊之处

在C++11以前,程序员,或者初学者经常会感到疑惑关于怎样去初始化一个变量或者是一个对象。这么多的对象初始化方式,不仅增加了学习成本,也使得代码风格有较大出入,影响了代码的可读性和统一性。

从C++11开始,对列表初始化(List Initialization)的功能进行了扩充,可以作用于任何类型对象的初始化,至此,列表初始化方式完成了天下大一统。

花括号列表初始化,作为C++11新标准的一部被加入到了C++中。因为这个原因,c++11提出了统一初始化,下面的做法都是正确的。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Test
{
public:
C(int i, int j);
private:
int a;
int b;
};

Test t{1,3}; //初始化
t={2,4}; //这里可以直接进行赋值,而且是对私有变量....
Test *p=new Test{3,5}; //列表初始化
int *q = new int[3]{1,2,0}; //数组

此外,C++11列表初始化还可以应用于容器,终于可以摆脱 push_back() 调用了,将C++11提供的列表初始化作为统一的初始化方式,既降低了记忆难度,也提高的代码的统一度。

需要注意的点:使用初始化列表来初始化内置类型的变量时,若存在类型转换且具有丢失信息的风险时,编译器将会报错

通过这一点可以看出,列表初始化比原有的初始化方式具有更严格的安全要求。下面是例子:

1
2
3
long double ld = 3.1415926536
int a {ld}, b = {ld}; // 编译器报错,存在丢失信息的风险
int c(ld), d = ld ; //正确