C++11 的发展历史
C++11 是 C++98 之后最重要的更新,标准化了既有实践并改进了抽象能力。在 ISO 于 2011 年 8 月正式采纳前,它曾被称为 C++0x。这次更新间隔长达 8 年,此后 C++ 的迭代周期才稳定为每 3 年一次。

列表初始化
C++98 传统的{}
在 C++98 中,数组和结构体可以使用花括号进行初始化,但这仅限于特定类型。
struct Point { int _x; int _y; };
int main() {
int array1[] = { 1, 2, 3, 4, 5 };
int array2[5] = { 0 };
Point p = { 1, 2 };
return 0;
}
C++11 中的{}
C++11 试图统一初始化方式,实现'一切对象皆可用{}初始化',这被称为列表初始化。它不仅支持内置类型,也支持自定义类型。对于自定义类型,本质上是构造临时对象后优化为直接构造。值得注意的是,列表初始化过程中可以省略等号 =。
#include<iostream>
#include<vector>
using namespace std;
struct P { int a; int b; };
class Date {
public:
Date(int year = 1, int month = 1, int day = 1) :_year(year), _month(month), _day(day) {
cout << "Date(int year = 1, int month = 1, int day = 1)" << endl;
}
Date(const Date& d) :_year(d._year), _month(d._month), _day(d._day) {
cout << "Date(const Date& d)" << endl;
}
private:
int _year; int _month; int _day;
};
int main() {
// C++98 支持
int a[] = { 1, 2, 3, 4, 5 };
int a2[5] = { 0 };
P p = { 1, 2 };
// C++11 支持
// 内置类型支持
int x1 = { 2 };
// 自定义类型支持
// 这里本质是用 { 2025, 1, 1 } 构造一个 Date 临时对象
// 临时对象再去拷贝构造 d1,编译器优化后合二为一变成直接构造
Date d1 = { 2025, 1, 1 };
// 这里 d2 引用的是 { 2024, 7, 25 } 构造的临时对象
const Date& d2 = { 2024, 7, 25 };
// 需要注意的是 C++98 支持单参数时类型转换,也可以不用{}
Date d3 = { 2025 };
Date d4 = 2025;
// 可以省略掉=
P p1{ 1, 2 };
int x2{ 2 };
Date d6{ 2024, 7, 25 };
const Date& d7{ 2024, 7, 25 };
// 不支持,只有{}初始化,才能省略=
vector<Date> v;
v.push_back(d1);
v.push_back(Date(2025, 1, 1));
// 比起有名对象和匿名对象传参,这里{}更有性价比
v.push_back({ 2025, 1, 1 });
return 0;
}
C++11 中的 std::initializer_list
虽然列表初始化很方便,但在容器初始化场景下仍有局限。如果要用 N 个值构造 vector,传统做法需要重载多个构造函数。C++11 引入了 std::initializer_list,底层开一个数组将数据拷贝过来,内部有两个指针分别指向数组的开始和结束。
STL 容器通过支持 std::initializer_list 的构造函数,实现了任意多个值的 {x1,x2,x3...} 初始化。
#include<iostream>
#include<vector>
#include<string>
#include<map>
using namespace std;
int main() {
std::initializer_list<int> mylist;
mylist = { 10, 20, 30 };
cout << sizeof(mylist) << endl;
// begin 和 end 返回的值是 initializer_list 对象中存的两指针
// 这两个指针的值跟 i 的地址接近,说明数组存在栈上
int i = 0;
cout << mylist.begin() << endl;
cout << mylist.end() << endl;
cout << &i << endl;
// {}列表中可以有任意多个值
// 第一个 v1 是直接构造,第二个 v2 是构造临时对象 + 拷贝 + 优化为直接构造
vector<int> v1({ 1, 2, 3, 4, 5 });
vector<int> v2 = { 1, 2, 3, 4, 5 };
const vector<int>& v3 = { 1, 2, 3, 4, 5 };
// pair 对象的{}初始化和 map 的 initializer_list 构造结合使用
map<string, string> dict = { {"sort", "排序"}, {"string", "字符串"} };
// initializer_list 版本的赋值支持
v1 = { 10, 20, 30, 40, 50 };
return 0;
}
右值引用
左值和右值
左值是表示数据的表达式(如变量名或解引用的指针),通常有持久状态,存储在内存中,我们可以获取它的地址。左值可以出现在赋值符号左边或右边。const 修饰后的左值不可赋值,但可取地址。
int main() {
int* p = new int(0);
*p = 10;
int c = 1;
const int b = c;
string s = "hello";
string s1("world");
cout << &p << endl;
cout << &b << endl;
cout << &s << endl;
cout << &s1 << endl;
cout << (void*) &s1[0] << endl;
return 0;
}

右值也是表示数据的表达式,通常是字面值常量或表达式求值过程中的临时对象。右值只能出现在赋值符号右边,不能取地址。
// 右值:不能取地址
double x = 1.1, y = 2.2;
// 以下 10、x + y、fmin(x, y)、string("11111") 都是常见的右值
10;
x + y;
fmin(x, y);
string("11111");
// cout << &10 << endl; // 编译错误
// cout << &(x+y) << endl; // 编译错误

值得一提的是,lvalue 被解释为 locator value(存储地址的对象),rvalue 被解释为 read value(提供数值但不可寻址)。核心区别在于能否取地址。
左值引用和右值引用
- Type& r1 = x; 是左值引用,给左值取别名。Type&& rr1 = y; 是右值引用,给右值取别名。
int main() {
// 左值:可以取地址
int* p = new int(0);
int b = 1;
const int c = b;
*p = 10;
string s("111111");
s[0] = 'x';
double x = 1.1, y = 2.2;
// 左值引用给左值取别名
int& r1 = b;
int*& r2 = p;
int& r3 = *p;
string& r4 = s;
char& r5 = s[0];
return 0;
}
右值引用给右值取别名:
int && rr1 = 10;
int && rr2 = x + y;
double && rr3 = fmin(x, y);
string && rr4 = string("11111");
- 左值引用不能直接引用右值,但 const 左值引用可以。
// const 左值引用可以引用右值
const int& rr5 = 10;
const int& rr6 = x + y;
const double& rr7 = fmin(x, y);
const string& rr8 = string("11111");
- 右值引用不能直接引用左值,但可以通过 move 函数引用左值。
// 右值引用 move 左值
int&& rrx1 = move(b);
int*&& rrx2 = move(p);
int&& rrx3 = move(*p);
string&& rrx4 = move(s);
char&& rrx5 = move(s[0]);
-
move是库里的函数模板,本质是强制类型转换,涉及引用折叠知识。 -
变量表达式都是左值属性。这意味着一个右值被右值引用绑定后,该引用变量的属性变为左值。
// b、r1、rr1 都是变量表达式,都是左值
cout << &b << endl;
cout << &r1 << endl;
cout << &rr1 << endl;
// 这里要注意,rr1 的属性是左值,所以不能再被右值引用绑定,除非 move 一下
int& r6 = r1;
// int&& rrx6 = rr1; -- 报错
int&& rrx6 = move(rr1);
引用延长生命周期
右值引用可用于为临时对象延长生命周期。const 左值引用也能延长临时对象生存期,但这些对象无法被修改。
int main() {
string s1 = "hello";
// string&& s2 = s1; // 不能右值引用绑定左值
const string& s2 = s1 + s1; // OK:到 const 的左值引用延长生存期
// r2 += "Test"; // 错误:不能通过 const 引用修改
string&& r3 = s1 + s1; // OK:右值引用延长生存期
r3 += "Test"; // OK:能通过非 const 引用修改
cout << r3 << '\n';
return 0;
}
左值和右值的参数匹配
- C++98 中,const 左值引用作为参数的函数,实参传递左值和右值都可以匹配。
- C++11 以后,分别重载左值引用、const 左值引用、右值引用作为形参的函数,实参会精确匹配对应版本。注意右值引用变量在用于表达式时属性是左值。
void fun1(int& x) { cout << "左值引用重载 func1(int&x)" << endl; }
void fun1(int&& x) { cout << "右值引用重载 func1(int&&x)" << endl; }
void fun1(const int& x) { cout << "左值引用重载 func2(const int&x)" << endl; }
int main() {
int x = 10;
const int y = x;
fun1(x); // 匹配左值引用
fun1(10); // 如果没有 fun1(int&&) 重载则会调用 fun1(const int&)
fun1(y); // 匹配 const 左值引用
// 右值引用变量在用于表达式时是左值
int&& r = 10;
fun1(r); // 匹配左值引用
fun1(move(r)); // 匹配右值引用
return 0;
}



