在 C++98 时代,花括号 {} 初始化的使用场景非常受限,只能用于数组、简单结构体,自定义对象、容器初始化写法繁琐又不统一。
C++11 推出的「统一列表初始化 」 和 std::initializer_list彻底解决了这个问题,让所有类型的初始化语法高度统一,代码更简洁、更易读。 本篇博客会从基础用法到原理,再到实战模拟实现,带你彻底掌握这个 C++11 高频特性
一、C++98 的 {} 初始化:受限的用法
在 C++98 中,{} 初始化只能用于内置数组 和无构造函数的简单结构体,自定义类、动态数组、STL 容器都无法使用,语法非常不统一。
示例代码:
cpp
struct Point
{
int _x;
int _y;
};
int main()
{
// 支持:数组初始化
int array1[] = { 1, 2, 3, 4, 5 };
int array2[5] = { 0 };
// 支持:简单结构体初始化
Point p = { 1, 2 };
// 不支持:自定义类、容器、new动态数组
// Date d{2024, 1, 1}; 报错
// vector<int> v{1,2,3}; 报错
return 0;
}
痛点总结:
- 初始化语法不统一,数组 / 结构体 / 类各写各的
- 自定义类型、STL 容器无法使用
{}初始化 - 代码冗余,可读性差
二、C++11 统一列表初始化:{} 全能使用
C++11 直接扩大了{}的使用范围,所有类型(内置类型、自定义类型、动态数组、对象)都可以用{}初始化 ,并且可以省略=,语法完全统一。
1. 内置类型 & 数组
cpp
int main()
{
// 内置变量
int x1 = 1;
int x2{ 2 }; // C++11 写法,省略=
int x3 = { 3 }; // 带=也支持
// 静态数组
int array1[]{ 1, 2, 3, 4, 5 };
int array2[5]{ 0 };
// 动态数组 new 表达式(C++98不支持)
int* pa = new int[4] { 1, 2, 3, 4 };
return 0;
}
2. 自定义结构体 / 类:调用构造函数
只要类有对应的构造函数,直接用{}传参即可,编译器会自动匹配构造函数:
cpp
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
{
cout << "Date构造函数调用" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2022, 1, 1); // C++98 老式写法
Date d2{ 2022, 1, 2 }; // C++11 列表初始化
Date d3 = { 2022, 1, 3 }; // 带=也支持
return 0;
}
✅ 效果:三种写法都能正常调用构造函数,语法更统一、更直观。
三、std::initializer_list:{} 初始化的底层支撑
{1,2,3,4} 这种花括号列表,在 C++11 中会被自动推导为 std::initializer_list 类型,它是一个轻量级的容器,用来存储临时列表数据。
1. 查看类型
cpp
int main()
{
auto il = { 10, 20, 30 };
cout << typeid(il).name() << endl; // 输出:class std::initializer_list<int>
return 0;
}
2. 核心特性
- 是一个轻量级只读容器,只存储数据的引用 / 指针,不深拷贝
- 支持
begin()、end()、size()接口 - 可使用范围 for 遍历
- 主要用途:作为构造函数 / 赋值运算符重载的参数 ,实现
{}初始化和赋值
3. STL 容器的天然支持
C++11 给所有 STL 容器(vector、list、map、set 等)都增加了:
initializer_list构造函数initializer_list赋值运算符重载
所以我们可以直接这样写:
cpp
#include <vector>
#include <list>
#include <map>
using namespace std;
int main()
{
// 容器列表初始化
vector<int> v = { 1,2,3,4 };
list<int> lt = { 1,2 };
map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };
// 容器列表赋值
v = {10, 20, 30};
return 0;
}
这就是 C++11 最常用、最便捷的容器初始化方式。
四、实战:模拟实现 vector,支持 {} 初始化和赋值
我们自己模拟实现的 vector,只要添加两个函数:
-
vector(initializer_list<T> l):列表初始化构造 -
operator=(initializer_list<T> l):列表赋值
就能和 STL vector 一样使用{}初始化和赋值,完整代码如下:
cpp
#include <iostream>
#include <initializer_list> // 必须包含头文件
#include <algorithm>
using namespace std;
namespace biter
{
template<class T>
class vector
{
public:
typedef T* iterator;
// 1. 无参构造
vector()
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{}
// 2. 列表初始化构造函数(核心)
vector(initializer_list<T> l)
{
// 开辟空间
_start = new T[l.size()];
_finish = _start + l.size();
_endofstorage = _start + l.size();
// 遍历initializer_list,拷贝数据
iterator vit = _start;
// 注意:必须加typename,否则编译器无法识别iterator是类型
typename initializer_list<T>::iterator lit = l.begin();
while (lit != l.end())
{
*vit++ = *lit++;
}
// 简化写法:范围for
// for (auto e : l)
// *vit++ = e;
}
// 3. 列表赋值运算符重载(核心)
vector<T>& operator=(initializer_list<T> l)
{
// 复用构造函数创建临时对象,交换指针
vector<T> tmp(l);
std::swap(_start, tmp._start);
std::swap(_finish, tmp._finish);
std::swap(_endofstorage, tmp._endofstorage);
return *this;
}
// 打印函数,方便测试
void print() const
{
for (auto it = _start; it != _finish; ++it)
{
cout << *it << " ";
}
cout << endl;
}
// 析构函数
~vector()
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
private:
iterator _start; // 起始位置
iterator _finish; // 有效数据结尾
iterator _endofstorage;// 容量结尾
};
}
// 测试代码
int main()
{
// {} 初始化
biter::vector<int> v1 = { 1,2,3,4,5 };
cout << "v1:";
v1.print();
// {} 直接初始化
biter::vector<int> v2{ 10,20,30 };
cout << "v2:";
v2.print();
// {} 赋值
v1 = { 100,200,300 };
cout << "v1赋值后:";
v1.print();
return 0;
}
关键知识点说明:
必须包含 <initializer_list> 头文件
遍历 initializer_list 时,迭代器前必须加 typename,因为它是依赖类型
赋值重载利用拷贝交换,代码简洁且异常安全
支持两种写法:vector v{...} 和 vector v={...}
五、统一列表初始化的优势
语法统一:所有类型(内置 / 自定义 / 容器)都能用{}初始化
代码简洁:一行完成容器初始化,告别循环 push_back
安全性更高:{}初始化会严格检查类型,禁止隐式缩窄转换
扩展性强:自定义类型只需实现initializer_list接口,即可支持统一初始化