C++11列表初始化 {}
文章目录
- C++11列表初始化 {}
- 一、列表初始化({})的核心特性
- 二、C++11 列表初始化核心内容(对比 C++98/03 初始化方法)
- 1. 基本类型与数组初始化
- 2. 结构体与类初始化
- 3. 容器初始化(STL)
- 3.1 序列式容器(`vector`、`list`、`deque`等)
- 3.2 关联式容器(`map`、`set`、`unordered_map`等)
- 3.3 嵌套容器初始化
- 3.4 容器赋值操作
- 4. 禁止窄化转换
- 4.1 什么是“窄化转换”?
- 4.2 列表初始化列表初始化如何禁止窄化转换?
- 4.3 具体示例:禁止窄化转换的场景
- (1) 浮点数 → 整数(丢失小数)
- (2)大整数 → 小整数(可能溢出)
- (3)高精度浮点数 → 低精度浮点数(丢失精度)
- (4)例外:安全的转换(非窄化)允许通过
- 5. `std::initializer_list` -- 初始化列表构造函数
- 5.1 核心功能与设计目的
- 5.2 基本特性
- 5.3 使用场景
- (1) 作为函数参数
- (2) 作为类的构造函数参数
- (3) STL容器中的应用
- (4)作为返回值
- 5.4关键注意事项
C++11列表初始化 {}
C++11 引入的列表初始化(List Initialization) 是一种统一的初始化方式,支持使用 {}
对变量、对象、容器等进行初始化,解决了 C++98/03 中初始化语法混乱的问题。其核心特点是兼容多种场景、禁止窄化转换(Narrowing Conversion),并简化了初始化代码。
一、列表初始化({})的核心特性
- 统一语法:可用于几乎所有类型的初始化(基本类型、数组、结构体、类、容器等)。
- 禁止窄化转换:不允许可能丢失精度的类型转换(如
int
转float
、大整数转小整数),编译时会报错。 - 支持初始化列表构造:类可通过
std::initializer_list
接收{}
中的元素,实现批量初始化(如 STL 容器)。
二、C++11 列表初始化核心内容(对比 C++98/03 初始化方法)
1. 基本类型与数组初始化
C++98/03 方式:
// 基本类型
int a = 10;
double b(3.14);// 数组
int arr1[3] = {1, 2, 3}; // 只能在定义时初始化,且不能省略大小(部分场景除外)
int arr2[] = {4, 5, 6}; // 需依赖初始化元素数量推导大小
C++11 列表初始化:
// 基本类型:更简洁,无需等号或括号
int a{10}; // 等价于 int a = 10;
double b{3.14}; // 等价于 double b(3.14);// 数组:支持省略等号,且语法更统一
int arr1[3]{1, 2, 3};
int arr2[]{4, 5, 6}; // 同样支持推导大小
2. 结构体与类初始化
C++98/03 方式:
C++98 中,有自定义构造函数的类无法用
{}
初始化,必须显式调用构造函数;需依赖默认构造函数或带参构造函数,结构体可直接用
{}
但类需显式调用构 造函数。
struct Point {int x;int y;
};class Student {
private:std::string name;int age;
public:Student(std::string n, int a) : name(n), age(a) {}
};// 初始化
Point p1 = {1, 2}; // 结构体可直接用{}(聚合类型)
Student s1("Tom", 18); // 类必须调用构造函数
C++11 列表初始化:
C++11 允许用
{}
直接匹配构造函数。结构体和类均可直接用
{}
初始化,类会自动匹配对应构造函数。
Point p1{1, 2}; // 无需等号,更简洁
Student s1{"Tom", 18}; // 类可直接用{}匹配构造函数,等价于 Student s1("Tom", 18);
3. 容器初始化(STL)
3.1 序列式容器(vector
、list
、deque
等)
序列式容器存储单一类型的元素,初始化时直接在{}
中列出元素即可。
C++98方式(繁琐):
#include <vector>
#include <list>// 初始化vector
std::vector<int> vec;
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);// 初始化list
std::list<std::string> lst;
lst.push_back("a");
lst.push_back("b");
C++11列表初始化(简洁):
#include <vector>
#include <list>// 直接用{}传入元素,自动匹配initializer_list构造函数
std::vector<int> vec{1, 2, 3, 4}; // 初始化包含4个int的vector
std::list<std::string> lst{"a", "b", "c"}; // 初始化包含3个string的list
std::deque<double> dq{3.14, 2.718, 1.618}; // 双端队列
3.2 关联式容器(map
、set
、unordered_map
等)
关联式容器存储键值对(map
)或单一键(set
),初始化时需按容器元素类型传入{}
中的元素。
map
和unordered_map
(键值对):
键值对用{key, value}
表示,多个键值对用逗号分隔。
#include <map>
#include <unordered_map>// 初始化map(有序键值对)
std::map<std::string, int> score{{"Alice", 90}, {"Bob", 85}, {"Charlie", 95}
};// 初始化unordered_map(无序键值对)
std::unordered_map<int, std::string> dict{{1, "one"}, {2, "two"}, {3, "three"}
};
set
和unordered_set
(单一键):
直接传入键值即可。
#include <set>
#include <unordered_set>// 初始化set(有序集合)
std::set<int> s{3, 1, 4, 1, 5}; // 自动去重并排序,结果为{1,3,4,5}// 初始化unordered_set(无序集合)
std::unordered_set<std::string> us{"apple", "banana", "cherry"};
3.3 嵌套容器初始化
列表初始化支持嵌套,可直接初始化多维容器(如vector<vector<int>>
)。
#include <vector>// 初始化二维vector(矩阵)
std::vector<std::vector<int>> matrix{{1, 2, 3},{4, 5, 6},{7, 8, 9}
};// 初始化vector of map
std::vector<std::map<int, std::string>> vec_of_map{{{1, "a"}, {2, "b"}},{{3, "c"}, {4, "d"}}
};
3.4 容器赋值操作
除了初始化,std::initializer_list
还支持对已存在的容器进行赋值。
#include <vector>
#include <set>std::vector<int> vec{1, 2, 3};
vec = {4, 5, 6}; // 赋值新的元素列表,覆盖原有内容std::set<std::string> s;
s = {"x", "y", "z"}; // 赋值后s包含{"x", "y", "z"}
4. 禁止窄化转换
在 C++11 引入的列表初始化({}
)中,禁止窄化转换(Narrowing Conversion) 是一项重要的安全性特性。它的核心作用是:防止可能导致数据精度丢失或溢出的隐式类型转换,在编译阶段就拦截这类风险,避免运行时错误。
4.1 什么是“窄化转换”?
窄化转换指的是将一种类型的值转换为另一种类型时,可能导致数据信息丢失或溢出的转换。常见场景包括:
- 浮点数转整数:如
double
转int
(丢失小数部分)。 - 大类型转小类型:如
long long
转int
(可能超出范围)。 - 高精度转低精度:如
long double
转float
(丢失精度)。
例如:
int a = 3.14; // 3.14 → 3(丢失小数部分,属于窄化转换)
short b = 100000; // 100000 超出 short 最大范围(32767),溢出(窄化转换)
float c = 123456789.123; // 超出 float 精度范围(丢失部分小数,窄化转换)
4.2 列表初始化列表初始化如何禁止窄化转换?
C++11 规定:使用 {}
进行列表初始化时,编译器必须检查并禁止所有窄化转换,若存在则直接编译报错。
与之对比:C++98 中使用 =
或括号的传统初始化允许窄化转换(仅可能触发警告,不会报错)。
4.3 具体示例:禁止窄化转换的场景
(1) 浮点数 → 整数(丢失小数)
// 传统初始化:允许(仅警告)
int a = 3.14; // 编译通过(a=3)
int b(2.718); // 编译通过(b=2)// 列表初始化:禁止(编译报错)
int c{3.14}; // 错误:double → int 丢失小数(窄化转换)
int d{2.718}; // 错误:窄化转换
(2)大整数 → 小整数(可能溢出)
// 传统初始化:允许(可能溢出但不报错)
short s1 = 100000; // 100000 超出 short 范围(32767),编译通过(结果不确定)
char c1 = 300; // 300 超出 char 范围(-128~127),编译通过(溢出)// 列表初始化:禁止(编译报错)
short s2{100000}; // 错误:整数超出 short 范围(窄化转换)
char c2{300}; // 错误:整数超出 char 范围(窄化转换)
(3)高精度浮点数 → 低精度浮点数(丢失精度)
// 传统初始化:允许(丢失精度但不报错)
float f1 = 123456789.123456789; // float 精度有限,编译通过(值被截断)// 列表初始化:禁止(编译报错)
float f2{123456789.123456789}; // 错误:long double → float 丢失精度(窄化转换)
(4)例外:安全的转换(非窄化)允许通过
列表初始化并非禁止所有转换,仅禁止可能丢失信息的转换。以下安全转换允许通过:
- 整数 → 更大的整数(如
int
→long long
)。 - 整数 → 浮点数(如
int
→double
,浮点数精度足够容纳整数)。 - 低精度浮点数 → 高精度浮点数(如
float
→double
)。
示例:
// 安全转换:允许通过
int a{100};
long long b{a}; // int → long long(范围更大,非窄化)
double c{100}; // int → double(精度足够,非窄化)
double d{3.14f}; // float → double(精度更高,非窄化)
5. std::initializer_list
– 初始化列表构造函数
std::initializer_list
是 C++11 引入的一个轻量级模板类(定义在 <initializer_list>
头文件中),专门用于处理列表初始化(即用 {}
包裹的元素列表)。它为编译器提供了一种统一的方式来处理初始化列表,是 C++11 中列表初始化语法(如容器初始化、变量初始化)的底层实现基础。
5.1 核心功能与设计目的
std::initializer_list
的主要作用是:
- 封装初始化列表:将
{a, b, c}
这样的列表元素转换为一个临时的容器对象,供其他代码(如构造函数、函数参数)使用。 - 支持统一初始化:让自定义类型(如类、容器)能够像原生数组一样使用
{}
语法初始化。 - 简化代码:避免为不同数量的初始化参数编写多个重载函数/构造函数。
5.2 基本特性
- 模板类型:
std::initializer_list<T>
是模板类,其中T
是列表中元素的类型(如int
、std::string
)。 - 轻量级:内部仅存储两个指针(或一个指针+长度),指向列表元素的起始和结束位置,不负责内存管理(元素存储在栈上的临时区域)。
- 只读访问:元素是常量,不允许修改(
std::initializer_list<T>
本质是const T*
的封装)。 - 临时对象:
std::initializer_list
对象的生命周期与初始化列表相同,通常是表达式结束时销毁。
5.3 使用场景
(1) 作为函数参数
函数可以接收 std::initializer_list<T>
类型的参数,从而支持用 {}
列表传参。
#include <initializer_list>
#include <iostream>// 接收initializer_list的函数
void print(const std::initializer_list<int>& list) {for (auto val : list) { // 遍历列表元素(只读)std::cout << val << " ";}std::cout << std::endl;
}int main() {print({1, 2, 3, 4}); // 直接用{}传参,自动转换为initializer_listprint({10, 20});return 0;
}
输出:
1 2 3 4
10 20
(2) 作为类的构造函数参数
类可以定义接收 std::initializer_list<T>
的构造函数,从而支持用 {}
初始化对象(这也是 STL 容器支持列表初始化的原理)。
#include <initializer_list>
#include <vector>class MyContainer {
private:std::vector<int> data;
public:// 接收initializer_list的构造函数MyContainer(std::initializer_list<int> list) {// 将列表元素添加到容器中for (auto val : list) {data.push_back(val);}}void print() {for (auto val : data) {std::cout << val << " ";}std::cout << std::endl;}
};int main() {MyContainer c{1, 2, 3, 4}; // 用{}初始化,自动匹配initializer_list构造函数c.print(); // 输出:1 2 3 4 return 0;
}
(3) STL容器中的应用
所有 STL 容器(如 vector
、map
、set
等)在 C++11 中都新增了接收 std::initializer_list
的构造函数,因此可以直接用 {}
初始化:
#include <vector>
#include <map>int main() {// vector用initializer_list初始化std::vector<int> vec{1, 2, 3};// map用initializer_list初始化(元素是键值对)std::map<std::string, int> score{{"Alice", 90}, {"Bob", 85}};return 0;
}
(4)作为返回值
函数可以返回 std::initializer_list<T>
类型,允许用 {}
直接返回元素列表。
#include <initializer_list>
#include <iostream>std::initializer_list<int> get_numbers() {return {1, 2, 3, 4}; // 返回initializer_list
}int main() {for (auto num : get_numbers()) {std::cout << num << " "; // 输出:1 2 3 4 }return 0;
}
5.4关键注意事项
-
元素的生命周期
std::initializer_list
中的元素存储在栈上的临时内存中,其生命周期与std::initializer_list
对象一致。不能保存指向这些元素的指针或引用,否则会导致悬垂指针:#include <initializer_list>int main() {std::initializer_list<int> list = {1, 2, 3};const int* p = list.begin(); // 指向临时元素list = {4, 5, 6}; // 原临时元素已销毁,p变为悬垂指针return 0; }
-
元素是常量
std::initializer_list<T>
中的元素是const T
类型,不允许修改:#include <initializer_list>int main() {std::initializer_list<int> list = {1, 2, 3};// list[0] = 10; // 错误:元素是常量,不允许修改return 0; }
-
与其他构造函数的优先级
当类同时有接收std::initializer_list
的构造函数和其他构造函数时,列表初始化({}
)会优先匹配initializer_list
构造函数:#include <initializer_list> #include <iostream>class MyClass { public:MyClass(int a, int b) {std::cout << "调用(int, int)构造函数" << std::endl;}MyClass(std::initializer_list<int> list) {std::cout << "调用initializer_list构造函数" << std::endl;} };int main() {MyClass obj1(1, 2); // 调用(int, int)构造函数MyClass obj2{1, 2}; // 优先调用initializer_list构造函数return 0; }