list Initialization各版本异同

本节内容

学习列表初始化在各个版本更新中的异同

学前知识了解

以下是窄化转换的几种情形:

  • 浮点型转整数
  • 低精度浮点数转高精度浮点数(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 };

相关推荐
szcsun51 小时前
机器学习(四)--无监督学习
人工智能·学习·机器学习
.小墨迹2 小时前
apollo中车辆的减速绕行,和加速超车实现
c++·学习·算法·ubuntu·机器学习
好奇龙猫2 小时前
【大学院-筆記試験練習:线性代数和数据结构(22)】
学习
超级大只老咪2 小时前
DFS算法(回溯搜索)
算法
Poetinthedusk2 小时前
WPF应用跟随桌面切换
开发语言·wpf
困死,根本不会2 小时前
OpenCV摄像头实时处理:九宫格棋盘检测与棋子识别
笔记·opencv·学习
MicroTech20252 小时前
量子仿真新基石:MLGO微算法科技专用地址生成器驱动量子算法仿真革命
科技·算法·量子计算
Hello World . .2 小时前
数据结构:二叉树(Binary tree)
c语言·开发语言·数据结构·vim
恒者走天下2 小时前
操作系统内核项目面经分享
c++