统一初始化
统一初始化
传统圆括号初始化可能产生歧义,被编译器误解析为函数声明,而列表初始化可避免这一问题。
1 | class Foo {}; |
类似地,对于带参数的场景:
1 | class Bar { |
初始化是指为变量赋予初始值
在C++中,初始化有多种方式,包括复制初始化和直接初始化。
复制初始化(copy initialization):使用等号
=进行初始化直接初始化(direct initialization):使用圆括号
()或花括号{}进行初始化
1 | int a = 1; //(1) 复制初始化(copy initialization) |
C++ 初始化形式中的 (2) (4) 都属于列表初始化,在 C++11 中得到全面应用
初始化和赋值:初始化的等号和赋值的等号含义不同
- 初始化:为变量申请存储空间,创建新的变量。如果是类类型,将调用类的构造函数
- 赋值:把一个现有变量的值用另一个值替代,不创建新的变量。如果是类类型,将调用类的赋值运算符
operator=()
1
2
3
4
5 int a = 1; // 初始化
a = 2; // 赋值
ClassType obj1; // 初始化,调用构造函数
ClassType obj2("Hello"); // 初始化,调用构造函数
obj1 = obj2; // 赋值
- 列表初始化(list initialization):使用花括号
{}进行初始化
列表初始化的底层支持
C++11 引入的std::initializer_list<T>是列表初始化的核心机制,它本质是一个轻量级容器,用于封装同类型元素的列表。当使用{}初始化时,编译器会自动将列表元素转换为std::initializer_list<T>对象(若元素类型一致)。
类中使用
std::initializer_list:若类定义了接受std::initializer_list<T>的构造函数,列表初始化会优先调用该构造函数,即使存在其他更匹配的构造函数。这是列表初始化的特殊优先级规则。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MyClass {
public:
MyClass(int a, int b) {
std::cout << "构造函数(int, int)\n";
}
MyClass(std::initializer_list<int> list) {
std::cout << "构造函数(initializer_list)\n";
}
};
int main() {
MyClass obj1(1, 2); // 调用 (int, int)
MyClass obj2{1, 2}; // 优先调用 initializer_list(即使参数数量匹配)
MyClass obj3{1, 2, 3}; // 只能调用 initializer_list
return 0;
}std::initializer_list的特性:- 元素为
const,无法修改(如list[0] = 5;是错误的)。 - 生命周期与初始化列表绑定,超出范围后失效。
- 常用于容器初始化(如
std::vector、std::map),标准库容器均提供接受std::initializer_list的构造函数。
- 元素为
统一初始化特点
统一语法:使用花括号
{}进行初始化,适用于几乎所有类型的初始化。自动避免窄化转换:列表初始化禁止 “窄化转换”(可能导致数据丢失的转换):
- 从浮点类型到整数类型(如
double→int)。 - 从
long double→double→float(除非源是常量且值在目标范围內)。 - 从整数类型到浮点类型(除非源是常量且值在目标范围內)。
- 从大整数类型到小整数类型(如
long long→char,且值超出小类型范围)。
1
2
3
4
5long double num = 3.1415;
//int a1{ num }; 错误 C2397 从“long double”转换到“int”需要收缩转换
//int a2 = { num }; 错误 C2397 从“long double”转换到“int”需要收缩转换
int b1 = num; // 警告 C4244 “初始化” : 从“long double”转换到“int”,可能丢失数据
int b2(num); // 警告 C4244 “初始化” : 从“long double”转换到“int”,可能丢失数据- 从浮点类型到整数类型(如
适于所有类型:包括 POD(Plain Old Data,可以被直接使用
memcpy进行复制的对象)类型、聚合类型、类类型等。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23//聚合类型(指没有用户自定义的构造函数、析构函数、拷贝构造函数和赋值运算符的类,包括数组、结构体等)初始化:
struct A
{
int a;
int b;
};
A a1 = { 1,2 }; //复制初始化, C++11引入的列表初始化
A a2{ 3,4 }; //直接初始化,,比上一行初始化的方式更加通用
//类类型初始化
class MyClass {
public:
MyClass(int x, double y) : a(x), b(y) {}
int a;
double b;
};
MyClass obj1{ 5, 3.14 }; // 列表初始化
std::string str{ "Hello, C plusplus!" }; // 列表初始化
//容器和数组初始化
std::vector<int> vec{ 1,2,3,4,5 }; // 列表初始化
int arr[]{ 1,2,3,4,5 }; // 数组列表初始化聚合类型:
- C++11:聚合类型需满足:无用户定义构造函数、无私有 / 保护非静态成员、无基类、无虚函数。
- C++17:允许包含公共基类(基类也必须是聚合类型)。
- C++20:进一步放松,允许存在用户声明但未定义的构造函数(即
= default或= delete的构造函数)。
示例(C++17 及以上):
1
2
3
4struct Base { int x; };
struct Derived : Base { int y; }; // 聚合类型(C++17允许公共基类)
Derived d{ {1}, 2 }; // 正确:Base的x=1,Derived的y=2
C++17 指定初始化器(Designated Initializers)
C++17 引入了类似 C 语言的 “指定初始化器”,允许通过成员名称初始化聚合类型,顺序可与声明顺序不同。
1 | struct Point { int x; int y; }; |
注意:指定初始化器不能跳过前面的成员(如Point{.y=2}在 C++ 中错误,C 语言允许)。
构造函数与列表初始化的交互
explicit关键字用于禁止构造函数的隐式转换,但对列表初始化的影响需注意:
explicit构造函数不能用于复制初始化(如Bar b = 10;错误)。- 但可以用于直接列表初始化(如
Bar b{10};正确),因为列表初始化属于显式行为。
1 | class Bar { |



