一、列表初始化
1、C++98中{}的初始化问题
在C++98中,标准允许使用花括号{}对数组元素进行统一的列表初始值设定。比如:
int array1[] = {1,2,3,4,5};
int array2[5] = {0};
对于一些自定义的类型,却无法使用这样的初始化。比如:
vector<int> v{1,2,3,4,5};
就无法通过编译,导致每次定义vector时,都需要先把vector定义出来,然后使用循环对其赋初始值,非常不方便。
C++ 提供了多种变量初始化的方式,如等号 =、括号 ()、和大括号 {},每种方式都有其特殊的用途和限制。要选择合适的初始化方式,就需要了解它们的区别和适用情况。
2、C++ 的传统初始化方式:括号 ()
2.1、括号 () 的初始化方式和用途
在 C++ 中,括号 () 可以用于调用构造函数进行初始化,比如:
std::string s("Hello, World!");
在这个例子中,我们使用括号 () 调用了 std::string 的构造函数,用字符串字面量 “Hello, World!” 初始化了 s。这是一种直接的、明确的初始化方式。
2.2、使用括号 () 进行初始化的限制和问题
然而,C++ 的括号初始化也有其局限性。一个著名的问题是,当你试图定义一个对象,但却不小心写成了函数声明。例如,以下的 C++ 代码:
std::string s(); // Oops, 这是一个函数声明,而不是对象的定义
在这个例子中,我们可能希望定义一个 std::string 类型的对象 s,并用默认的构造函数进行初始化。然而,按照 C++ 的语法规则,这实际上是声明了一个没有参数并返回 std::string 类型的函数 s。这被称为 “最令人困惑的语法”(Most Vexing Parse)。
3、C++11列表初始化
为了解决老版本的初始化存在的问题,C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加。
3.1、内置类型的列表初始化
int main() {
// 内置类型变量
int x1 = {10};
int x2{10};
int x3 = 1+2;
int x4 = {1+2};
int x5{1+2};
// 数组
int arr1[5] {1,2,3,4,5};
int arr2[]{1,2,3,4,5};
// 动态数组,在C++98中不支持
int* arr3 = new int[5]{1,2,3,4,5};
// 标准容器
vector<int> v{1,2,3,4,5};
map<int, int> m{{1,1}, {2,2},{3,3},{4,4}};
return 0;
}
注意:列表初始化可以在{}之前使用等号,其效果与不使用=没有什么区别。
3.2、自定义类型的列表初始化
class Point
{
public:
Point(int x = 0, int y = 0):
_x(x),
_y(y)
{}
private:
int _x;
int _y;
};
int main() {
Point p{ 1, 2 };
return 0;
}
注意:如果自定义的数据类型的成员在声明时初始化过,就不能使用{}进行初始化,不然编译会报错。例如:下面的代码在编译时会报错
struct Point {
int x = 0; // 成员声明时进行初始化
int y = 0;
};
Point p{ 10, 20 }; // 错误 C2440 “初始化”: 无法从“initializer list”转换为“Point”
3.3、使用大括号 {} 进行初始化的优点
列表初始化的优点之一是它可以防止窄化转换(Narrowing Conversion)。窄化转换是指一种可能会丢失信息的类型转换,比如将浮点数转换为整数,或者将大的整数转换为小的整数。如果我们试图进行窄化转换,编译器将会报错。例如,下面的 C++ 代码试图使用浮点数初始化一个整数:
int a{3.14}; // 错误:窄化转换
这段代码将无法通过编译,因为 {3.14} 试图将一个浮点数转换为整数,这是一种窄化转换。这个特性可以帮助我们避免由于窄化转换引起的潜在错误。