initializer-list和列表初始化
C++11的列表初始化
在C语言和C++98/03中,大括号可以用来初始化数组,例如:
int a[] = {1, 2, 3};
int b[4] = {1, 2, 3, 4}; // 如果个数不足的,用0初始化
C++11将这类大括号初始化,扩展到自定义类型,但需要满足一定的条件,否则会编译报错。
- C++98/03标准中对于普通数组和POD类型可以直接使用列表初始化;
- C++11标准中对于普通数组和聚合类型可以直接使用列表初始化;
- C++11标准中对于非聚合类型可以通过自定义构造函数的方式使用列表初始化。
C++11新特性之列表初始化提到非聚合类型不能使用列表初始化是不对的。实验证明,不论是不是聚合类型,均可以采用列表初始化。
class A {
public:
A(int a1) {}
A(std::initializer_list<int> l) {}
private:
int a {12};
};
int main()
{
A a = {123};
...
}
首先声明A a={123}
或者A a{123}
这两种构造方法是一致的。
其次,对于这种构造方法,编译器会先尝试用A(std::initializer_list<int> l)
去匹配,如果不成功,则会尝试A(int)
,如果这两种构造函数都未定义,就会编译报错。
另外,成员变量也可以采用就地初始化, 虽然这会导致类成为非聚合类,但并不妨碍其采用列表初始化方法。使用虚函数,有基类的效果都是一样的,不影响列表初始化方法的使用。
C++11中的几种初始化方法
- 就地初始化
class A {
private:
int a1 {12};
double a2 = {12.0};
float a3 = 12.0
B b{123}
C c = {123};
};
- 构造函数初始化列表
class A {
public:
A() : a(123), b(456) {}
private:
int a;
int b;
};
- 列表初始化
class A {
public:
A(int, int) {}
A(std::initializer_list<int, int>) {}
private:
int a;
int b;
};
void main ()
{
// 优先匹配A(std::initializer_list<int, int>)
// 再匹配A(int, int)
// 否则报错
A a {123, 456};
}
- 就地初始化会最先得到执行,构造函数初始化列表会覆盖就地初始化的值
- 如果采用初始化列表,即
a{...}
初始化,std::initializer_list<T>
的构造函数会优先得到执行 - 如果采用原生构造函数,即
a(int)
初始化,A(init)
优先得到执行
什么是initializer-list
摘录C++11新特性之列表初始化-初始化列表
- 它是一个轻量级的容器类型,内部定义了迭代器iterator等容器必须的一些概念。
- initialzer-list<T>来说,它可以接受任意长度的初始化列表,但是元素必须是要相同的或者可以转换为T类型的。
- 三个成员接口,begin(),end(),size(),其中size()返回initialzer-list的长度。
- 能被整体的初始化和赋值,遍历只能通过begin和end迭代器来,遍历取得的数据是可读的,是不能对单个进行修改的。
注意一:
initialzer-list<T>保存的是T类型的引用,并不对T类型的数据进行拷贝,因此需要注意变量的生存期
std::initializer_list<int> func(void)
{
return{ 2, 3 };
}
void main()
{
auto c = func();
for (auto it = c.begin(); it != c.end(); it++)
std::cout << it - c.begin() << ":" << (*it) << std::endl;
}
此处打印是乱的。因为func返回的是右值引用,在退出函数后失效。
注意二:
列表初始化防止类型收窄
int a = 1.1; //OK
int b{ 1.1 }; //error
float f1 = 1e40; //OK, 科学计数法10^40
float f2{ 1e40 }; //error
const int x = 1024, y = 1;
char c = x; //OK
char d{ x };//error
char e = y;//error
char f{ y };//error