1. C++11 发展概述
C++11是 C++ 的第二个主要版本,并且是从 C++98 起的最重要更新。它引入了大量更改,标准化了既有实践,并改进了对C++程序员可用的抽象。在它最终由1S0在 2011年8月12日采纳前,人们曾使用名称“C++0x”,因为它曾被期待在 2010年之前发布。C++03与C++11期间花了8年时间,故而这是迄今为止最长的版本间隔。从那时起,C++有规律地每3年更新一次。
2. 列表初始化
2.1 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;
}
2.2 C++11 统一初始化语法
C++11 引入 {}
初始化语法,旨在提供统一的初始化方式:
// 内置类型
int x1 = {2};
int x2{2};//省略=
// 自定义类型
Date d1 = {2025, 1, 1};
const Date& d2 = {2024, 7, 25};
// 省略等号
Point p1{1, 2};
Date d6{2024, 7, 25};
优势:
- 统一所有类型的初始化语法
- 避免"最令人烦恼的解析"问题
- 防止窄化转换
- 容器初始化更方便
2.3 std::initializer_list
C++11 引入 std::initializer_list
支持任意数量元素的初始化:
vector<int> v1 = {1, 2, 3}; // 使用 initializer_list 构造
vector<int> v2{1, 2, 3, 4, 5};
map<string, string> dict = {
{"sort", "排序"},
{"string", "字符串"}
};
实现原理是容器提供了接受 initializer_list
的构造函数:
template<class T>
class vector {
public:
vector(initializer_list<T> il) {
for (auto e : il)
push_back(e);
}
};
3. 右值引用与移动语义
3.1 左值与右值
-
左值:有持久状态的对象,可以取地址
- 变量名、解引用指针、字符串字面量
- 可以出现在赋值符号左侧
-
右值:临时对象或字面量,不能取地址
- 字面常量、表达式结果、函数返回值
- 只能出现在赋值符号右侧
int main() {
// 左值
int* p = new int(0);
int b = 1;
const int c = b;
string s("111111");
// 右值
10;
x + y;
fmin(x, y);
string("11111");
}
3.2 左值引用与右值引用
- 左值引用:
Type&
,只能绑定左值,const左值引用可以绑定右值 - 右值引用:
Type&&
,只能绑定右值,右值引用可以绑定move后的左值move原理类似强转
int main() {
int b = 1;
// 左值引用
int& r1 = b;
// 右值引用
int&& rr1 = 10;
// const左值引用可以绑定右值
const int& rx1 = 10;
// 右值引用可以绑定move后的左值
int&& rrx1 = move(b);
}
3.3 右值引用关键特性
-
只能绑定到右值(临时对象)
-
可以延长临时对象的生命周期
-
主要用途是实现移动语义和完美转发
-
需要注意的是变量表达式都是左值属性,也就意味着⼀个右值被右值引用绑定后,右值引用变量变量表达式的属性是左值
string&& rref = string("temp"); // 合法
// string&& rref2 = rref; // 错误!rref本身是左值
string&& rref3 = std::move(rref); // 合法,使用move转换
3.4 移动语义
移动构造和移动赋值允许"窃取"右值资源,避免深拷贝:
class string {
public:
// 移动构造函数
string(string&& s) {
swap(s); // 直接交换资源
}
// 移动赋值运算符
string& operator=(string&& s) {
swap(s);
return *this;
}
};
3.5 完美转发的实现机制
1. 引用折叠规则
当出现引用的引用时,编译器会按照以下规则折叠:
T& &
→T&
T& &&
→T&
T&& &
→T&
T&& &&
→T&&
2. 万能引用
模板参数中的T&&会根据传入实参自动推导:
template <typename T>
void func(T&& arg) { // 可以是左值引用或右值引用
// ...
}
int a = 10;
func(a); // T推导为int&,arg类型为int&
func(10); // T推导为int,arg类型为int&&
2. 完美转发的使用
结合前面所说,变量表达式都是左值属性,也就意味着一个右值被右值引用绑定后,右值引用变量表达式的属性是左值,也就是说Function函数中t的属性是左值,那么我们把t传递给下一层函数Fun,那么匹配的都是左值引用版本的Fun函数。这里我们想要保持t对象的属性就需要使用完美转发实现。
template <class T>
void Function(T&& t) {
// 使用forward保持值类别
Fun(std::forward<T>(t));
}
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
int main() {
int a = 1;
Function(a); // 左值 推导出T为int&
Function(std::move(a)); // 右值 推导出T为int
Function(10); // 右值 推导出T为int
}