C++11

一.列表初始化
在 C++11 标准中,列表初始化(Uniform Initialization)作为一种全新的初始化方式,为 C++ 编程带来了显著的变革。它不仅让代码更加简洁易读,还提升了语言的表达能力和灵活性。
一、列表初始化简介
列表初始化是一种使用花括号 {}
来初始化对象的语法形式,其基本形式如下:
Type obj = {value1, value2, ...};
或者
Type obj{value1, value2, ...};
这种方式可以用于初始化各种类型的对象,包括内置类型、类类型、数组等。它旨在提供一种统一的初始化语法,以解决传统初始化方式中存在的问题,如初始化方式不一致、类型安全问题等。
(一)初始化内置类型
对于内置类型,如 int
、float
等,列表初始化会进行值初始化。例如:
int a = {5}; // 等价于 int a(5);
float b{3.14f}; // 等价于 float b(3.14f);
如果初始化列表中包含多个值,编译器会报错,因为内置类型只能接受一个初始化值。
(二)类对象
对于类对象,列表初始化可以直接初始化类的成员变量。例如:
class Person {
public:
std::string name;
int age;
};
Person p{"zzh", 18}; // 使用列表初始化
在上述代码中,Person
类有两个成员变量 name
和 age
,我们使用列表初始化的方式直接初始化了这两个成员变量,这种方式非常直观和方便。
(三)初始化数组
列表初始化可以方便地初始化数组,包括静态数组和动态数组。例如:
int arr[] = {1, 2, 3, 4}; // 静态数组
int* dynArr = new int[4]{1, 2, 3, 4}; // 动态数组
如果初始化列表中的元素个数少于数组的大小,剩余的元素会被初始化为零。
(四)初始化标准库容器
列表初始化可以用于初始化标准库容器,如 std::vector
、std::map
等。例如:
std::vector<int> vec{1, 2, 3, 4};
std::map<int, std::string> map{{1, "one"}, {2, "two"}};
这种方式不仅简洁,还避免了逐个插入元素的繁琐操作。
二、列表初始化的规则
(一)直接初始化与列表初始化
列表初始化和直接初始化在某些情况下可能会产生不同的结果。例如:
struct A {
A(int) {}
};
struct B {
B(std::initializer_list<int>) {}
};
A a1(1); // 直接初始化,调用 A(int)
A a2{1}; // 列表初始化,报错,因为 A 没有接受 initializer_list 的构造函数
B b1(1); // 报错,因为 B 没有接受 int 的构造函数
B b2{1}; // 列表初始化,调用 B(std::initializer_list<int>)
在使用列表初始化时,编译器会优先查找接受 initializer_list
的构造函数。如果没有找到,会尝试匹配其他构造函数。
(二)列表初始化的限制
- 内置类型:内置类型只能接受一个初始化值。如果初始化列表中包含多个值,编译器会报错。
- 类类型:如果类定义了接受
initializer_list
的构造函数,会优先调用该构造函数。如果没有找到合适的构造函数,编译器会报错。 - 数组:如果初始化列表中的元素个数少于数组的大小,剩余的元素会被初始化为零。
(三)空列表初始化
空列表初始化是指使用空的花括号 {}
来初始化变量或对象。对于基本数据类型,空列表初始化会将变量初始化为零值。例如:
int a{}; // a 的值为 0
double b{}; // b 的值为 0.0
对于类对象,空列表初始化会调用默认构造函数。如果类没有默认构造函数,则会报错。例如:
class Test {
public:
Test() { std::cout << "Default constructor called" << std::endl; }
};
Test t{}; // 调用默认构造函数
在上述代码中,Test
类有一个默认构造函数,因此空列表初始化会调用该默认构造函数。
二.initializer_list
C++11库中提出了⼀个std::initializer_list的类,这个类的本质是底层开⼀个数组,将数据拷⻉过来,std::initializer_list内部有两个指针分别指向数组的开始和结束。
initializer_list基本介绍
std::initializer_list
是一个模板类,用于表示初始化列表。它提供了一种方便的方式来处理一组同类型的数据。initializer_list
的对象可以通过花括号 {}
来创建,例如:
std::initializer_list<int> list = {1, 2, 3, 4};
或者直接在函数参数中使用:
void printList(std::initializer_list<int> list) {
for (auto it : list) {
std::cout << it << " ";
}
}
printList({1, 2, 3, 4});
(一)特性
- 只读性:
initializer_list
中的元素是只读的,不能被修改。这是因为initializer_list
是一个轻量级的包装器,它并不真正拥有数据,而是指向原始的初始化列表。 - 迭代器支持:
initializer_list
提供了迭代器支持,可以通过迭代器来访问其中的元素。例如:
std::initializer_list<int> list = {1, 2, 3, 4};
for (auto it = list.begin(); it != list.end(); ++it) {
std::cout << *it << " ";
}
- 大小查询:可以通过
size()
方法来获取initializer_list
中的元素个数。例如:
std::initializer_list<int> list = {1, 2, 3, 4};
std::cout << "Size: " << list.size() << std::endl;
(二)应用
- 构造函数初始化:类可以通过接受
initializer_list
参数的构造函数来初始化其成员变量。例如:
class Vector {
private:
std::vector<int> data;
public:
Vector(std::initializer_list<int> list) : data(list) {}
};
Vector vec{1, 2, 3, 4}; // 初始化 Vector 对象
容器⽀持⼀个std::initializer_list的构造函数,也就⽀持任意多个值构成{x1,x2,x3…} 进⾏初始化。STL中的容器⽀持任意多个值构成的 {x1,x2,x3…} 进⾏初始化,就是通过std::initializer_list的构造函数⽀持的。
- 函数参数:函数可以接受
initializer_list
参数,从而方便地处理一组数据。例如:
void printList(std::initializer_list<int> list) {
for (auto it : list) {
std::cout << it << " ";
}
}
printList({1, 2, 3, 4});
(三)initializer_list
的生命周期
initializer_list
的生命周期与初始化列表的生命周期相同,即在初始化列表的作用域结束后,initializer_list
也会失效。因此,不能将 initializer_list
的引用存储起来,否则会导致未定义行为。
三、列表初始化与 initializer_list 的优势
(一)代码简洁性
列表初始化和 initializer_list
的使用让代码更加简洁易读。例如,初始化一个数组或类对象时,不再需要逐个赋值,而是可以直接使用花括号 {}
来完成初始化。
(二)类型安全
列表初始化会进行严格的类型检查,避免了类型不匹配的问题。例如,如果初始化列表中的值类型与目标类型不匹配,编译器会报错。
(三)灵活性
initializer_list
提供了灵活的方式来处理一组数据,可以通过迭代器、大小查询等方式来操作数据。