1、列表初始化的规则
在C++11中可以直接在变量名后面加上初始化列表来进行对象的初始化。
struct A {
public:
A(int) {}
};
int main() {
A a(123);
A c = { 123 };
A d{123}; // c++11
int e = {123};
int f{123}; // c++11
return 0;
}
聚合类型可以进行直接列表初始化,那么什么是聚合类呢?
(1)类型是一个普通数组,如int[5],char[],double[]等
(2)类型是一个类,且满足以下条件:
- 没有用户声明的构造函数
- 没有用户提供的构造函数(允许显示预置或弃置的构造函数)
- 没有私有或保护的非静态数据成员
- 没有基类
- 没有虚函数
- 没有{}和=直接初始化的非静态数据成员
- 没有默认成员初始化器
文字难以琢磨可以看下面例子帮助理解:
// 有自定义的构造函数,不能列表初始化
class A {
public:
A(int, int){}
int a;
int b;
int c;
};
// 含有虚函数,不是聚合类
class B {
public:
virtual void func() {}
int a;
int b;
};
// 有基类,不是聚合类
class Base {};
class C : public Base {
public:
int a;
int b;
};
// 有等号初始化,不是聚合类
class D {
public:
int a;
int b = 10;
};
// 含有私有的非静态数据成员,不是聚合类
class E {
public:
int a;
int b;
private:
int c;
};
// 含有默认成员初始化器,不是聚合类
class F {
public:
F() : a(0), b(0) {}
int a;
int b;
};
2、列表初始化的优点
(1)高效,减少调用构造函数的次数
#include<iostream>
using namespace std;
class Data
{
public:
// 无参构造函数
Data() {cout<<"This is Data constructor1"<<endl;}
// 拷贝构造函数
Data(const Data&) {cout<<"This is Data constructor2"<<endl;}
// 拷贝赋值构造函数
Data& operator=(const Data&) {cout<<"This is Data constructor3"<<endl;}
};
// 低效写法
class Test1
{
public:
Test1(Data data) {m_data = data;}
private:
Data m_data;
};
// 高效写法
class Test2
{
public:
Test2(Data data) : m_data(data){}
private:
Data m_data;
};
// 更高效的写法
class Test3
{
public:
Test3(Data& data) : m_data(data){}
private:
Data m_data;
};
int main()
{
Data a;
cout<<"---------------THIS IS TEST1---------------"<<endl;
Test1 t1(a);
cout<<"---------------THIS IS TEST2---------------"<<endl;
Test2 t2(a);
cout<<"---------------THIS IS TEST3---------------"<<endl;
Test3 t3(a);
return 0;
}
/*
输出结果:
This is Data constructor1
---------------THIS IS TEST1---------------
This is Data constructor2
This is Data constructor1
This is Data constructor3
---------------THIS IS TEST2---------------
This is Data constructor2
This is Data constructor2
---------------THIS IS TEST3---------------
This is Data constructor2
(1)对于TEST1,没有使用列表初始化,所以其私有变量m_data是通过调用Data()定义的,所以会出现“This is Data constructor1”,而“This is Data constructor2”是在发生在参数传递调用的拷贝构造,最 后“m_data = data”会发生拷贝赋值,从而调用“This is Data constructor3”
(2)对于TEST2,“This is Data constructor2”也是在发生在参数传递调用的拷贝构造,而另外一次调用是 发生在列表初始化“Test2(Data data) : m_data(data){}”
(3)对于TEST3,则是TEST3的构造函数中参数使用了引用,就避免了参数传递而调用的拷贝构造,所以只有一次 调用拷贝构造是发生在列表初始化的时候
*/
(2) 防止类型窄化,避免精度丢失的隐式类型转化
C++11可使用 explicit 关键字对单参数的构造函数进行声明,让编译器无法进行隐式类型转换,但仅限于单参数或者其他参数有默认值的情况下使用。而列表初始化是禁止避免精度丢失的隐式类型转化,不像 explicit 禁止所有的隐式类型转换
int main() {
// 浮点型到整型的转换
int a = 1.2; // ok
int b = {1.2}; // error
// 整型到浮点型的转换
float c = 1e70; // ok
float d = {1e70}; // error
// char的枚举类型
const int i = 1000;
const int j = 65;
char k = i; // ok
char l = {i}; // error
char m = j; // ok,m为'A'
char m = {j}; // ok,因为是const类型,这里如果去掉const属性,也会报错
}
(3)可以使用初始化列表接受任意长度的参数
std::initializer_list,它可以接收任意长度的初始化列表,但是里面必须是相同类型T,或者都可以转换为T
class Test {
public:
Test(std::initializer_list<int> list) {
for (auto iter = list.begin(); iter != list.end(); ++iter) {
vec.push_back(*iter);
}
}
private:
std::vector<int> vec;
};