在C++11以前,C++的初始化基本上是在C的初始化方式基础上加上了类和类成员的初始化,有很多不太方便的地方。C++11一口气修改了大多数问题,但是也让初始化问题变得更复杂,所以我觉得有必要梳理一下。
C++98的变量初始化
C++98的初始化大部分继承自C,也支持数组,struct和union复合类型利用{}初始化列表来初始化。需要注意的是,初始化列表是顺序初始化,即按照内存顺序一个一个初始化对应成员,这点在下面我们将会看到。
1、POD类型,指针,引用初始化:
- 可用=或者()
1 | int a = 10; |
2、复合类型结构体和联合体初始化:
- 非堆上的对象(不是new出来的),可用=形式的{}初始化列表和同类型对象初始化
- 堆上的对象(new出来的),用空括号()初始化,括号不能传参
- 注意,复合类型结构体不能有类对象成员或构造函数;否则就成了类
1 | struct Man { |
3、类对象初始化:
- 可用=形式或者()形式,不可用{}初始化列表,其实都是调用构造函数或拷贝构造函数
- 成员变量通过构造函数初始化列表初始化;没有显式初始化的成员将不会被初始化
1 | struct CMan { |
3、数组的初始化:
- 非堆上的数组(不是new出来的),可用=形式的{}初始化列表,字符串数组可以直接用常量字符串初始化
- 堆上的数组(new出来的),用空括号()初始化,括号不能传参
- 类对象数组的成员一定会调用构造函数初始化
1 | int a[10] = {1,2}; //顺序初始化,a[0]=1,a[1]=2,其余置0 |
4、复杂类型初始化
- 对于复杂类型,其自身和成员的初始化应满足以上规则和方式
1 | //Compond1是复合结构体,可以用初始化列表 |
C++11的变量初始化
C++11在变量初始化上做了很大拓展,主要有这么两方面:
- 将初始化列表进一步完善成**统一初始化器(Uniform initialization)**;
- 类成员的原地初始化;
另外:初始化列表前的等号可以省略了。
1、统一初始化器
统一初始化器依然保持初始化列表的形式,兼容C++98的用法。从上面C++98我们可以看到,原来的{}初始化列表不能用来初始化new出来的结构对象或数组,也不能用来初始化类对象。C++11中这两个弊端被解决。所以我们只需要了解下统一初始化器在这些地方的使用。
1 | int i{3}; //和括号类似 |
对于类对象来说,如果利用初始化列表来初始化:
- 如果类存在接受
std::initializer_list
参数的构造函数,则优先调用该构造函数; - 如果类不存在接受
std::initializer_list
参数的构造函数,则调用参数个数和类型与初始化列表元素个数相等,类型相符的构造函数; - 如果类中找不到以上两种匹配的构造函数则报错;
- 一个例外是,如果一个自定义类既有默认构造函数,也有
std::initializer_list
作为参数的构造函数,则使用{}作为初始化值构造对象的话,C++标准显式规定了调用其默认构造函数,如果想要以空列表的语义调用第二个版本,则可以使用({})的方式进行初始化。
另外,以std::initializer_list作为形参的话,其实参列表中的元素不要求和T完全匹配,而只需要能转换成T即可,此时只要转换后满足要求,编译器都会优先使用std::initializer_list作为形参的重载版本。在转换的过程中,如果类型提升满足要求则会正常调用;如果发生了窄化转换,则调用会失败报错;只有诸如字符串和数字这类无法转换的类型相互重载时候,重载机制才可能正常工作。
1 | struct Widget { |
2、类成员就地初始化
在C++11之前,只能对结构体或类的静态常量成员进行就地初始化。在C++11中,结构体和类的数据成员在申明时可以直接赋予一个默认值,初始化的方式有两种,一是使用等号"="
,二是使用{}列表初始化。
1 | //C++98 |
C++11标准支持了就地初始化非静态数据成员的同时,构造函数初始化列表的方式也被保留下来,也就是说既可以使用就地初始化,也可以使用初始化列表来完成数据成员的初始化工作。当二者同时使用时,并不冲突,初始化列表发生在就地初始化之后,即最终的初始化结果以构造函数初始化列表为准。参考如下代码:
1 |
|
一些需要注意的问题
C++11的初始化确实给我们带来了许多方便,不过有些情况的存在,也很容易让我们忽视和误解,这里举一些例子。
比如对于vector变量定义,有这么两种形式:
1
2vector<int> va(99); //调用接受size为参数的构造函数,初始化99个元素,默认值都是0
vector<int> vb{99}; //调用接受initializer_list参数的构造函数,初始化一个元素,值为99用auto定义的变量:
1
2auto z1 {99}; // typeof(z1) = initializer_list<int>
auto z2 = 99; // typeof(z2) = int