C++11 ---- 列表初始化

目录

列表初始化

[1.1 C++98传统的{}](#1.1 C++98传统的{})

[1.2 C++11中的{}](#1.2 C++11中的{})

[1.2.1 内置类型支持列表初始化](#1.2.1 内置类型支持列表初始化)

[1.2.2 自定义类型支持支持列表初始化](#1.2.2 自定义类型支持支持列表初始化)

[1.2.3 列表初始化过程中,= 可以省略](#1.2.3 列表初始化过程中,= 可以省略)

[1.2.4 列表初始化的本意](#1.2.4 列表初始化的本意)

[1.2.5 窄化转换与窄化检查](#1.2.5 窄化转换与窄化检查)

[1.2.5.1 什么是窄化转换](#1.2.5.1 什么是窄化转换)

[1.2.5.2 上面是窄化检查](#1.2.5.2 上面是窄化检查)

[1.2.5.3 常见的窄化转换](#1.2.5.3 常见的窄化转换)

[1.3 C++11中的std::initializer_list ​](#1.3 C++11中的std::initializer_list)

[1.3.1 std::initializer_list的引入](#1.3.1 std::initializer_list的引入)

[1.3.2 什么是std::initializer_list](#1.3.2 什么是std::initializer_list)

[1.3.3 std::initializer_list 的作用](#1.3.3 std::initializer_list 的作用)

[1.3.4 std::initializer_list 和 {} 的使用](#1.3.4 std::initializer_list 和 {} 的使用)

[1.3.5 {} 使用时,类型转换和 std::initializer_list 优先级](#1.3.5 {} 使用时,类型转换和 std::initializer_list 优先级)

[1.3.6 {}传递构造容器对象的过程](#1.3.6 {}传递构造容器对象的过程)


列表初始化

1.1 C++98传统的{}

在C++98中一般只有数组和结构体用{}进行初始化。

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

struct Student
{
	int _id;
	string _name;
};

int main()
{
	int arr[] = { 1,2,3,4,5 };
	Student s = { 2026, "zhangsan" };
	return 0;
}

1.2 C++11中的{}

C++11以后想统一初始化方式,尝试实现一切对象皆可用{}进行初始化,{}初始化也叫做列表初始化。

1.2.1 内置类型支持列表初始化

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

int main()
{
	int a = { 2 };
	char b = { 'a' };
	double c = { 2.1 };
	cout << a << endl;
	cout << b << endl;
	cout << c << endl;
	return 0;
}

1.2.2 自定义类型支持支持列表初始化

自定义类型本质是类型转换,先根据初始化列表产生构造临时对象,临时对象再调用拷贝构造进行初始化对象,由于编译器优化,这一过程直接变成用初始化列表进行初始化对象。

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		cout << "Date(int year, int month, int day)" << endl;
	}
	Date(const Date& d)
		:_year(d._year)
		, _month(d._month)
		, _day(d._day)
	{
		cout << "Date(const Date& d)" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	// C++11 支持的
	// 这里的本质是:先用{ 2026,5,29 }构造一个Date临时对象
	// 临时对象再去拷贝构造d1,由于编译器会进行优化
	// 构造+拷贝构造合二为一,变成{ 2026,5,29 }直接构造d1
	Date d1 = { 2026,5,29 };
	// 这里的d2引用的是{ 2026,5,30 }构造的临时对象
	const Date& d2 = { 2026,5,30 };

	// 注意:C++98支持单参数时类型转换,也可以不用{}
	// d3 和 d4 的本质与d1相同都是调用构造 + 拷贝构造
	Date d3 = { 2026 };
	Date d4 = 2026;

	// d5 的本质是直接调用构造进行传参
	Date d5(2026);
	return 0;
}

1.2.3 列表初始化过程中,= 可以省略

cpp 复制代码
	int a {2};
	char c {'x'};
	Date d1 { 2026,5,29 };
	const Date& d2 { 2026,5,30 };

	Date d3 { 2026 };
	// 不支持,只有{}初始化,才能省略 =
	// Date d4 2026;

1.2.4 列表初始化的本意

C++11列表初始化的本意是想实现一个统一的初始化方式,方便用户使用,其次它在有些场景下会非常便利。

cpp 复制代码
// 插入一个Date对象
vector<Date> v;
// 1.有名对象
Date d1(2026, 1, 1);
v.push_back(d1);
// 2.匿名对象
v.push_back(Date(2025, 1, 1));
// 3.列表初始化
// 比起有名对象和匿名对象传参,{}初始化更便利​
v.push_back({ 2025, 1, 1 });

map<string, string> d;
d.insert({"sort", "排序"})

1.2.5 窄化转换与窄化检查

1.2.5.1 什么是窄化转换

从范围更大、精度更高的类型,隐式转换到范围更小、精度更低的类型,可能导致值丢失、精度丢失、值溢出。

1.2.5.2 上面是窄化检查

编译器只对{}做窄化检查,在编译时,如果采用列表初始化发生了窄化转换,则编译报错,从而防止运行时值溢出或精度丢失。

1.2.5.3 常见的窄化转换
cpp 复制代码
// 均发生编译错误
int a {3.14}; // double -> int 
short s{100000}; // int ->short 
int x {5u}; // unsigned int -> int

1.3 C++11中的std::initializer_list ​

1.3.1 std::initializer_list的引入

即便上面的初始化已经很方便了,但是对象容器初始化还是不太方便。比如一个vector对象,我想用1~N个值去构造初始化,那么我们需要实现1~N个构造函数才支持,那么对于这样初始化的例子是非常麻烦的。vector<int> v1 ={1,2,3};vector<int> v2 = {1,2,3,4,5};

1.3.2 什么是std::initializer_list

std::initializer_list是C++11中提出的一个类模板。专门用来处理{}初始化列表。

cpp 复制代码
initializer_list<int> il = { 1,3,5 };

std::initializer_list 的本质是底层在上开辟一个数组,将列表初始化中的数据拷贝到数组中,它内部有一个指针指向数组的开始,有一个变量记录数组的元素个数。

cpp 复制代码
  /// initializer_list
  template<class _E>
    class initializer_list
    {
    public:
      typedef _E 		value_type;
      typedef const _E& 	reference;
      typedef const _E& 	const_reference;
      typedef size_t 		size_type;
      typedef const _E* 	iterator;
      typedef const _E* 	const_iterator;

    private:
      iterator			_M_array;
      size_type			_M_len;

      // The compiler can call a private constructor.
      constexpr initializer_list(const_iterator __a, size_type __l)
      : _M_array(__a), _M_len(__l) { }

    public:
      constexpr initializer_list() noexcept
      : _M_array(0), _M_len(0) { }

      // Number of elements.
      constexpr size_type
      size() const noexcept { return _M_len; }

      // First element.
      constexpr const_iterator
      begin() const noexcept { return _M_array; }

      // One past the last element.
      constexpr const_iterator
      end() const noexcept { return begin() + size(); }
    };

std::initializer_list 支持迭代器遍历

1.3.3 std::initializer_list 的作用

对于STL容器(除去array 和 空间适配器 stack/ queue / priority_queue)和string容器都支持一个std::initializer_list的构造函数,也就支持任意多个值构成的{x1,x2,x3...} 进行初始化

此外,容器的赋值也支持 std::initializer_list。

C++参考文献链接:

Reference - C++ Referencehttps://legacy.cplusplus.com/reference/C++ 参考手册 - cppreference.comhttps://zh.cppreference.com/w/cppcppreference.comhttps://en.cppreference.com/w/

说明:第一个链接不是 C++ 官方文档,标准只更新到 C++11,但它以头文件形式呈现,内容简单易懂、阅读体验好。后两个链接分别是 C++ 官方文档的中文版和英文版,信息非常全面,更新到了最新的 C++ 标准,只是相比第一个链接没那么好读。这几份文档各有优势,建议结合起来使用。

1.3.4 std::initializer_list 和 {} 的使用

cpp 复制代码
	// {} + std::initializer_list
	// v1 v2 初始化的本质是匹配std::initializer_list
	// 的构造函数,进行构造
	vector<int> v1({ 1,2,3,4,5 });
	vector<int> v2 = { 1,2,3,4,5 };
	// {}
	// 由于v3是引用,语法强制需要临时对象,
	// 所以先调用构造产生临时对象,进行引用 
	const vector<int>& v3 = { 1,2,3,4,5 }; 

1.3.5 {} 使用时,类型转换和 std::initializer_list 优先级

cpp 复制代码
#include <iostream>
#include <string>
#include <map>
#include <vector>
using namespace std;

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
		: _year(year), _month(month), _day(day)
	{
		cout << "Date(int year, int month, int day)" << endl;
	}
	Date(initializer_list<int> il)
	{
		cout << "initializer_list构造\n";
	}
	Date(const Date &d)
		: _year(d._year), _month(d._month), _day(d._day)
	{
		cout << "Date(const Date& d)" << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1 = {2026, 1, 1};
	const Date& d2 = {2026, 1, 1};
	Date d3({2025,1});
	return 0;
}

{} 和 std::initializer_list 优先级高于 {} 和 类型转换,只要采用列表初始化且类中有std::initializer_list的构造函数,一定会调用std::initializer_list构造函数。对于赋值,也是如此。

1.3.6 {}传递构造容器对象的过程

第一步:构造initializer_list:

容器对象有initializer_list初始化的时候,列表传递给initializer_list,然后initializer_list在上开辟空间,把列表的数据拷贝过来。

验证initializer_list在栈上开辟的方法:

cpp 复制代码
#include <iostream>
using namespace std;

int main()
{
	initializer_list<int> il = { 1,2,3 };	
	int i = 0;
	cout << &il << endl;
	cout << &i << endl;
	return 0;
}

运行结果:

0x7ffe92a475c0

0x7ffe92a475bc

第二步:初始化容器

本质是:遍历initializer_list的元素,进行尾插。

cpp 复制代码
vector(initializer_list<T> l)
{
    for (auto e : l)
    push_back(e)
}

第一步和第二步本质是构造+拷贝构造,对于现代编译器,现代编译器追求效率会对其做优化处理,省去构造initializer_list和拷贝构造,直接在vector内部用{}中的元素进行初始化

相关推荐
PAK向日葵1 小时前
【C++】深入浅出,理解 C++ 奇异递归模板模式(CRTP)
c++·后端·面试
不会C语言的男孩2 小时前
C++ Primer Plus 第8章:函数探幽
开发语言·c++
William_wL_2 小时前
【C++】模板进阶
c++
MC皮蛋侠客9 小时前
Google Test 单元测试指南
c++·单元测试·google test
艾莉丝努力练剑10 小时前
【Linux:文件】Ext系列文件系统进阶
linux·运维·服务器·c++·文件系统·文件io·ext
basketball61612 小时前
C++ NULL 和 nullptr 区别 以及 nullptr 的核心实现
java·开发语言·c++
Fre丸子_14 小时前
自定义文件夹选取功能
c++
思麟呀15 小时前
C++工业级日志项目(六)异步日志器
linux·c++·windows
PAK向日葵16 小时前
从零实现 Python 虚拟机(二):S.A.A.U.S.O 的总体架构设计
c++·python