
🔥个人主页:胡萝卜3.0****
📖个人专栏:************************************************************************************************************************************************************************************************************************************************************《C语言》、《数据结构》 、《C++干货分享》、LeetCode&牛客代码强化刷题****************************************************************************************************************************************************************************************************************************************************************
⭐️人生格言:不试试怎么知道自己行不行
🎥胡萝卜3.0🌸的简介:


目录
[4.4 emplace系列接口](#4.4 emplace系列接口)
[4.5 emplace和emplace_back在list中的模拟实现](#4.5 emplace和emplace_back在list中的模拟实现)
[5.1 默认的移动构造和移动赋值](#5.1 默认的移动构造和移动赋值)
[5.1.1 移动构造函数](#5.1.1 移动构造函数)
[5.1.2 移动赋值函数](#5.1.2 移动赋值函数)
[5.2 成员变量声明时给缺省值](#5.2 成员变量声明时给缺省值)
[5.3 default和delete](#5.3 default和delete)
[5.4 目标构造函数和委托构造函数(了解)](#5.4 目标构造函数和委托构造函数(了解))
[5.4.1 目标构造函数](#5.4.1 目标构造函数)
[5.4.2 委托构造函数](#5.4.2 委托构造函数)
[5.5 final和override](#5.5 final和override)
[6.1 新的容器](#6.1 新的容器)
[6.2 新的接口](#6.2 新的接口)
[6.3 宝藏:范围for](#6.3 宝藏:范围for)
[7.1 lambda表达式语法](#7.1 lambda表达式语法)
[7.2 lambda表达式的应用](#7.2 lambda表达式的应用)
[7.3 捕捉列表](#7.3 捕捉列表)
[7.3.1 显示捕捉](#7.3.1 显示捕捉)
[7.3.2 隐式捕捉](#7.3.2 隐式捕捉)
[7.3.3 混合捕捉](#7.3.3 混合捕捉)
[7.3.4 成员函数中写lambda表达式](#7.3.4 成员函数中写lambda表达式)
[7.4 lambda的原理](#7.4 lambda的原理)
四、可变参数模版
4.4 emplace系列接口
通过前面的学习,我们知道emplace_back和push_back的区别其实不是很大,真正的区别就是:
- emplace_bakc是一个可变参数模版,而push_back只是一个普通的函数
emplace_back可以传参数包进行构造,而push_back不能传参数包,要么是传插入的对象,或者是进行隐式类型转换


ok,当我们了解了这些,我们就来实现一个emplace_back:
4.5 emplace和emplace_back在list中的模拟实现
cpp
template<class ...Args>
void emplace_back(Args... args)
{
emplace(end(), forward<Args>(args)...);
}
template<class ...Args>
iterator emplace(iterator pos,Args&&... args)
{
Node* cur = pos._node;
Node* newnode = new Node(forward<Args>(args)...);
Node* prev = cur->_prev;
// prev newnode cur
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator(newnode);
}
ok,当我们实现了emplace_back和emplace接口后,我们就要实现相应的可变模板参数版本的构造节点的代码:
cpp
template <class... Args>
ListNode(Args&&... args)
: _next(nullptr)
, _prev(nullptr)
, _data(std::forward<Args>(args)...)
{}
ok,这样改完之后,我们就可以使用emplace_back进行尾插操作。
但是,当我们加上emplace_back后,push_back就不能使用万能引用版本的尾插,为什么?
- 库中并没有把push_back写成泛型化,因为有了emplace_back就没有必要将push_back写成泛型化
- 要兼容以前的代码,我们只能写一个左值版本和一个右值版本的push_back代码

总结:有了emplace_back,就不需要这个万能引用版的push_back,直接使用左值版本和右值版本的push_back

emplace_back总体而言是更高效,推荐以后使用emplace系列替代insert和push系列
- 除此之外:emplace_back和push_back的用法是不能混着的~


五、C++11中类的新功能
5.1 默认的移动构造和移动赋值
原来的C++类中,有6个默认成员函数:构造、析构、拷贝构造、赋值重载、取地址重载、const取地址重载,最重要的是前4个,后2个用处不大
所谓默认成员函数,就是我们不显示写,编译器会默认生成一个。
C++11中新增了两个默认成员函数------
- 移动构造函数
- 移动赋值函数
通过前面的学习,我们知道,对于默认成员函数的学习,我们通常进行一下几点:
- 我们不显示写,编译器默认生成的行为是什么?满不满足我们的需求?如果不满足我们的需求,我们该怎么写?
ok,那我们先来看移动构造函数------
5.1.1 移动构造函数
如果你没有自己实现移动构造函数,并且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个,(换句话来说:就是没有实现移动构造、析构函数、拷贝构造、拷贝赋值重载),那么编译器会自动生成一个默认移动构造**(若实现了其中一个,编译器都不会生成默认移动构造)**
默认生成的移动构造函数:
- 对于内置类型成员会执行逐成员按字节拷贝(完成值拷贝);
- 对于自定义类型成员,则需要看这个自定义类型成员中是否实现移动构造,如果实现了移动构造,则调用移动构造;如果没有实现移动构造,则调用拷贝构造
这就说明:自身需要深拷贝处理的类型,需要自己实现移动构造
通过上面,我们也能看出,移动构造函数试用于复合类型------
就比如,我们之前的MyQueue------
cpp
class MyQueue
{
std::stack _pushSt;
std::stack _popSt;
};
移动构造函数对于MyQueue就很有意义,MyQueue中没有写析构函数、拷贝构造、赋值重载以及移动构造,MyQueue中就会默认生成一个移动构造函数。
这个默认生成的移动构造函数:
- 对于内置类型成员,完成逐字节的拷贝;
- 对于自定义类型成员,会去调用栈中的移动构造,如果栈中没有实现移动构造,就去调用栈中的拷贝构造
移动构造和拷贝构造的逻辑相似!!!
cpp
namespace carrot
{
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
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)
{
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;
}
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
if (_str)
{
strcpy(tmp, _str);
delete[] _str;
}
_str = tmp;
_capacity = n;
}
}
void push_back(char ch)
{
if (_size >= _capacity)
{
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
const char* c_str() const
{
return _str;
}
size_t size() const
{
return _size;
}
private:
char* _str = nullptr;
size_t _size = 0;
size_t _capacity = 0;
};
}
class Person
{
public:
Person(const char* name="张三",int age=18)
:_name(name)
,_age(age)
{}
private:
bit::string _name;
int _age;
};
int main()
{
Person s1;//构造
Person s2 = s1;//拷贝构造
Person s3 = std::move(s2);//右值,移动构造
return 0;
}

ok,前两个还是比较容易理解的,我们直接看第三个,第三个就比较有意思------

运行结果------

那我们现在来验证一下,当自定义类型成员中没有移动构造函数,会不会调用自定义类型中的拷贝构造函数------

- 显示写移动构造函数的写法和拷贝构造的写法类似,拷贝构造会开新空间,而移动构造直接转移资源------
cpp
// 拷贝构造
string(const string& s)
{
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);
}
注意:通过上面的代码,我们也能看出,不要轻易的对一个值进行move操作!!!
5.1.2 移动赋值函数
默认移动赋值和上面的移动构造完全类似
如果你没有自己实现移动构造函数,并且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个,(换句话来说:就是没有实现移动构造、析构函数、拷贝构造、拷贝赋值重载),那么编译器会自动生成一个默认移动赋值**(若实现了其中一个,编译器都不会生成默认移动赋值)**
默认生成的移动赋值函数:
- 对于内置类型成员会执行逐成员按字节拷贝(完成值拷贝);
- 对于自定义类型成员,则需要看这个自定义类型成员中是否实现移动赋值,如果实现了移动赋值,则调用移动赋值;如果没有实现移动赋值,则调用赋值重载
ok,我们通过代码来看一下------
cpp
class Person
{
public:
Person(const char* name="张三",int age=18)
:_name(name)
,_age(age)
{}
private:
carrot::string _name;
int _age;
};
int main()
{
Person s1;
Person s2 = s1;
Person s4("李四", 20);
s4 = std:: move(s2);
return 0;
}
运行一下------


那我们现在来验证一下,当自定义类型成员中没有移动赋值函数,会不会调用自定义类型中的拷贝构造函数------

ok,移动赋值函数这里还有一个特殊的地方------

我们通过调试来看一下------

移动赋值函数的写法和赋值重载的写法相似------
cpp
//赋值重载
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;
}
如果你自己提供了移动构造或者移动赋值,编译器就不会自动提供默认的移动构造或者移动赋值。
如果显示提供析构、拷贝构造、赋值重载中的任意一个,编译器就默认生不成移动构造和移动赋值了,就会去调用拷贝构造或者拷贝赋值(我们不写拷贝构造或者拷贝赋值,编译器会生成默认的拷贝构造),左值可以使用,右值也可以使用
5.2 成员变量声明时给缺省值
成员变量声明时给缺省值是给初始化列表用的,如果没有显示在初始化列表初始化,就会在初始化列表中用这个缺省值进行初始化
5.3 default和delete
- C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。
比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成。
- 如果能想要限制某些默认函数的生成,**在C++98中,是该函数设置成private(私有),并且 只声明不实现,这样只要其他人想要调用就会报错。**在C++11中更简单,只需在该函数声明加上【 = delete】即可,该语法指示编译器不生成对应函数的默认版本,称【= delete】修饰的函数为 删除函数。
如果我不想让别人去调用这个函数,就加上delete,加上之后,别人用了就会报错!!!
代码演示:
cpp
class Person
{
public:
Person(const char* name="张三",int age=18)
:_name(name)
,_age(age)
{}
//C++11
//不想让别人去调用这个拷贝构造函数
Person(const Person& p) = delete;
//强制生成默认移动构造函数
Person(Person&& p) = default;
private:
//C++98 中不想让别人去调用这个拷贝构造函数
//在private中只定义这个函数,但不实现
Person(const Person& p);
carrot::string _name;
int _age;
};
int main()
{
Person s1;
Person s2 = s1;
Person s4("李四", 20);
s4 = std:: move(s2);
return 0;
}
5.4 目标构造函数和委托构造函数(了解)
5.4.1 目标构造函数

5.4.2 委托构造函数

5.5 final和override
老朋友,这个final和override我们在继承和多态那个章节已经进行了详细讲过了,uu们如果忘了就看一下往期的博客------
六、C++11:STL的变化
6.1 新的容器
下面这张图中圈起来的就是 C++11的STL中的新增容器,但是实际中最有用的是**unordered_map和unordered_set**。
C++11新增容器:array、forward_list(单链表)、unordered_map和unordered_ed(真正有用的就这俩)------

6.2 新的接口
STL中容器的新接口也不少,最重要的就是右值引用和移动语义相关的push / insert / emplace系列接口(插入数据系列的接口);移动构造和移动赋值(雪中送炭),还有initializer_list版本的构造(锦上添花的作用)等,这些前面都讲过了,还有一些无关痛痒的cbegin / cend等需要时查查文档即可。
6.3 宝藏:范围for
容器的范围for遍历,这个在容器部分也讲过了
七、lambda
7.1 lambda表达式语法
- lambda表达式本质是一个匿名函数对象,和普通函数不同的是lambda表达式可以定义在函数的内部
- lambda表达式在语法使用层而言没有类型,所以我们一般使用auto 或者 模板参数定义的对象 去接收lambda对象,(语法层没有类型,因为写不出类型)
lambda表达式的格式------
cpp
[capture-list] (parameters) -> return type { function boby }
匿名函数,定义一个没有名字的函数,但是定义出来的又是一个对象!!!
- **[ capture-list ]:捕捉列表,该列表总是出在lambda函数的开始位置,编译器根据 [ ] 来判断接下来的代码是否为lambda 函数,捕捉列表能够捕捉上下文的变量供lambda函数使用,捕捉列表可以传值和传引用捕捉(后面会详细介绍)。**捕捉列表为空也不能省略
- **(parameters):参数列表,于普通函数的参数列表功能类似,**如果不需要参数传递,则可以连同()一起省略
- **-> return type:返回值类型,用追踪返回类型形式声明函数的返回值类型,**没有返回值时此部分可省略。一般返回值类型明确情况下,也可省略,由编译器对返回值类型进行推导。
注意:返回值类型写出来更清晰!!!,不写可以自动推导
- **function boby :函数体,函数体内的实现和普通函数完全类似,在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。**函数体为空也不能省略
下面就是一个简单的lambda表达式------
cpp
int main()
{
auto add1 = [](int x, int y)->int {return x + y; };
cout << add1(2, 3) << endl;
return 0;
}

不知道会不会有uu会有这个疑问:为什么这里可以这么写?
其实lambda表达式的底层是仿函数!!!

lambda表达式中有些部分是可以省略的,有些地方是不可以被省略的:

ok,我们再来看一个lambda表达式的例子------
cpp
int main()
{
int x = 1;
int y = 2;
cout << x << ":" << y << endl;
//返回值类型为void,可以省略
auto swap = [](int& x, int& y)
{
//若函数体较长,不必写在同一行
int tmp = x;
x = y;
y = tmp;
};
swap(x, y);
cout << x << ":" << y << endl;
return 0;
}
通过上面的示例,我们看到返回值类型是可以省略的,并且如果函数体较长,可以像普通函数一样分行书写

7.2 lambda表达式的应用
在学习lambda表达式之前,我们使用的可调用对象只有函数指针和仿函数对象,函数指针的类型定义起来比较麻烦,仿函数要定义一个类,相对来说比较麻烦------
cpp
#include<vector>
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 } };
return 0;
}
假设现在我们想对这些商品进行排序,我们可以根据价格排序,也可以根据评价进行排序,那现在我们是不是要写出相应的比较的仿函数------
cpp
#include<vector>
#include<algorithm>
struct Goods
{
string _name;//名字
double _price;//价格
int _evaluate;//评价
Goods(const char* str, double price, int evaluate)
:_name(str)
, _price(price)
, _evaluate(evaluate)
{}
};
struct ComparePriceLess
{
bool operator()(const Goods& g1, const Goods& g2)
{
return g1._price < g2._price;
}
};
struct ComparePriceGreater
{
bool operator()(const Goods& g1, const Goods& g2)
{
return g1._price > g2._price;
}
};
struct CompareEvaluateLess
{
bool operator()(const Goods& g1, const Goods& g2)
{
return g1._evaluate < g2._evaluate;
}
};
struct CompareEvaluateGreater
{
bool operator()(const Goods& g1, const Goods& g2)
{
return g1._evaluate > g2._evaluate;
}
};
int main()
{
vector<Goods> v = { { "苹果", 2.1, 5 }, { "⾹蕉", 3, 4 }, { "橙⼦", 2.2, 3}, { "菠萝", 1.5, 4 } };
sort(v.begin(), v.end(), ComparePriceLess());
sort(v.begin(), v.end(), ComparePriceGreater());
sort(v.begin(), v.end(), CompareEvaluateLess());
sort(v.begin(), v.end(), CompareEvaluateGreater());
return 0;
}
这样写还是比较麻烦的,sort的第三个参数不是只可以接收仿函数,还可以接收lambda表达式------

其实我们还可以更简便点,因为lambda表达式本身就是一个匿名对象,我们可以直接将lambda作为第三个参数传过去,lambda表达式作为模板参数


这么来看,lambda表达式很明显比仿函数要简单点!!!
ok,接下来,我们来看一下这个比较陌生的内容------捕捉列表
7.3 捕捉列表
所谓捕捉列表:就是在lambda函数体中我既可以使用参数传过来的,也可以使用我直接捕捉的
- lambda表达式中默认只能用lambda函数体和参数中的变量,如果想用外层作用域中的变量就需要进行捕捉
lambda表达式可以写到全局,但很少,因为全局变量不需要捕捉就可以直接使用,没有捕捉的变量,所以如果lambda表达式写在全局,捕捉列表必须为空

所以,通常情况下,lambda表达式写在局部!!!
- 捕捉方式为以下三种:
7.3.1 显示捕捉
第一种捕捉方式是在捕捉列表中显示的传值捕捉和传引用捕捉,捕捉的多个变量之间用逗号隔开。[x,y,&z],表示x和y为值捕捉,z为引用捕捉
显示捕捉,用谁就捕捉谁!!!
cpp
int main()
{
int a = 0, b = 1, c = 2, d = 3;
//显示捕捉,用谁就捕捉谁
auto func1 = [a, &b]
{
//a++; //值捕捉的变量不能修改
++b;//引用捕捉的对象可以修改
int ret = a + b;
return ret;
};
cout << func1() << endl;
return 0;
}

- **默认情况下,lambda捕捉列表中值捕捉是被const修饰的,也就是说传值捕捉过来的对象不能被修改,****但是mutable加在参数列表的后面就可以取消其常属性,也就是说使用该修饰符后,传值捕捉的对象就可以修改了,****但是修改还是修改的那个被拷贝的对象,不会影响外面的那个对象。**使用该修饰符后,参数列表不可省略(即使参数为空)
cpp
int main()
{
int a = 0, b = 1, c = 2, d = 3;
//显示捕捉,用谁就捕捉谁
auto func1 = [a, &b]()mutable
{
a++;//值捕捉的变量可以修改,但不影响外面的那个
++b;//引用捕捉的对象可以修改,影响外面的那个
int ret = a + b;
return ret;
};
cout << func1() << endl;
return 0;
}
7.3.2 隐式捕捉
第二种捕捉方式是在捕捉列表中隐式捕捉:
- 我们在捕捉列表中写一个 = 表示隐式值捕捉;
- 在捕捉列表中写一个 & 表示隐式引用捕捉
这样我们lambda表达式中用了哪些变量,编译器就会自动捕捉哪些变量
也就是说,参数比较多,但是我都想用,可以隐式捕捉,我们在捕捉列表中写一个 = 表示隐式值捕捉;在捕捉列表中写一个 & 表示隐式引用捕捉,当前域的全捕捉(局部和全局)
cpp
int x = 10;
int main()
{
int a = 0, b = 1, c = 2, d = 3;
//捕捉列表中为 = 表示当前域的全部值捕捉
//全局域的值不需要捕捉,也不能捕捉,可以直接用
auto func2 = [=]
{
return a + b + c+x;
};
cout << func2() << endl;
//捕捉列表中为 & 表示当前域的全部引用捕捉
auto func3 = [&]
{
++a;
++b;
++c;
return a + b + c + x;
};
cout << func3() << endl;
return 0;
}

7.3.3 混合捕捉
所谓混合捕捉,就是在捕捉列表中混合使用隐式捕捉和显示捕捉。
例如:
-
=,\&x\] 表示其他变量隐式值捕捉,x引用捕捉;
当使用混合捕捉时,第一个元素必须是&或者=****,并且:
- &混合捕捉时,后面的捕捉变量必须是值捕捉;
- =混合捕捉时,后面的捕捉变量必须是引用捕捉
也就是说不能自相矛盾!!!
cpp
int x = 10;
int main()
{
int a = 0, b = 1, c = 2, d = 3;
//混合捕捉1
//a和b是值捕捉,其余元素为引用捕捉
auto func4 = [&, a, b]
{
//a++;//值捕捉,不能修改
//b++;//值捕捉,不能修改
c++;//引用捕捉,可以修改
d++;//引用捕捉,可以修改
return a + b + c + d;
};
cout << func4() << endl;
//混合捕捉2
//a和b引用捕捉,其余元素值捕捉
auto func5 = [=, &a, &b]
{
a++;//引用捕捉,可以修改
b++;//引用捕捉,可以修改
//d++;//值捕捉,不能修改
//c++;//值捕捉,不能修改
return a + b + c + d;
};
cout << func5() << endl;
return 0;
}
注意:在混合捕捉中,&或者=必须在第一个!!!
- 局部的静态和全局变量不能捕捉,也不需要捕捉,生命周期在全局,使用域也在全局,可以直接使用。
cpp
int x = 10;
int main()
{
int a = 0, b = 1, c = 2, d = 3;
static int y = 20;
auto func6 = [a, b, c, d]
{
return a + b + c + d + x+y;//局部的静态变量和全局变量可以直接使用
};
cout << func6() << endl;
return 0;
}
- 传值捕捉的本质是一种拷贝,并且被const修饰了,所以我们不能修改值捕捉!!!

但是加上mutable就相当于去掉了const属性,就可以修改了,但是这种修改不会影响外面被捕捉的对象,只能影响里面的对象,因为值捕捉是一种拷贝------

加上mutable后,参数列表即使为空也不能省略!!!但是一般情况下,不会加上mutable!!!
使用引用捕捉,内部被修改也会影响外面的被捕捉的对象!!!
ok,在上面的这些情况中,我们的lambda表达式都不是写在成员函数中的,那如果我们在成员函数写这个lambda表达式,会是什么情况------
7.3.4 成员函数中写lambda表达式
cpp
class A
{
public:
void func()
{
int x = 2, y = 3;
auto func1 = [=]//隐式值捕捉
{
++_b;//可以修改成员变量
return x + y + _a + _b;
};
cout << func1() << endl;
}
private:
int _a = 10;
int _b = 20;
};
int main()
{
A a;
a.func();
return 0;
}
在上面所展示的代码中,我们在A这个类中的成员函数中写了一个lambda表达式,并在表达式中使用隐式值捕捉,能不能成功运行呢?

为什么这里可以运行成功呢?_a和_b不是成员变量吗?
其实这是因为隐式捕捉(=或者&,都可以),可以把成员变量给捕捉过来,相当于把this指针捕捉了
那如果我们使用显示捕捉,还能运行吗?
cpp
class A
{
public:
void func()
{
int x = 2, y = 3;
auto func1 = [x,y]//显示捕捉,值捕捉x和y
{
return x + y + _a + _b;
};
cout << func1() << endl;
}
private:
int _a = 10;
int _b = 20;
};
int main()
{
A a;
a.func();
return 0;
}

ok,此时我们就无法正常运行,这里我们显示捕捉,只捕捉了x和y,所以我们在lambda函数体中,我们只能使用x和y,不能使用成员变量。
但是现在我想使用成员变量,怎么办?可以显示捕捉this指针,捕捉了this指针,我们就可以访问成员变量
cpp
class A
{
public:
void func()
{
int x = 2, y = 3;
//显示捕捉this指针,就可以使用成员变量了
auto func1 = [x,y,this]
{
return x + y + _a + _b;
};
cout << func1() << endl;
}
private:
int _a = 10;
int _b = 20;
};
int main()
{
A a;
a.func();
return 0;
}
并且当我们捕捉了this指针,还可以修改成员变量------
cpp
class A
{
public:
void func()
{
int x = 2, y = 3;
//显示捕捉this指针,就可以使用成员变量了
auto func1 = [x,y,this]
{
++_a;//可以修改成员变量
return x + y + _a + _b;
};
cout << func1() << endl;
cout << _a << endl;
}
private:
int _a = 10;
int _b = 20;
};
int main()
{
A a;
a.func();
return 0;
}

ok,除了这里可以修改成员变量,隐式捕捉也可以修改(因为捕捉的也是this)

7.4 lambda的原理
lambda的原理和范围for很像,范围for底层是迭代器,而****lambda底层是仿函数对象,也就说我们写了一个lambda以后,编译器会生成一个对应的仿函数的类。
在语法层,我们拿不到lambda表达式的类型,编译器可以拿到,所以lambda表达式并不是没有类型,而是我们拿不到,因为不同的编译器生成的也不同,不同的编译器会生成对应的仿函数的类
仿函数的类名是编译器按一定规则生成的,保证不同的;ambda生成的类名不同,lambda表达式的参数/返回类型/函数体就是仿函数operator()的参数/返回类型/函数体,lambda的捕捉列表本质是生成的仿函数的成员变量,也就是说捕捉列表的变量都是lambda类构造函数的实参
(很重要,是一个面试题!!!)
当然隐式捕捉,编译器要看使用哪些就传哪些对象

ok,在operator()中可以使用x很正常,x是形参,实参传给形参,那我怎么使用a和b呢?
ok,捕捉列表中的a和b就变成这个lambda类的成员变量------
- 如果是引用捕捉,那这个成员变量就是引用
- 如果是值捕捉(a),这个成员变量就类似于(const int a)

😯,现在我们就能理解------lambda的捕捉列表本质是生成的仿函数的成员变量
那我们该怎么理解------捕捉列表的变量都是lambda类构造函数的实参

也就是说,在这个lambda中有一个构造函数------

- _b就是外面b的别名,成员变量b又是_b的别名,所以成员变量b就是外面被捕捉对象b的别名,所以修改成员变量b就是修改被捕捉的对象b
- _a是被捕捉对象a的拷贝,成员变量a又是_a的拷贝,所以修改成员变量a不会影响被捕捉的对象a(更何况成员变量a不能被修改)
