c++11详解(一):统一初始化——列表初始化与 initializer_list
前言:c++11为什么要重新设计初始化?
1. c++98 中初始化方式比较零散
在 c++98 中,不同类型往往使用不同方式初始化。
数组:
int a[] = {1, 2, 3, 4, 5};
结构体:
struct point
{
int x;
int y;
};
point p = {1, 2};普通变量:
int x = 10;
对象:
date d(2025, 1, 1);
容器:
vector<int> v; v.push_back(1); v.push_back(2); v.push_back(3);
可以看到:
数组 → {}
普通变量 → =
对象 → ()
容器 → 一个一个插入
初始化语法并不统一。
c++11 的一个重要目标,就是尽可能提供一套统一的初始化方式:
{}
这就是:
列表初始化 list initialization
一、c++11 的列表初始化
1. 内置类型也可以使用 {}
c++11 中可以写:
int x{10};
double d{3.14};
char ch{'a'};也可以保留等号:
int x = {10};
因此:
int x1 = {10};
int x2{10};
都是合法的。
第二种:
int x2{10};
通常称为:
直接列表初始化
二、自定义类型也支持列表初始化
1. 普通类
定义:
#include <iostream>
using namespace std;
class date
{
public:
date(int year = 1,
int month = 1,
int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};以前通常:
date d1(2025, 1, 1);
c++11 可以:
date d2{2025, 1, 1};
甚至:
date d3 = {2025, 1, 1};
编译器会根据:
2025 1 1
寻找能够匹配的构造函数。
三、列表初始化并不是“简单换了一对括号”
这是一个很容易产生误解的地方。
date d{2025, 1, 1};
虽然表面看起来只是:
() → {}
但:
列表初始化
在 c++ 语言中有自己完整的一套重载决议与类型检查规则。
尤其需要注意:
initializer_list 窄化转换 构造函数选择
这些问题。
四、列表初始化禁止很多窄化转换
这是 {} 相比传统初始化非常重要的特点。
所谓窄化转换,可以简单理解为:
把一个值转换成表达能力更弱、可能损失信息的类型。
例如:
double d = 3.14; int x = d;
传统写法通常允许:
3.14 ↓ 3
但是:
int x{d};
编译器通常会直接报错。
1. 一个简单例子
double d = 3.14;
int x1 = d; // 可以,可能丢失小数
// int x2{d}; // 错误:窄化转换因此 {} 能够帮助我们在编译阶段发现一部分潜在的数据丢失问题。
五、为什么现代 c++ 经常推荐 {}
例如:
int x{10};
string s{"hello"};
vector<int> v{1, 2, 3};
这样有几个明显优点:
初始化语法更加统一 能够发现一部分窄化转换 方便初始化容器 方便构造临时对象 可以直接调用对象构造函数
但这并不意味着:
任何时候
{}都一定比()更好。
因为一旦类提供:
initializer_list
构造函数,{} 的重载选择可能和 () 不一样。
后面就会看到这一点。
六、容器初始化为什么仍然是一个问题?
假设我们希望创建:
vector<int>
并直接放进去:
1 2 3 4 5
如果没有专门机制,理论上可能需要:
vector<int> v; v.push_back(1); v.push_back(2); v.push_back(3); v.push_back(4); v.push_back(5);
那么能不能:
vector<int> v{1, 2, 3, 4, 5};
当然可以。
支持它的重要机制之一,就是:
std::initializer_list
七、什么是 std::initializer_list
需要:
#include <initializer_list>
它是 c++11 引入的一个标准库类模板:
std::initializer_list<t>
可以把它理解成:
一个轻量级对象,用来访问编译器为
{x1,x2,x3...}创建的一组只读元素。
例如:
std::initializer_list<int> il
= {10, 20, 30, 40};
此时:
il ↓ 10 20 30 40
八、initializer_list 可以遍历
例如:
#include <iostream>
#include <initializer_list>
using namespace std;
int main()
{
initializer_list<int> il
= {10, 20, 30, 40};
for (auto e : il)
{
cout << e << " ";
}
cout << endl;
return 0;
}输出:
10 20 30 40
也可以:
for (auto it = il.begin();
it != il.end();
++it)
{
cout << *it << " ";
}九、initializer_list 中的元素为什么不能修改?
initializer_list<t> 访问的是:
const t
因此:
initializer_list<int> il
= {1, 2, 3};
不能:
//*il.begin() = 100;
可以把它理解为:
initializer_list 负责“描述这一组初始化元素” 而不是一个真正可修改的 vector
它本身也不会拥有类似:
capacity push_back erase
这种容器能力。
十、initializer_list 并不是 vector
两者很容易混淆。
1. vector
vector<int> v{1, 2, 3};
vector 真正拥有:
动态内存 size capacity insert erase push_back
2. initializer_list
initializer_list<int> il{1, 2, 3};
更接近一个:
只读视图 / 轻量代理
它只是帮助函数或构造函数:
接收一组数量可变的同类型初始化元素
十一、vector 为什么能使用 {1,2,3,4,5}
标准容器提供了类似:
vector(
initializer_list<value_type> il
);
这样的构造函数。
可以简化理解为:
template<class t>
class vector
{
public:
vector(initializer_list<t> il)
{
for (const auto& e : il)
{
push_back(e);
}
}
// ...
};所以:
vector<int> v{1, 2, 3};
本质过程可以理解为:
{1,2,3}
↓
initializer_list<int>
↓
vector 的 initializer_list 构造函数
↓
构造 vector
十二、list、map 等容器同样支持
例如:
list<int> lt{1, 2, 3, 4};
以及:
map<string, int> m
{
{"apple", 3},
{"banana", 5},
{"orange", 2}
};
这里实际上组合了两层列表初始化:
外层 {}
↓
初始化 map
内层 {}
↓
初始化 pair<const string,int>
十三、map 初始化过程怎么理解?
例如:
map<string, int> m
{
{"apple", 3},
{"banana", 5}
};
可以抽象理解成:
"apple", 3
↓
pair
"banana", 5
↓
pair
两个 pair
↓
initializer_list<value_type>
↓
map 构造函数
这也是 c++11 列表初始化非常方便的地方。
十四、initializer_list 也可以支持赋值
例如:
vector<int> v{1, 2, 3};
v = {10, 20, 30, 40};
对应容器提供:
operator=(initializer_list<t>);
因此不只是构造,重新赋值也可以使用 {}。
十五、自己给类增加 initializer_list 构造
例如设计一个简单容器:
#include <initializer_list>
#include <iostream>
using namespace std;
class numbers
{
public:
numbers(initializer_list<int> il)
{
for (auto e : il)
{
cout << e << " ";
}
cout << endl;
}
};
int main()
{
numbers n{1, 2, 3, 4, 5};
return 0;
}输出:
1 2 3 4 5
也就是说:
initializer_list
并不是 stl 容器专属机制。
我们自己的类型也可以使用。
十六、一个非常重要的坑:initializer_list 构造优先级
考虑:
#include <initializer_list>
#include <iostream>
using namespace std;
class test
{
public:
test(int x, int y)
{
cout << "test(int,int)" << endl;
}
test(initializer_list<int> il)
{
cout << "initializer_list" << endl;
}
};执行:
test t1(1, 2);
会更自然地匹配:
test(int, int)
但是:
test t2{1, 2};
如果 initializer_list 构造函数可匹配,列表初始化的重载决议会非常偏向:
initializer_list 构造函数
所以:
{}
和
()
不能机械地认为完全等价。
十七、vector 中最经典的例子
观察:
vector<int> v1(5, 1);
它表示:
5 个元素 每个元素都是 1
即:
1 1 1 1 1
而:
vector<int> v2{5, 1};
表示:
两个元素: 5 1
所以:
()
和
{}
在这里具有完全不同的语义。
十八、auto 和列表初始化
c++11 还经常看到:
auto il = {10, 20, 30};
这里 il 会被推导成类似:
initializer_list<int>
因此可以:
for (auto e : il)
{
cout << e << endl;
}
但是不要因此形成:
所有使用 {} 的 auto 都一定推导为 initializer_list
这种过度简化。
实际推导规则与具体语法形式有关。
十九、列表初始化在函数调用中的应用
假设:
class date
{
public:
date(int year,
int month,
int day)
{}
};
void printdate(const date& d)
{
}调用可以直接:
printdate({2025, 1, 1});
编译器利用:
{2025,1,1}
构造 date 临时对象,再绑定到参数。
二十、在容器插入中的应用
例如:
vector<date> dates;
dates.push_back(
{2025, 1, 1}
);相比:
date d(2025, 1, 1); dates.push_back(d);
前者更加紧凑。
后面学习移动语义和:
emplace_back
之后,还会继续解决:
如何减少临时对象和拷贝
的问题。
二十一、列表初始化真正统一了什么?
可以把 c++11 之前的:
数组初始化 对象构造 结构体初始化 容器初始化 临时对象构造
统一成非常相似的:
type obj{...};
例如:
int x{10};
point p{1, 2};
date d{2025, 1, 1};
vector<int> v{1, 2, 3};
map<string, int> m
{
{"apple", 1},
{"banana", 2}
};代码在形式上更加一致。
二十二、常见错误
1. 认为 {} 与 () 完全一样
错误。
vector<int> v1(5, 1);
vector<int> v2{5, 1};
语义完全不同。
2. 认为 initializer_list 是 vector
不是。
它没有:
push_back erase capacity
等能力。
3. 认为 initializer_list 中元素可以修改
元素视为:
const t
不能直接修改。
4. 忽略窄化转换
例如:
double d = 3.14;
// int x{d};列表初始化会阻止这种潜在信息损失。
5. 认为 initializer_list 的底层布局是标准固定的
不要依赖类似:
“一定两个指针” “一定在栈上”
这样的实现细节。
程序应该只使用:
begin() end() size()
等标准接口。
总结
c++11 列表初始化解决的核心问题是:
让不同类型拥有更加统一的初始化语法
最重要的语法:
type obj{args...};
而:
std::initializer_list<t>
则让函数和类能够方便地接收:
{x1,x2,x3,...}
这一组同类型初始化元素。
需要重点记住四件事情:
1. {} 可以初始化内置类型和自定义类型
2. 列表初始化会阻止很多窄化转换
3. stl 容器通过 initializer_list 支持
vector<int>{1,2,3}
4. 如果类存在 initializer_list 构造函数,
{} 与 () 的构造函数选择可能不同理解这一部分以后,c++11 后面的:
右值引用 移动构造 emplace
会逐渐把“对象怎么构造、立即掌握c++11列表初始化与initializer_list的核心用法,避开窄化转换陷阱和构造优先级坑,学会用{}统一初始化内置类型、自定义类和stl容器,提升代码安全性与可读性,从根源杜绝传统语法的隐蔽错误怎么传递”这件事继续向前推进。
到此这篇关于c++11统一初始化之列表初始化与 initializer_list的文章就介绍到这了,更多相关c++列表初始化与 initializer_list内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论