lambda表达式
lambda表达式本质是一个匿名函数,其作用一是取代了传统仿函数的麻烦写法,二是对一些小函数进行局部命名处理,从而增加程序的可读性
lambda表达式是C++11新加的一个与C++风格格格不入的表达式。由于C++作为一门古老的语言,在最初创造出来时有很多地方并没有考虑到,往后打补丁时C++委员会既要考虑与C++风格的兼容,又要修复C++之前并没有考虑到的问题,可谓是费劲了苦心
于是,到了C++11,C++委员会终于做出了抉择
之后,就产生了一个保留了python滋味的语法——lambda表达式
lambda表达式的定义
如果我们将lambda表达式写全,那便是如下的样子
一眼就能看出来不是C++原本的味道
所以,我们在理解上不得不重新去了解这一表达式,不能带着对原有C++语法的认知来看待这个式子。我们来分开看这几个部分
( )——参数列表
这与众多函数一样, 任何函数都需要参数列表。就比如我们需要写一个相加的函数
//使用普通函数
int sum(int a, int b)
{
return a + b;
}
int main()
{
//使用lambda表达式
auto lambda_sum =[](int a, int b) {return a + b; };
int c = sum(1, 2);
int d = lambda_sum(1, 2);
}
两者实际上是没有区别的。
有时,我们有些函数不需要传入参数,此时连( )都可以省略掉
//使用普通函数
void func()
{
cout << "Normal func" << endl;
}
int main()
{
//使用lambda表达式
//当没有传参的时候,省略掉参数列表的括号
auto lambda_func = [] {cout << "lambda_func" << endl; };
func();
lambda_func();
}
[ ]——捕捉列表
但是和一般的函数不同的是,一般的函数体存在于全局中,在编译时便存在于函数表里;而lambda表达式不同,lambda表达式一般存在于局部里,比如存在于某个函数中,在某个参数列表里,这个时候lambda表达式旁定然会带着许多局部变量
int main()
{
//函数体里有大量局部变量
int i = 0;
char c = 'a';
vector<int> tmp;
//lambda表达式的产生也在这些局部变量里
auto lambda_func = []() {};
}
而为了方便,lambda表达式里便有一个新的参数列表——捕捉列表
捕捉列表,顾名思义,可以捕捉该局部域中的所有参数,比如
int main()
{
//函数体里有大量局部变量
int i = 0;
char c = 'a';
vector<int> tmp;
//所有局部变量均可以捕捉
auto lambda_func = [i, c, tmp]() {cout << c << i << endl; };
}
只需要在捕捉列表中输入在这个局部域中变量的名称,这个变量就被捕捉了,可以在函数体中随意使用该变量。
但是,捕捉来的变量只是一个拷贝构造的局部变量,如果要捕捉一个引用对象,需要在捕捉的变量前加上引用
auto lambda_func = [&i, c]() {i += c; };
总结一下,捕捉列表有以下特性和特殊写法:
- [var] 以值传递的方式传递一个变量var
- [=] 以值传递的方式传递整个作用域所有变量(包括this指针)
- [&var] 以引用传递的方式传递一个引用变量var
- [&] 以引用传递的方式传递整个作用域的所有变量(包括this指针)
- [this] 以值传递的方式传递this指针
class lambda
{
public:
void func()
{
int i = 0;
//单个值传递
[i]() {};
//所有值传递
[=]() {};
//单个引用传递
[&i]() {};
//所有引用传递
[&]() {};
//传递this指针
[this]() {/*可以访问类中的_a*/ cout << _a << endl; };
}
private:
int _a;
};
同时,也有一些特殊规则
- 捕捉列表可由多个捕捉项组成,并以逗号分割
比如:[=,&a,&b] 代表以值传递捕捉除a,b以外的所有变量,以引用传递捕捉a,b - 捕捉列表不能重复捕捉
[=,a,b]重复捕捉了a,b,编译器会报错
mutable——捕捉参数的性质
我们以值传递捕捉后,写一个函数体
这是因为,在lambda表达式中进行值捕捉,默认捕捉的是一个const值,无法进行修改
那值捕捉不是一个鸡肋吗?别急,这个时候就会有一个我们一般不会加上的关键字——mutable
mutable关键字的作用是修改值捕捉的属性,让捕捉的值可以被修改
并且,当我们使用引用捕捉的时候,默认传入的是非const值,因为都引用捕捉了,不就是要修改被引用的值吗?
->返回值
有人可能会好奇,为什么上面的例子都没有见到->这个东西?
这是因为,C++抄python作业的时候,也借鉴了其他语言弱类型的语法——当我们不指定返回值的类型时,编译器会自动推倒返回值的类型,所以,我们没有必要去定义返回值的类型,既然编译器会自动推导,那还要我们多笔干什么呢?
lambda表达式的类型
每一个lambda表达式的类型名称都不一样,就算他们的定义是相同的
我们直接来看一个例子
int main()
{
//两个lambda表达式完全相同
auto lambda_func1 = []() {cout << "hello world";};
auto lambda_func2 = []() {cout << "hello world";};
//但是不能进行相互赋值
lambda_func1 = lambda_func2;
}
尽管两个lambda表达式完全相同,但是相互赋值的时候,编译器还是会报错
我们不妨看看两个的类型分别是什么
我们发现,两个lambda表达式的类型是完全不同的。实际上,在底层中,每一个lambda表达式都会通过一个特定的算法,生成一个特定的uid
通过这个特定的uid,编译器便可以访问到不同的lambda表达式,而这个底层的算法我们不需要了解,只需要知道这个算法可以让几乎所有的lambda表达式不会重名便可以了
所以,已经定义了类型的lambda表达式是不能相互赋值的,因为其类型无法相互转换。但是,可以用一个lambda表达式构造另一个lambda表达式
int main()
{
//两个lambda表达式完全相同
auto lambda_func1 = []() {cout << "hello world"; };
auto lambda_func2(lambda_func1);
//类型名相同了
cout << typeid(lambda_func1).name() << endl;
cout << typeid(lambda_func2).name() << endl;
}
lambda表达式的应用
在刚开始中已经说到过,lambda表达式是一个匿名函数。我们在使用匿名对象的时候,目的是为了传参,让程序更加简洁方便。lambda表达式也是同样,在处理一些小函数的时候,lambda表达式可以极大提高程序的可读性
int main()
{
vector<int> a = { 1,3,9,5,8,4,2 };
//最初写法:采用仿函数
std::sort(a.begin(), a.end(), greater<int>());
//lambda写法,简化程序,增强可读性
std::sort(a.begin(), a.end(), [](int num1, int num2)->bool
{return num1 < num2; });
}
补充知识点——包装器
不过,lambda表达式最大的问题——类型不匹配也被解决了,包装器的产生让C++又升上了一个台阶一文详解C++11包装器https://blog.youkuaiyun.com/qq_74260823/article/details/134858775?spm=1001.2014.3001.5501