C++——列表初始化

C++ 列表初始化:{} 到底做了什么?

C++11 引入了统一的列表初始化语法:

cpp 复制代码
int x{10};
vector<int> v{1, 2, 3};

相比传统的 (){} 不只是换了一种写法,它还有两个非常重要的特点:

  • 可以配合 std::initializer_list
  • 能够检查并禁止窄化转换

下面只掌握这几个核心概念。


一、{} 可以初始化什么?

最简单的例子:

cpp 复制代码
int x{10};
int arr[3]{1, 2, 3};

也可以初始化结构体:

cpp 复制代码
struct MyStruct
{
    int a;
    char b;
    string c;
};

MyStruct s{1, 'a', "hello"};

这里并不是编译器偷偷生成了一个:

cpp 复制代码
MyStruct(int, char, string);

而是因为 MyStruct 满足聚合类型的条件,所以 {} 可以按照成员顺序直接初始化它:

text 复制代码
a ← 1
b ← 'a'
c ← "hello"

所以要记住:

聚合初始化不是调用一个"编译器生成的多参数构造函数"。


二、{} 初始化类对象时,会调用构造函数

如果一个类型不是通过聚合初始化,而是通过构造函数初始化,那么 {} 会参与构造函数的匹配。

例如:

cpp 复制代码
class MyClass
{
public:
    MyClass(int x) {}
    MyClass(int x, char c) {}
};

那么:

cpp 复制代码
MyClass a{10};

匹配:

cpp 复制代码
MyClass(int);

而:

cpp 复制代码
MyClass b{10, 'a'};

匹配:

cpp 复制代码
MyClass(int, char);

所以:

{} 本身不是构造函数,它只是列表初始化语法;对于类类型,编译器会根据初始化规则寻找合适的构造函数。


三、initializer_list{} 的一个重要用途

看这个例子:

cpp 复制代码
vector<int> v{1, 2, 3};

这里的:

cpp 复制代码
{1, 2, 3}

可以用于初始化一个:

cpp 复制代码
initializer_list<int>

因此 vector 可以通过 initializer_list 构造:

cpp 复制代码
vector<int> v{1, 2, 3};

最终得到:

text 复制代码
v = [1, 2, 3]

需要注意:

{1, 2, 3} 本身不是一个普通的 initializer_list 对象,而是一个 braced-init-list(花括号初始化列表)

当上下文需要时,它可以用于构造 initializer_list


四、为什么 vector<int> v(10, 20)vector<int> v{10, 20} 不一样?

这是 {} 最经典的坑。

使用 ()

cpp 复制代码
vector<int> v(10, 20);

这里匹配的是类似:

cpp 复制代码
vector(size, value);

意思是:

创建 10 个元素,每个元素都是 20。

所以:

text 复制代码
20 20 20 20 20 20 20 20 20 20

使用 {}

cpp 复制代码
vector<int> v{10, 20};

如果 vector 有可用的 initializer_list 构造函数,那么列表初始化会优先考虑它。

于是这里表示:

用两个元素 10 和 20 初始化 vector。

结果:

text 复制代码
10 20

所以一定要记住:

cpp 复制代码
vector<int> v(10, 20);  // 10个20
vector<int> v{10, 20};  // 两个元素:10、20

五、{} 的另一个重要特性:禁止窄化转换

传统初始化:

cpp 复制代码
int x = 3.14;

通常可以编译,只是会发生:

text 复制代码
3.14 → 3

但是:

cpp 复制代码
int x{3.14};

会直接报错。

因为列表初始化会检查窄化转换。

例如:

cpp 复制代码
int x{3.14};   // ❌
char c{1000};  // ❌

因此可以把 {} 理解成:

初始化的时候更加严格,发现可能丢失信息的转换就拒绝。

这也是为什么现代 C++ 中很多时候推荐:

cpp 复制代码
int x{10};

而不是:

cpp 复制代码
int x = 10;

六、explicit{} 的关系

例如:

cpp 复制代码
class MyClass
{
public:
    explicit MyClass(int x) {}
};

因为构造函数是 explicit

cpp 复制代码
MyClass a{10};    // ✅
MyClass b(10);    // ✅
MyClass c = 10;   // ❌

这里可以简单理解成:

  • MyClass a{10}:直接初始化,可以使用 explicit 构造函数
  • MyClass b(10):直接初始化,可以使用 explicit 构造函数
  • MyClass c = 10:需要隐式转换,不能使用 explicit 构造函数

所以:

explicit 主要控制"能不能进行隐式转换",而不是禁止构造函数本身被调用。


七、最后总结

把整篇文章压缩成下面这张表就够了:

写法 核心含义
int x{10}; 列表初始化
int arr[]{1,2,3}; 初始化数组
MyStruct s{1,'a',"hi"}; 聚合初始化
MyClass a{10}; 根据构造函数进行列表初始化
vector<int> v{1,2,3}; 通常使用 initializer_list
vector<int> v(10,20); 构造函数参数为 10,20
vector<int> v{10,20}; 通常表示两个元素 10,20
int x{3.14}; ❌ 禁止窄化转换
explicit MyClass(int); 禁止依靠该构造函数进行隐式转换

因此,学习 {} 时真正需要抓住的只有三件事:

{} 可以进行列表初始化

{} 会和 initializer_list 产生重要联系

{} 会阻止很多窄化转换

这三个点搞明白,C++ 的列表初始化基本就掌握了。

相关推荐
blueSatchel2 小时前
c++action 编写(ros-humble)
c++·ros2
老赵的博客3 小时前
c++面试之从虚函数表到Rtti一次性讲清楚
c++·qt
傲世仙尊4 小时前
System V 进程间通信详解:共享内存、消息队列与信号量(CSDN博客)
linux·开发语言·c++
cvby5 小时前
C++11
开发语言·c++
6Hzlia5 小时前
【Classic 150 刷题计划】 LeetCode 26. 删除有序数组中的重复项 | C++ 快慢双指针经典模板
c++·算法·leetcode
夜不会漫长6 小时前
C++:类和对象(2)
java·javascript·c++
无小道6 小时前
C++ 中 `explicit` 关键字详解:彻底理解隐式类型转换
c++·explicit
蒸蒸yyyyzwd6 小时前
cpp 选手秋招学习笔记 day34
c++·八股
Tairitsu_H6 小时前
[C++] C++11类和STL的新特性
开发语言·c++·stl·c++11