本节内容
学习列表初始化在各个版本更新中的异同
学前知识了解
以下是窄化转换的几种情形:
- 浮点型转整数
- 低精度浮点数转高精度浮点数(double转float)
- 整数转浮点数
- 整数转更小整数( char c { 256 }) ,溢出
- 指针转bool,允许 bool b (pX != nullptr )
列表初始化:使用花括号初始值设定项列表初始化对象
直接列表初始化
- 花括号初始化设定项列表初始化变量
- 花括号初始值设定项列表无名临时对象
- 用new动态开辟内存的对象
- 非静态数据成员初始化设定项
- 构造函数成员初始化项
复制列表初始化
- 在等号后花括号初始值设定项列表初始化变量
- 在函数调用中,参数使用花括号初始值设定项列表
- 在return中,列表初始化返回对象
- 在使用operator[]访问对象时,使用列表初始化重载运算符参数
- 赋值表达式中,列表初始化代替构造函数按顺序赋值
- 在使用等号的非静态数据成员初始值设定项
列表初始化规则如下:
1、如果花括号里是指定初始化器{ .成员名= 值 } ,且为聚合类型(无自定义构造函数、无基类、无虚函数类型),则满足
- 指定初始化器的成员名,必须是该类的直接非静态数据成员
- 指定成员顺序执行聚合初始化(无需匹配类成员声明顺序)
cpp
struct Point { int x; int y ;};
Point p {.y = 2 , .x = 10 ; }; //C++20
2、如果该类是聚合类型,且花括号里是普通初始化列表(无指定成员)
- 对聚合列表初始化,直接安装列表顺序初始化成员
- 对复制列表初始化,列表只能有一个元素,且类型与该类相同/是其基类
cpp
Point p1{ 1 , 2 }; // x = 1 ,y = 2;
Point p2 = { 1 }; // x = 1,y 默认初始化
3、字符数组的字符串字面量初始化
如果该类是字符数组,且花括号里是一个字符串字面量,数组按字符串字面量初始化(自动补/0)
cpp
char str[5] = { "hello" } ; //编译警告但会允许通过,不会填充/0,但是在使用strlen和memcpy时候会崩溃
char str1[6] = "hello"; //末位补0
4、非聚合类的聚合初始化,执行聚合初始化(按成员顺序匹配列表),传统写法
5、空列表+默认构造函数的初始化
如果花括号是空的,且该类有默认构造函数,执行值初始化(调用默认构造函数,内置类型初始化为0)
cpp
int a = {}; // a= 0
std::string s{}; //std::string()
6、std:initalizer_list构造函数匹配,如果该类定义了std::initializer_list作为参数构造函数:
- 优先匹配以std::initializer_list作为唯一参数或第一个参数的构造函数
- 如果匹配失败,在匹配普通构造函数
- 如果同时匹配,优先选std::initializer_list版本
cpp
class Test
{
public:
Test(std::initializer_list<int>) { cout << " init list " << endl; }
Test(int,int) { cout << " normal constructor " << endl; }
}
Test t1 { 1, 2 }; //优先匹配std::initializer_list
Test t2 (1,2) ; //匹配普通构造
7、枚举类型的列表初始化
如果该类是枚举类型,且花括号里只有一个值v,同时满足
- v是标量类型
- v可隐式转换为枚举底层类型
- 转换时非窄化的(int 转 enum { Red = 1 }合法,double转enum不合法),则枚举被直接列表初始化
cpp
enum Color { Red = 1 , Green = 2 , Blue = 3 };
Color c {1}; // C++17合法
8、非引用类型的直接/复制列表初始化,如果该类不是引用类型,且花括号里只有一个值,
- 指向直接初始化列表 (等价于 T obj(v) );
- 复制列表初始化(等价于T obj = v );
- 不允许窄化转换(int 转 char需要显式转换 )
9、引用类型列表初始化(C++17),如果该类是一个引用类型,花括号里是一个值v :
- 生成一个临时对象,通过复制列表初始化该临时对象
- 引用绑定到这个临时对象
- 若引用是非const左值引用,则失败(左值引用不能绑定临时对象)
cpp
const int & ref { 10 } // //临时对象int(10) 被绑定到ref里面
int & ref1 { 20 } ; // error, 非const左值引用不能绑定临时对象
10、空列表零初始化,如果花括号里没有任何值,且该类没有默认构造函数,执行零初始化(内置类型为0)
列表初始化std::initializer_list
cpp
std::initializer_list<int> lists = { 1, 2, 3, 4 };
void f ( std::initializer_list<int> lists );
f ({ 1,2,3,4 });
- 当初始化一个对象之后,编译器会在后台建立一个 const int { 1, 2, 3, 4 }的临时数组,称为后备数组。
- 由于后备数组中的各个元素都是从原语句拷贝过来的,所以每个元素的拷贝/移动函数必须是可访问的
- std::initializer_list对象本身不存储数据,只是指向了后备数组的指针和长度
- 不允许发生窄化转换(double转int, 发生溢出或截断等)
- f ({ 1,2,3,4 })在f 返回之后,立即销毁,跟普通对象行为一致
- 当对象被绑定到后备数组之后,生命周期会被延长 ,例子中lists已经绑定了后备数组,并非引用写法
cpp
bool fun(std::initializer_list<int> il1, std::initializer_list<int> il2)
{
return il2.begin() == il1.begin() + 1;
}
bool overlapping = fun({1, 2, 3}, {2, 3, 4}); // the result is unspecified: the back arrays can share storage within {1, 2, 3, 4}
解释 :结果是unspecified(未指定的),在不同编译器返回结果可能是不同,原因如下:
当运行到fun ( { 1, 2, 3 } , { 2, 3 ,4 })时,生成两个后备数字 const int {1 ,2 ,3 } ,const int { 2 ,3, 4 },因为两者都是const 数据时不会产生修改且含有大量重叠数字,所以编译器会优化空间为 const int { 1 ,2, 3, 4},此时判断结果是true
初始化列表小知识点
1、初始化列表中的元素或者函数调用,严格从左到右
cpp
int func ( int i ) { cout << " i <<endl; }
int arr[] = { func{1} , func{2} , func{3}};
2、花括号列表本身并没有类型,所以使用decltype( {1,2} )是非法的,但是支持auto推导出std::initializer_list
3、当花括号作为参数时,优先匹配std::initializer_list重载,除非使用括号直接调用
cpp
class Test
{
public:
Test(std::initializer_list<int>){}
Test(int a,int b){}
}
Test{1,2}; //优先调用初始化列表
Test(1,2); //调用二元参数
4、当其他可推导参数作为首参时,后续花括号类型默认使用首参类型
std::vector v(std::istream_iterator(std::cin), {}); //正确, {}默认为int
std::vector v({} , std::istream_iterator(std::cin)); //错误,首参不可推导
直接列表初始化
cpp
//通过成员名指定初始化,无需在意顺序
struct Person
{
string name;
int age;
int classId;
}
Person b { .age = 12 , .name = "Alia" , classId = 0}; //使用指定初始化器,可打乱顺序
Person b1{} = { .name = "Bob" , . classId = 3 , .age = 15};
std::vector<Person> vec;
vec.push_back( { "Elice",15,3 };
vec.emplace_back( { .classId = 5 , .name = "Bob" , .age = 11 } ) ;
//省略对象名称,用于临时对象
void PrintMessage (const Person & p } { cout << p.name << p.age << p.classId << endl; }
PrintMessage(Person{ .classId = 0 , .name = "Alice" , .age = 15});
PrintMessage( { .classId = 0 , .name = "Alice" , .age = 15} );
//在堆上创建对象,返回指针
Person* p = new Person { .name = "Aliys" , .age = 13 , .classId = 3 };
if(p2) delete p;
Person GetMessage() { return { .name = "Alice" , . classId = 5 , .age = 15 };
//非静态成员在声明时直接初始化
Class Student
{
public:
Person p { "Bob" , 13 , 1};
Person b { .age = 12 , .name = "Alia" , classId = 0};
}
//构造函数中,初始化成员对象(优先级高于类内初始化)
Class Student
{
Person pp;
public:
Student(string n , int a , int c) : pp {.age = a , .name = n , .classId = c }{}
}
struct Base { int a ; int b ; };
struct Derived : Base
{
int c;
Derived() : Base { .b = 20, .a = 13 } , c {19} {}
}
C++11初次引入
支持花括号列表初始化容器 / 对象
auto可推导为std::initializer_list,模板不支持推导,decltype也不支持
本质为指向一组const数组的指针和长度
cpp
std::initializer_list<int> arr = { 1, 2, 3, 4, 5 } ; //复制初始化
auto arr1 = { 2 ,4, 5 } ; //自动推导为initializer_list
template<typename T>
void func( T t ) {}
func({ 1 ,2 ,3 }); //error,无法模板直接推导为int
func( std::initializer_list<int>{ 1, 2, 3, 4}); //ok , 显式指定类型
C++14推导增强
返回类型支持Initializer_list
常量表达式中可调用initializer_list
标准库容器全面支持initializer_list构造
cpp
auto getList() { return { 1, 2, 3 }; }; //自动推导为initializer_list
//编译期即可获取
constexpr int getSize()
{
std::initializer_list<int> lists = { 1 ,2 ,3 };
return lists.size();
}
std::map<int,string> maps = { { 1, " abc " } , { 2 , " Hello " } }; //支持初始化列表 初始化
C++17 求值顺序严格化
明确后备数组(临时数组)是编译器实体化的prvalue数组
花括号内求值严格从左到右
聚合类型初始化不再优先匹配std::initializer_list构造函数
cpp
class Base { int a ; int b }
class Test
{
public:
Test(std::initializer_list<int>) { cout << " init list " << endl; }
Test(int,int) { cout << " normal constructor " << endl; }
}
Base base { 1, 2 }; //优先匹配到直接初始化 a = 1 , b = 2
Test t1 { 1, 2 }; //优先匹配std::initializer_list
C++20 适配模板/约束/范围库
模块系统隐式支持 initializer_list
结合Concepts约束initializer_list类型
范围库支持initializer_list作为范围
cpp
template < class T >
concept IntList = std::same_as<T, std::initializer_list<int>>;
template <IntList T > //特约版本
void PrintList( T t1) { for(const auto& item : t1 ) cout << item << endl; }
auto t1 = { 1, 2 ,3 };
auto even = t1 | std::views::filter([](int n){ return n % 2 == 0; });
for ( const auto& item : even ) cout << item << endl;
PrintList({ "Bob" , " Alice " } ); //error 约束只支持int
PrintList({ 1, 2, 3 }); // ok
C++23 推导优化
构造函数推导智能,减少显式类型指定
明确悬空引用场景,优化错误提示
支持用户定义字面量适配initializer_list
cpp
auto badList = [](){ return { 1, 2, 3 } };
//该句结束后 立刻销毁badList, 接下来在使用就会变成悬空引用
template <char... Cs>
constexpr auto operator""_list()
{
return std;:initializer_list<int>( (Cs - '0' )... );
}
auto lists = 123_list;
自定义字面量, operator""_list,123_list相当于带入了 { '1' ,'2' , '3' } ,进过Cs - '0'时,转化为 { 1, 2, 3 };