C++发展历史
C++11是C++语言的第二个主要版本,也是自C++98以来最重要的一次更新。它引入了大量的新特性,标准化了已有的实践,并极大地改进了C++程序员可用的抽象能力。在2011年8月12日被ISO正式采纳之前,人们一直使用“C++0x”这个名称,因为它原本预计会在2010年之前发布。然而,由于各种原因,直到2011年才最终确定。C++03与C++11之间间隔了8年,这是C++版本发布史上最长的一次。从那时起,C++社区每三年发布一次新标准,保持了更加稳定的更新节奏。
列表初始化
C++11引入了列表初始化(List Initialization),试图统一所有对象的初始化方式,使代码更加简洁和安全。然而,这也带来了一些细节和概念上的区别,可能会引起混淆。该章节将结合具体代码,深入讲解C++11中的列表初始化,与C++98进行对比,更清晰地理解这些概念。
C++98中的初始化方式
在C++98中,数组和聚合类型(如结构体)可以使用大括号{}
进行初始化,但基本类型和自定义类对象通常不能直接使用{}
初始化,需要使用构造函数或赋值操作。
数组和结构体的初始化
struct Point {
int _x;
int _y;
};
int main() {
// 数组初始化
int a1[] = {1, 2, 3, 4, 5};
int a2[5] = {0}; // 所有元素初始化为0
// 结构体初始化
Point p = {1, 2};
return 0;
}
上述代码中,数组a1
和a2
,结构体p
都使用{}
进行初始化,这是C++98所支持的。
基本类型和自定义类的初始化
在C++98中,基本类型的初始化不能使用{}
,需要使用赋值或构造函数。
int x = 2; // 赋值初始化
对于自定义类对象,需要定义构造函数,然后使用括号()
进行初始化。
class Date {
public:
Date(int year = 1, int month = 1, int day = 1)
: _year(year), _month(month), _day(day) {}
private:
int _year;
int _month;
int _day;
};
int main() {
Date d(2025, 1, 1); // 使用构造函数初始化
return 0;
}
C++11中的列表初始化
C++11引入了列表初始化,使得几乎所有类型的对象都可以使用{}
进行初始化,包括基本类型和自定义类对象。这种统一的初始化方式带来了代码简洁性和安全性的提升。
基本类型的列表初始化
int x1 = {2}; // 列表初始化
int x2 = 2; // 传统赋值初始化
int x3{2}; // 省略等号的列表初始化
- 区别:
x1
和x3
使用了列表初始化,x2
使用了传统的赋值初始化。 - 优势:列表初始化可以防止窄化转换。例如,
int x = {2.5};
会编译错误,防止精度丢失。
自定义类型的列表初始化
Date d1 = {2025, 1, 1};
Date d20(2025, 1, 1);
const Date& d2 = {2024, 7, 25};
Date d3 = {2025};
Date d4 = 2025;
Date d6{2024, 7, 25};
const Date& d7{2024, 7, 25};
Date d1 = {2025, 1, 1};
:使用列表初始化,按照语义应该是先构造一个临时的Date
对象,然后调用拷贝构造函数复制给d1
。但是,编译器会进行优化,直接构造d1
,避免了拷贝构造。const Date& d2 = {2024, 7, 25};
:引用一个临时的Date
对象,该对象由列表初始化创建。Date d3 = {2025};
:当只有一个参数时,列表初始化也可以使用。Date d4 = 2025;
:C++98中允许的隐式类型转换,调用Date(int, int, int)
构造函数,剩余参数使用默认值。- 省略等号的初始化:
Date d6{2024, 7, 25};
和const Date& d7{2024, 7, 25};
。
结构体的列表初始化
struct Point {
int _x;
int _y;
};
int main() {
Point p1 = {1, 2}; // C++98风格
Point p2{3, 4}; // C++11,省略等号
return 0;
}
容器的列表初始化
vector<Date> v;
v.push_back(d1);
v.push_back(Date(2025, 1, 1));
v.push_back({2025, 1, 1}); // 使用列表初始化
vector<int> v1 = {1, 2, 3, 4};
vector<int> v2 = {10, 20, 30, 1, 1, 1, 1, 1, 1, 1, 1, 1};
const vector<int>& v4 = {10, 20, 30, 1, 1, 1, 1, 1, 1, 1, 1, 1};
vector<int> v3({10, 20, 30, 1, 1, 1, 1, 1, 1, 1, 1, 1});
v.push_back({2025, 1, 1});
:直接使用列表初始化创建一个Date
对象,并插入到向量v
中。- 容器的列表初始化:
v1
、v2
、v4
、v3
都使用了列表初始化,其中v3
显式地调用了构造函数。
std::initializer_list
的使用
initializer_list<int> il1 = {10, 20, 30, 1, 1, 1, 1, 1, 1, 1, 1, 1};
std::initializer_list
是一个轻量级的只读容器,用于保存初始化列表中的元素。- 容器类(如
vector
)的构造函数和赋值运算符都增加了接受std::initializer_list
的版本,因此可以直接使用{}
进行初始化。
map
的列表初始化和插入
map<string, string> dict;
dict.insert({"xxx", "yyyy"}); // 使用列表初始化的 pair
map<string, string> dict2 = {{"xxx", "yyyy"}, {"sort", "zzzz"}};
dict.insert({"xxx", "yyyy"});
:{"xxx", "yyyy"}
会被隐式转换为std::pair<const string, string>
,然后插入到dict
中。dict2
的初始化:直接使用列表初始化,将多个键值对插入到map
中。
std::initializer_list
原理和作用
C++11引入了std::initializer_list
,使得初始化容器和自定义类型的方式更加灵活和简洁。
背景
在C++98中,初始化数组和聚合类型(如结构体)可以使用大括号{}
,但对于容器和自定义类的初始化,尤其是当需要传入多个参数时,显得不够方便。例如,要初始化一个std::vector
对象并赋予多个初始值,可能需要多次调用push_back
,或者手动实现多个构造函数来支持不同数量的参数。
std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
// 或者手动实现多个构造函数来支持不同数量的参数
为了解决这个问题,C++11引入了std::initializer_list
,提供了一种统一的方式来初始化容器和自定义类型。
std::initializer_list
的原理
std::initializer_list
是C++11标准库中的一个模板类,用于表示由大括号{}
括起来的一系列元素。它允许我们使用列表初始化的方式为对象赋值,从而简化代码书写,提高可读性。
#include <initializer_list>
std::initializer_list<int> il = {1, 2, 3};
内部实现
std::initializer_list
内部包含了两个指针,分别指向初始化列表中第一个元素和最后一个元素的下一个位置。其实现通常如下:
template <class E>
class initializer_list {
public:
// 类型定义
typedef E value_type;
typedef const E& reference;
typedef const E& const_reference;
typedef size_t size_type;
typedef const E* iterator;
typedef const E* const_iterator;
// 构造函数
initializer_list() noexcept : _array(0), _length(0) {}
// 大小和开始、结束迭代器
size_t size() const noexcept { return _length; }
const E* begin() const noexcept { return _array; }
const E* end() const noexcept { return _array + _length; }
private:
// 私有成员
const E* _array;
size_t _length;
};
_array
:指向存储元素的数组的指针。_length
:表示元素的数量。
特性
- 只读容器:
std::initializer_list
是一个轻量级的只读容器,不能修改其中的元素。 - 自动推导类型:可以通过
auto
关键字自动推导类型。 - 范围for循环:支持使用范围for循环遍历元素。
std::initializer_list
的作用
初始化容器
C++11中的标准容器(如std::vector
、std::list
、std::map
等)都增加了接受std::initializer_list
的构造函数和赋值运算符,使得容器可以方便地使用列表初始化。
#include <iostream>
#include <vector>
#include <map>
#include <string>
int main() {
// 初始化vector
std::vector<int> v1 = {1, 2, 3, 4, 5};
std::vector<int> v2{6, 7, 8, 9, 10}; // 省略等号
// 初始化map
std::map<std::string, std::string> dict = {
{"apple", "苹果"},
{"banana", "香蕉"}
};
// 输出vector内容
for (auto val : v1) {
std::cout << val << " ";
}
std::cout << std::endl;
// 输出map内容
for (const auto& pair : dict) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
运行结果
1 2 3 4 5
apple: 苹果
banana: 香蕉
std::vector
的列表初始化:通过{}
直接传入初始值列表,调用了接受std::initializer_list
的构造函数。然后作为initializer_list
来构造容器。std::map
的列表初始化:使用{}
传入键值对的列表,其中每个键值对也是使用{}
初始化的std::pair
对象,也就相当于initializer_list
的嵌套构造。
自定义类型的初始化
除了标准容器,用户自定义的类也可以通过定义接受std::initializer_list
的构造函数,来支持列表初始化。
#include <iostream>
#include <initializer_list>
class MyClass {
public:
MyClass(std::initializer_list<int> il) {
for (auto it = il.begin(); it != il.end(); ++it) {
data.push_back(*it);
}
}
void print() const {
for (auto val : data) {
std::cout << val << " ";
}
std::cout << std::endl;
}
private:
std::vector<int> data;
};
int main() {
MyClass obj = {1, 2, 3, 4, 5};
obj.print(); // 输出:1 2 3 4 5
return 0;
}
- 接受
std::initializer_list
的构造函数:在自定义类MyClass
中,定义了一个构造函数,接受std::initializer_list<int>
类型的参数。 - 使用列表初始化创建对象:在
main
函数中,直接使用{1, 2, 3, 4, 5}
来初始化MyClass
对象。
函数参数的初始化
std::initializer_list
也可以作为函数的参数,方便地传递一组值。
#include <iostream>
#include <initializer_list>
void printValues(std::initializer_list<int> il) {
for (auto val : il) {
std::cout << val << " ";
}
std::cout << std::endl;
}
int main() {
printValues({10, 20, 30, 40, 50});
return 0;
}
10 20 30 40 50
- 函数接受
std::initializer_list
参数:printValues
函数接受一个std::initializer_list<int>
类型的参数。 - 调用函数时传入列表:在调用
printValues
时,直接传入一个初始化列表{10, 20, 30, 40, 50}
,也可以作为构造函数或拷贝构造函数等的实参进行传入。
容器对std::initializer_list
的支持
- 构造函数
标准容器都增加了接受std::initializer_list
的构造函数。例如:
// vector的initializer_list构造函数
std::vector<int> v = {1, 2, 3, 4, 5};
// map的initializer_list构造函数
std::map<std::string, int> m = {{"one", 1}, {"two", 2}};
- 赋值运算符
容器的赋值运算符也支持std::initializer_list
,可以方便地重置容器的内容。
std::vector<int> v = {1, 2, 3};
v = {4, 5, 6}; // 重新赋值
示例代码:
#include <iostream>
#include <vector>
#include <map>
int main() {
// 使用initializer_list初始化vector
std::vector<int> vec = {1, 2, 3, 4, 5};
// 使用initializer_list赋值vector
vec = {6, 7, 8, 9, 10};
// 输出vector内容
for (auto val : vec) {
std::cout << val << " ";
}
std::cout << std::endl;
// 使用initializer_list初始化map
std::map<std::string, int> mp = {{"apple", 1}, {"banana", 2}};
// 输出map内容
for (const auto& pair : mp) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
运行结果:
6 7 8 9 10
apple: 1
banana: 2