
🎬 博主名称 :月夜的风吹雨
🔥 个人专栏 : 《C语言》《基础数据结构》《C++入门到进阶》
文章目录
- [1. 列表初始化:统一初始化语法背后的原理 🌊](#1. 列表初始化:统一初始化语法背后的原理 🌊)
-
- [1.1 C++98传统的{}初始化](#1.1 C++98传统的{}初始化)
- [1.2 C++11中的{}初始化](#1.2 C++11中的{}初始化)
- [1.3 std::initializer_list](#1.3 std::initializer_list)
- [2. 右值引用和移动语义:如何大幅提升性能 💨](#2. 右值引用和移动语义:如何大幅提升性能 💨)
-
- [2.1 左值和右值](#2.1 左值和右值)
- [2.2 左值引用和右值引用](#2.2 左值引用和右值引用)
- [2.3 引用延长生命周期](#2.3 引用延长生命周期)
- [2.4 左值和右值的参数匹配](#2.4 左值和右值的参数匹配)
- [2.5 移动构造和移动赋值](#2.5 移动构造和移动赋值)
- [2.6 右值引用和移动语义解决传值返回问题](#2.6 右值引用和移动语义解决传值返回问题)
-
- [2.6.1 为什么需要移动语义](#2.6.1 为什么需要移动语义)
- [2.6.2 移动构造的本质原理](#2.6.2 移动构造的本质原理)
- [2.6.3 移动赋值的原理](#2.6.3 移动赋值的原理)
- [2.7 编译器何时默认生成移动构造和移动赋值](#2.7 编译器何时默认生成移动构造和移动赋值)
-
- 2.7.1默认生成移动构造的条件
- [2.7.2 什么情况下不会生成默认移动构造](#2.7.2 什么情况下不会生成默认移动构造)
- [2.8 编译器优化机制:RVO/NRVO与移动语义](#2.8 编译器优化机制:RVO/NRVO与移动语义)
-
- [2.8.1 编译器优化的几种场景](#2.8.1 编译器优化的几种场景)
- [2.8.2 编译器如何优化拷贝构造](#2.8.2 编译器如何优化拷贝构造)
- [2.9 右值引用与移动语义在实际应用中的价值](#2.9 右值引用与移动语义在实际应用中的价值)
-
- [2.9.1 传参中的性能优化](#2.9.1 传参中的性能优化)
- [2.9.2 emplace系列接口的终极优化](#2.9.2 emplace系列接口的终极优化)
- [2.10 类型分类](#2.10 类型分类)
- [2.11 引用折叠](#2.11 引用折叠)
- [2.12 完美转发](#2.12 完美转发)
-
- [2.12.1 为什么需要std::forward<T>](#2.12.1 为什么需要std::forward<T>)
- [2.12.2 std::forward<T>的定义和工作原理](#2.12.2 std::forward<T>的定义和工作原理)
- [2.12.3 std::forward<T>的底层原理](#2.12.3 std::forward<T>的底层原理)
- [3. 可变参数模板:函数与类模板的终极灵活实现](#3. 可变参数模板:函数与类模板的终极灵活实现)
-
- [3.1 基本语法及原理](#3.1 基本语法及原理)
- [3.2 包扩展](#3.2 包扩展)
- [3.3 emplace系列接口](#3.3 emplace系列接口)
- [4. Lambda表达式:匿名函数的内部工作原理 🎭](#4. Lambda表达式:匿名函数的内部工作原理 🎭)
-
- [4.1 Lambda表达式语法](#4.1 Lambda表达式语法)
- [4.2 捕捉列表](#4.2 捕捉列表)
- [4.3 Lambda的应用](#4.3 Lambda的应用)
- [4.4 Lambda的原理](#4.4 Lambda的原理)
- [5. 包装器](#5. 包装器)
-
- [5.1 function](#5.1 function)
- [5.2 bind](#5.2 bind)
- [6. 新的类功能](#6. 新的类功能)
-
- [6.1 默认的移动构造和移动赋值](#6.1 默认的移动构造和移动赋值)
- [6.2 成员变量声明时给缺省值](#6.2 成员变量声明时给缺省值)
- [6.3 default和delete](#6.3 default和delete)
- [6.4 final与override](#6.4 final与override)
- [7. STL中的一些变化](#7. STL中的一些变化)
1. 列表初始化:统一初始化语法背后的原理 🌊
1.1 C++98传统的{}初始化
C++98中,只有数组和结构体可以用{}进行初始化:
cpp
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;
}
1.2 C++11中的{}初始化
C++11的一大目标是统一初始化方式,试图实现一切对象皆可用{}初始化,也称为列表初始化:
cpp
#include <iostream>
#include <vector>
using namespace std;
struct Point {
int _x;
int _y;
};
class Date {
public:
Date(int year = 1, int month = 1, int day = 1)
: _year(year), _month(month), _day(day) {
cout << "Date(int year, int month, int day)" << 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 a1[] = {1, 2, 3, 4, 5};
int a2[5] = {0};
Point p = {1, 2};
// C++11支持的
int x1 = {2}; // 内置类型支持
// 自定义类型支持 - 本质是类型转换
Date d1 = {2025, 1, 1}; // 优化后直接构造,不调用拷贝构造
const Date& d2 = {2024, 7, 25}; // 引用临时对象
// 可以省略=
Point p1{1, 2};
int x2{2};
Date d6{2024, 7, 25};
// 容器初始化
vector<Date> v;
v.push_back({2025, 1, 1}); // 比有名对象和匿名对象传参更有性价比
return 0;
}
1.3 std::initializer_list
C++11引入了 std::initializer_list 来支持多值初始化:
cpp
#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; // 16 (底层是两个指针)
// 容器支持initializer_list构造
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};
// map的初始化
map<string, string> dict = {{"sort", "排序"}, {"string", "字符串"}};
// 赋值也支持initializer_list
v1 = {10, 20, 30, 40, 50};
return 0;
}
原理:std::initializer_list 内部维护一个常量数组,有两个指针分别指向数组的开始和结束。所有STL容器都增加了接 initializer_list 的构造函数,使统一初始化成为可能。
2. 右值引用和移动语义:如何大幅提升性能 💨
2.1 左值和右值
- 左值:可以取地址、有持久状态的表达式。如变量名或解引用的指针等。
- 右值:不能取地址的临时对象或字面值。如字面常量、表达式返回的临时对象等。
cpp
int main() {
// 左值:可以取地址
int* p = new int(0);
int b = 1;
const int c = b;
*p = 10;
string s("111111");
s[0] = 'x';
cout << &c << endl; // 可以取地址
cout << (void*)&s[0] << endl;
// 右值:不能取地址
double x = 1.1, y = 2.2;
10; // 字面常量
x + y; // 临时结果
fmin(x, y); // 函数返回的临时对象
string("11111"); // 临时对象
// 不能取地址
// cout << &10 << endl;
// cout << &(x+y) << endl;
// cout << &(fmin(x, y)) << endl;
// cout << &string("11111") << endl;
return 0;
}
2.2 左值引用和右值引用
- 左值引用 (
Type&):只能绑定到左值 - 右值引用 (
Type&&):只能绑定到右值 - 特殊规则:
- const左值引用可以绑定右值
- 右值引用可以绑定
move(左值)
cpp
#include <iostream>
using namespace std;
int main() {
int b = 1;
double x = 1.1, y = 2.2;
// 左值引用给左值取别名
int& r1 = b;
// 右值引用给右值取别名
int&& rr1 = 10;
double&& rr2 = x + y;
// const左值引用可以引用右值
const int& rx1 = 10;
// 右值引用可以引用move(左值)
int&& rrx1 = move(b);
// 注意:右值引用变量在表达式中是左值
int&& x = 1;
int& r6 = x; // x在表达式中是左值
int&& rrx6 = move(x); // 需要move才能再次作为右值
return 0;
}
2.3 引用延长生命周期
cpp
int main() {
std::string s1 = "Test";
// const左值引用延长临时对象生存期
const std::string& r2 = s1 + s1;
// r2 += "Test"; // 不能修改const引用
// 右值引用延长临时对象生存期
std::string&& r3 = s1 + s1;
r3 += "Test"; // 可以修改
std::cout << r3 << '\n';
return 0;
}
2.4 左值和右值的参数匹配
cpp
void f(int& x) {
std::cout << "左值引用重载 f(" << x << ")\n";
}
void f(const int& x) {
std::cout << "到const的左值引用重载 f(" << x << ")\n";
}
void f(int&& x) {
std::cout << "右值引用重载 f(" << x << ")\n";
}
int main() {
int i = 1;
const int ci = 2;
f(i); // 调用 f(int&)
f(ci); // 调用 f(const int&)
f(3); // 调用 f(int&&),如果没有则调用 f(const int&)
f(std::move(i));// 调用 f(int&&)
int&& x = 1;
f(x); // 调用 f(int& x)
f(std::move(x));// 调用 f(int&& x)
return 0;
}
2.5 移动构造和移动赋值
对于管理资源的类(如string、vector),移动构造和移动赋值可以大幅提升性能:
cpp
namespace yueye
{
class string
{
public:
typedef char* iterator;
string(const char* str = "")
: _size(strlen(str)), _capacity(_size) {
cout << "string(char* str)-构造" << endl;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
void swap(string& s) {
swap(_str, s._str);
swap(_size, s._size);
swap(_capacity, s._capacity);
}
// 拷贝构造
string(const string& s)
: _str(nullptr) {
cout << "string(const string& s)--拷贝构造" << endl;
reserve(s._capacity);
for (auto ch : s) {
push_back(ch);
}
}
// 移动构造
string(string&& s) {
cout << "string(string&& s)--移动构造" << endl;
swap(s); // 直接交换资源,不拷贝
}
// 拷贝赋值
string& operator=(const string& s) {
cout << "string& operator=(const string& s)--拷贝赋值" << endl;
if (this != &s) {
_str[0] = '\0';
_size = 0;
reserve(s._capacity);
for (auto ch : s) {
push_back(ch);
}
}
return *this;
}
// 移动赋值
string& operator=(string&& s) {
cout << "string& operator=(string&& s)--移动赋值" << endl;
swap(s); // 直接交换资源,不拷贝
return *this;
}
~string() {
cout << "~string()--析构" << endl;
delete[] _str;
_str = nullptr;
}
private:
char* _str = nullptr;
size_t _size = 0;
size_t _capacity = 0;
};
}
int main() {
yueye::string s1("xxxxx");
// 拷贝构造
yueye::string s2 = s1;
// 移动构造
yueye::string s3 = yueye::string("yyyyy");
// 移动构造
yueye::string s4 = move(s1);
return 0;
}
2.6 右值引用和移动语义解决传值返回问题
2.6.1 为什么需要移动语义
在C++98中,处理对象返回时只能通过拷贝构造,这在处理大型对象(如 string 、vector )时会产生高额的深拷贝开销:
cpp
string addStrings(string num1, string num2) {
string str;
// ... 处理逻辑
return str; // 需要调用拷贝构造
}
当返回一个局部对象时,C++98必须创建一个临时对象并拷贝内容,然后销毁原始对象,这在处理大数据结构时效率低下。
2.6.2 移动构造的本质原理
移动构造的核心思想是 "资源所有权转移" ,而非深拷贝。以 yueye::string 为例:
cpp
// 移动构造
string(string&& s) {
cout << "string(string&& s)--移动构造" << endl;
swap(s); // 本质是直接交换指针,不拷贝数据
}
移动构造的工作原理:
- 接收一个右值引用参数(
string&& s) - 通过
swap操作将资源指针直接交换 - 原始对象(
s)被"掏空",进入有效但未定义的状态(通常置为空)
为什么高效?
- 避免了内存分配和数据复制
- 对于大型数据结构(如字符串、容器),性能提升可达10-100倍
- 从 O ( n ) O(n) O(n)复杂度变为 O ( 1 ) O(1) O(1)复杂度
它没有进行任何的拷贝行为,只是指针之间的指向资源进行了交换
2.6.3 移动赋值的原理
移动赋值与移动构造类似,但需要先处理自身资源:
cpp
// 移动赋值
string& operator=(string&& s) {
cout << "string& operator=(string&& s)--移动赋值" << endl;
swap(s);
return *this;
}
移动赋值的工作流程:
- 接收右值引用参数
- 通过
swap交换资源 - 原始对象的资源在函数结束时随
s被销毁
2.7 编译器何时默认生成移动构造和移动赋值
C++11标准规定,编译器会在以下条件下默认生成移动构造和移动赋值函数:
2.7.1默认生成移动构造的条件
必须同时满足以下条件:
- 没有自定义移动构造函数
- 没有实现析构函数
- 没有实现拷贝构造函数
- 没有实现拷贝赋值运算符
cpp
class Person {
public:
Person(const char* name = "", int age = 0) : _name(name), _age(age) {}
// 满足条件:没有自定义移动构造、析构、拷贝构造、拷贝赋值
private:
bit::string _name; // 有默认移动构造
int _age;
};
默认移动构造的行为:
- 对内置类型成员:执行逐成员按字节拷贝
- 对自定义类型成员:调用该成员的移动构造(如果存在)或拷贝构造
2.7.2 什么情况下不会生成默认移动构造
cpp
class Person {
public:
Person(const char* name = "", int age = 0) : _name(name), _age(age) {}
Person(const Person& p) : _name(p._name), _age(p._age) {}
// 不会生成默认移动构造:因为实现了拷贝构造
// 如果需要默认移动构造,可显式指定
// Person(Person&& p) = default;
private:
bit::string _name;
int _age;
};
注意:只要自定义了析构函数、拷贝构造或拷贝赋值中的任何一个,编译器就不会生成默认移动语义函数。
2.8 编译器优化机制:RVO/NRVO与移动语义
2.8.1 编译器优化的几种场景
编译器会对返回值进行优化,减少不必要的拷贝:
场景1:无移动构造,只有拷贝构造
- 无优化:两次拷贝构造(原始对象→临时对象→返回对象)
- 优化后:一次拷贝构造(直接构造返回对象)
场景2:有移动构造
- 无优化:两次移动构造
- 优化后:一次移动构造
场景3:返回临时对象
- 无移动构造:一次拷贝构造
- 有移动构造:一次移动构造
场景4:返回赋值
- 无移动构造:一次拷贝构造+一次拷贝赋值
- 有移动构造:一次移动构造+一次移动赋值
2.8.2 编译器如何优化拷贝构造
现代C++编译器会应用两种主要优化:
- 返回值优化(RVO, Return Value Optimization)
- 为返回对象直接在调用者栈帧中分配空间
- 消除临时对象的创建
- 命名返回值优化(NRVO, Named Return Value Optimization)
- 对有名字的返回对象也进行RVO优化
cpp
string createString() {
string str("hello world");
return str; // NRVO:直接在调用者位置构造str
}
为什么需要移动构造?
- 并非所有场景都能进行RVO/NRVO优化
- 条件分支中的不同返回路径
- 复杂表达式返回
- 无法确定返回对象来源的情况
例如:
cpp
string getStr(bool flag) {
if (flag) {
return string("true"); // 可能优化
} else {
return string("false"); // 可能优化
}
}
在分支结构中,编译器难以确定返回哪个对象,无法进行RVO优化,此时移动构造是最佳选择。
2.9 右值引用与移动语义在实际应用中的价值
2.9.1 传参中的性能优化
STL容器增加了右值引用版本的接口:
cpp
// 传统push_back
void push_back(const value_type& val); // 左值:拷贝构造
// 右值版本
void push_back(value_type&& val); // 右值:移动构造
性能对比:
- 左值参数:调用拷贝构造(O(n))
- 右值参数:调用移动构造(O(1))
cpp
list<yueye::string> lt;
yueye::string s1("111111111111111111111");
// 左值:拷贝构造
lt.push_back(s1);
// 右值:移动构造
lt.push_back(move(s1));
// 直接构造:最高效
lt.push_back("111111111111");
2.9.2 emplace系列接口的终极优化
C++11引入了 emplace 系列接口,进一步减少拷贝:
cpp
template<class... Args>
void emplace_back(Args&&... args) {
insert(end(), std::forward<Args>(args)...);
}
工作原理:
- 接收任意参数包
- 使用完美转发将参数直接传递给元素构造函数
- 在容器内存位置直接构造对象
2.10 类型分类
C++11以后,进一步对类型进行了划分:
- 纯右值(prvalue):字面值常量、求值结果相当于字面值或不具名的临时对象
- 将亡值(xvalue):返回右值引用的函数的调用表达式和转换为右值引用的转换函数的调用表达
- 泛左值(glvalue):包含将亡值和左值
2.11 引用折叠
C++中不能直接定义引用的引用,如 int&&& r = i; ,但通过模板或typedef中的类型操作可以构成引用的引用:
cpp
// 由于引用折叠限定,f1实例化以后总是一个左值引用
template<class T>
void f1(T& x) {}
// 由于引用折叠限定,f2实例化后可以是左值引用,也可以是右值引用
template<class T>
void f2(T&& x) {}
int main() {
typedef int& lref;
typedef int&& rref;
int n = 0;
lref& r1 = n; // r1的类型是 int&
lref&& r2 = n; // r2的类型是 int&
rref& r3 = n; // r3的类型是 int&
rref&& r4 = 1; // r4的类型是 int&&
// 没有折叠->实例化为void f1(int& x)
f1<int>(n);
f1<int>(0); // 报错
// 折叠->实例化为void f1(int& x)
f1<int&>(n);
f1<int&>(0); // 报错
// 折叠->实例化为void f1(int& x)
f1<int&&>(n);
f1<int&&>(0); // 报错
// 折叠->实例化为void f1(const int& x)
f1<const int&>(n);
f1<const int&>(0);
// 折叠->实例化为void f1(const int& x)
f1<const int&&>(n);
f1<const int&&>(0);
// 没有折叠->实例化为void f2(int&& x)
f2<int>(n); // 报错
f2<int>(0);
// 折叠->实例化为void f2(int& x)
f2<int&>(n);
f2<int&>(0); // 报错
// 折叠->实例化为void f2(int&& x)
f2<int&&>(n); // 报错
f2<int&&>(0);
return 0;
}
2.12 完美转发
cpp
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const右值引用" << endl; }
template<class T>
void Function(T&& t) {
Fun(t); // 1. 直接调用:所有情况都匹配左值引用版本
// Fun(forward<T>(t)); // 2. 使用完美转发
}
int main() {
// 10是右值,推导出 T为int,模板实例化为void Function(int&& t)
Function(10); // 右值
int a;
// a是左值,推导出 T为int&,引用折叠,模板实例化为void Function(int& t)
Function(a); // 左值
// std::move(a)是右值,推导出 T为int,模板实例化为void Function(int&& t)
Function(std::move(a)); // 右值
const int b = 8;
// a是左值,推导出 T为const int&,引用折叠,模板实例化为void Function(const int& t)
Function(b); // const左值
// std::move(b)右值,推导出 T为const int,模板实例化为void Function(const int&& t)
Function(std::move(b)); // const右值
return 0;
}
2.12.1 为什么需要std::forward
在C++11中,当我们使用万能引用(universal reference)作为函数模板参数时,会出现一个关键问题:
cpp
template<class T>
void Function(T&& t) {
Fun(t); // 这里t总是被视为左值
}
尽管 T&& t 可以绑定左值和右值,但一旦参数被绑定,变量表达式 t 在函数体内就变成了左值。这意味着:
- 如果传入的是右值,
t会变成左值,无法触发移动语义 - 如果传入的是左值,
t保持为左值
这与我们期望的"完美转发"相悖------即函数应该将参数以原始的值类别转发给下一层函数。
2.12.2 std::forward的定义和工作原理
std::forward 本质是一个函数模板,它通过引用折叠规则实现类型保留:
cpp
template<class T>
T&& forward(typename remove_reference<T>::type& arg) noexcept {
return static_cast<T&&>(arg);
}
template<class T>
T&& forward(typename remove_reference<T>::type&& arg) noexcept {
return static_cast<T&&>(arg);
}
关键点解析:
- 当
T是左值引用类型(如int&)时,remove_reference<T>::type是int,因此参数类型为int&T&&会折叠为int& &&→int&static_cast<T&&>(arg)→static_cast<int&>(arg)- 返回左值引用
- 当
T是右值引用类型(如int)时,remove_reference<T>::type是int,因此参数类型为int&&T&&会折叠为int &&→int&&static_cast<T&&>(arg)→static_cast<int&&>(arg)- 返回右值引用
2.12.3 std::forward的底层原理
让我们通过一个具体例子理解std::forward的工作过程:
cpp
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const右值引用" << endl; }
template<class T>
void Function(T&& t) {
Fun(t); // 1. 直接调用:所有情况都匹配左值引用版本
// Fun(forward<T>(t)); // 2. 使用完美转发
}
int main() {
// 10是右值,推导出 T为int,模板实例化为void Function(int&& t)
Function(10); // 右值
int a;
// a是左值,推导出 T为int&,引用折叠,模板实例化为void Function(int& t)
Function(a); // 左值
// std::move(a)是右值,推导出 T为int,模板实例化为void Function(int&& t)
Function(std::move(a)); // 右值
const int b = 8;
// a是左值,推导出 T为const int&,引用折叠,模板实例化为void Function(const int& t)
Function(b); // const左值
// std::move(b)右值,推导出 T为const int,模板实例化为void Function(const int&& t)
Function(std::move(b)); // const右值
return 0;
}
情况1:不使用std::forward
t在函数体内总是被视为左值- 所有调用都匹配到左值引用版本的
Fun
情况2:使用std::forward(t)
- 保持了原始参数的值类别
- 正确匹配到相应的重载函数
3. 可变参数模板:函数与类模板的终极灵活实现
3.1 基本语法及原理
cpp
template<class... Args>
void Print(Args&&... args)
{
cout << sizeof...(args) << endl; // 计算参数包中参数个数
}
int main() {
double x = 2.2;
Print(); // 0个参数
Print(1); // 1个参数
Print(1, string("xxxxx")); // 2个参数
Print(1.1, string("xxxxx"), x); // 3个参数
return 0;
}
原理:编译器会根据参数类型和个数实例化多个函数:
cpp
void Print();
void Print(int&& arg1);
void Print(int&& arg1, string&& arg2);
void Print(double&& arg1, string&& arg2, double& arg3);
3.2 包扩展
参数包扩展允许我们对每个参数应用相同的模式:
cpp
void ShowList() {
cout << endl; // 递归终止
}
template<class T, class... Args>
void ShowList(T x, Args... args)
{
cout << x << " ";
ShowList(args...); // 递归处理剩余参数
}
template<class... Args>
void Print(Args... args)
{
ShowList(args...);
}
3.3 emplace系列接口
emplace系列接口利用可变参数模板,允许直接构造对象:
cpp
template<class... Args>
void emplace_back(Args&&... args)
{
insert(end(), std::forward<Args>(args)...);
}
template<class... Args>
iterator insert(iterator pos, Args&&... args)
{
Node* cur = pos._node;
// 完美转发参数包,直接构造对象
Node* newnode = new Node(std::forward<Args>(args)...);
// ...
}
4. Lambda表达式:匿名函数的内部工作原理 🎭
4.1 Lambda表达式语法
cpp
int main() {
// 一个简单的lambda表达式
auto add1 = [](int x, int y) -> int { return x + y; };
cout << add1(1, 2) << endl;
// 省略参数列表
auto func1 = [] {
cout << "hello bit" << endl;
return 0;
};
func1();
// 省略返回类型
int a = 0, b = 1;
auto swap1 = [](int& x, int& y) {
int tmp = x; x = y; y = tmp;
};
swap1(a, b);
return 0;
}
4.2 捕捉列表
lambda表达式可以捕捉外部变量:
cpp
int main() {
// 只能用当前lambda局部域和捕捉的对象和全局对象
int a = 0, b = 1, c = 2, d = 3;
// 显式捕捉 [值,&引用]
auto func1 = [a, &b] {
// a++; // 值捕捉的变量不能修改,引用捕捉的变量可以修改
b++;
int ret = a + b;
return ret;
};
cout << func1() << endl;
// 隐式值捕捉
auto func2 = [=] {
int ret = a + b + c;
return ret;
};
cout << func2() << endl;
// 隐式引用捕捉
auto func3 = [&] {
a++; c++; d++;
};
func3();
cout << a << " " << b << " " << c << " " << d << endl;
// 混合捕捉
auto func4 = [&, a, b] {
c++; d++;
return a + b + c + d;
};
func4();
cout << a << " " << b << " " << c << " " << d << endl;
// mutable取消常量性
auto func7 = [=]() mutable {
a++; b++; c++; d++;
return a + b + c + d;
};
cout << func7() << endl;
cout << a << " " << b << " " << c << " " << d << endl;
return 0;
}
4.3 Lambda的应用
cpp
struct Goods {
string _name; // 名字
double _price; // 价格
int _evaluate; // 评价
Goods(const char* str, double price, int evaluate)
: _name(str), _price(price), _evaluate(evaluate) {}
};
int main() {
vector<Goods> v = {
{"苹果", 2.1, 5},
{"香蕉", 3, 4},
{"橙子", 2.2, 3},
{"菠萝", 1.5, 4}
};
// 使用lambda进行排序
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
return g1._price < g2._price;
});
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
return g1._price > g2._price;
});
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
return g1._evaluate < g2._evaluate;
});
return 0;
}
4.4 Lambda的原理
lambda表达式底层是仿函数对象:
cpp
class Rate {
public:
Rate(double rate) : _rate(rate) {}
double operator()(double money, int year) {
return money * _rate * year;
}
private:
double _rate;
};
int main() {
double rate = 0.49;
// lambda
auto r2 = [rate](double money, int year) {
return money * rate * year;
};
// 函数对象
Rate r1(rate);
r1(10000, 2);
r2(10000, 2);
return 0;
}
从汇编层面看,lambda对象调用本质还是调用operator(),编译器会为lambda生成一个仿函数类,lambda的参数/返回类型/函数体就是仿函数operator()的参数/返回类型/函数体,lambda的捕捉列表本质是生成的仿函数类的成员变量。
5. 包装器
5.1 function
cpp
#include <functional>
using namespace std;
int f(int a, int b) {
return a + b;
}
struct Functor {
int operator()(int a, int b) {
return a + b;
}
};
class Plus {
public:
Plus(int n = 10) : _n(n) {}
static int plusi(int a, int b) {
return a + b;
}
double plusd(double a, double b) {
return (a + b) * _n;
}
private:
int _n;
};
int main() {
// 包装各种可调用对象:函数指针、仿函数、lambda
function<int(int, int)> f1 = f;
function<int(int, int)> f2 = Functor();
function<int(int, int)> f3 = [](int a, int b) { return a + b; };
cout << f1(1, 1) << endl;
cout << f2(1, 1) << endl;
cout << f3(1, 1) << endl;
// 包装静态成员函数
function<int(int, int)> f4 = &Plus::plusi;
cout << f4(1, 1) << endl;
// 包装普通成员函数
function<double(Plus*, double, double)> f5 = &Plus::plusd;
Plus pd;
cout << f5(&pd, 1.1, 1.1) << endl;
function<double(Plus, double, double)> f6 = &Plus::plusd;
cout << f6(pd, 1.1, 1.1) << endl;
return 0;
}
5.2 bind
cpp
#include <functional>
using namespace std;
using placeholders::_1;
using placeholders::_2;
using placeholders::_3;
int Sub(int a, int b) {
return (a - b) * 10;
}
int SubX(int a, int b, int c) {
return (a - b - c) * 10;
}
class Plus {
public:
static int plusi(int a, int b) {
return a + b;
}
double plusd(double a, double b) {
return a + b;
}
};
int main() {
auto sub1 = bind(Sub, _1, _2);
cout << sub1(10, 5) << endl;
// 调整参数顺序
auto sub2 = bind(Sub, _2, _1);
cout << sub2(10, 5) << endl;
// 调整参数个数
auto sub3 = bind(Sub, 100, _1);
cout << sub3(5) << endl;
auto sub4 = bind(Sub, _1, 100);
cout << sub4(5) << endl;
// 分别绑死第123个参数
auto sub5 = bind(SubX, 100, _1, _2);
cout << sub5(5, 1) << endl;
auto sub6 = bind(SubX, _1, 100, _2);
cout << sub6(5, 1) << endl;
auto sub7 = bind(SubX, _1, _2, 100);
cout << sub7(5, 1) << endl;
// bind一般用于,绑死一些固定参数
function<double(double, double)> f7 = bind(&Plus::plusd, Plus(), _1, _2);
cout << f7(1.1, 1.1) << endl;
// 计算复利的lambda
auto func1 = [](double rate, double money, int year) -> double {
double ret = money;
for (int i = 0; i < year; i++) {
ret += ret * rate;
}
return ret - money;
};
// 绑死一些参数,实现支持不同年华利率,不同金额和不同年份计算出复利的结算利息
function<double(double)> func3_1_5 = bind(func1, 0.015, _1, 3);
function<double(double)> func5_1_5 = bind(func1, 0.015, _1, 5);
function<double(double)> func10_2_5 = bind(func1, 0.025, _1, 10);
function<double(double)> func20_3_5 = bind(func1, 0.035, _1, 30);
cout << func3_1_5(1000000) << endl;
cout << func5_1_5(1000000) << endl;
cout << func10_2_5(1000000) << endl;
cout << func20_3_5(1000000) << endl;
return 0;
}
6. 新的类功能
6.1 默认的移动构造和移动赋值
cpp
class Person {
public:
Person(const char* name = "", int age = 0) : _name(name), _age(age) {}
// 显式指定生成默认移动构造
Person(Person&& p) = default;
private:
bit::string _name;
int _age;
};
int main() {
Person s1;
Person s2 = s1;
Person s3 = std::move(s1);
return 0;
}
6.2 成员变量声明时给缺省值
成员变量声明时给缺省值是给初始化列表用的,如果没有显式在初始化列表初始化,就会在初始化列表用这个缺省值初始化。
6.3 default和delete
cpp
class Person {
public:
Person(const char* name = "", int age = 0) : _name(name), _age(age) {}
Person(const Person& p) : _name(p._name), _age(p._age) {}
// 显式指定生成默认移动构造
Person(Person&& p) = default;
// 显式删除拷贝构造
// Person(const Person& p) = delete;
private:
bit::string _name;
int _age;
};
int main() {
Person s1;
Person s2 = s1;
Person s3 = std::move(s1);
return 0;
}
6.4 final与override
这两个特性在继承和多态章节已经进行了详细讲解。
7. STL中的一些变化
- STL中的新容器:unordered_map、unordered_set等
- 容器新增了右值引用和移动语义相关的push/insert/emplace系列接口
- 容器增加了initializer_list版本的构造函数
- 容器增加了cbegin/cend等只读迭代器接口
- 容器支持范围for遍历