各位大佬好,我是落羽!一个坚持不断学习进步的学生。
如果您觉得我的文章还不错,欢迎多多互三分享交流,一起学习进步!
也欢迎关注我的blog主页: 落羽的落羽
文章目录
- [一、 什么是lambda表达式](#一、 什么是lambda表达式)
- [二、 表达式语法](#二、 表达式语法)
- 三、lambda的应用
一、 什么是lambda表达式
C++中有"可调用对象"的概念,主要包括函数指针、仿函数、lambda表达式。lambda表达式本质是一个匿名函数对象,跟普通函数不同的是,他可以定义在函数内部。
lambda表达式在语法使用层面上"没有类型",简单来说:lambda表达式的类型是编译器即时生成的、唯一的、匿名的,程序员无法在代码中直接写出它的类型名称。lambda表达式使用时本质是被转换成一个仿函数再使用的,每个lambda表达式都会被编译器转换成一个独一无二的、匿名的类(仿函数)。即使两个lambda看起来完全一样,编译器也会为它们生成两个不同的类。所以,我们一般都是用auto或者模板参数定义的对象去接收lambda对象。
二、 表达式语法
lambda表达式的语法格式是:[capture-list](parameters)->return-type {function body}
(parameters)
:参数列表。与普通的函数参数列表功能类似,如果不需要传参,则可以连同()
一起省略。->return-type
:return type是lambda函数的返回值类型。如果没有返回值,那么->return type
这部分就可以省略,或者返回值类型明确的情况下,能由编译器自动推导返回类型出来,也能省略这部分。{function body}
:函数体。函数体的实现与普通函数完全一样,函数体内除了能使用参数列表外,还可以使用捕捉列表内的变量。函数体为空也必须写{}
。[capture-list]
:捕捉列表。该列表写在lambda表达式的开始位置,编译器根据[]
来判断接下来的代码是否为lambda函数。捕捉列表能够捕捉上下文的变量,以供lambda函数内部使用,捕捉列表可以传值或传引用,捕捉列表就算为空也必须写[]
。
举几个栗子:
cpp
auto add = [](int x, int y){ return x + y; };

cpp
auto func = []
{
cout << "hello world" << endl;
};

cpp
auto swap = [](int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
};

非常好理解吧!
关于捕捉列表,还有一些使用的细节:
lambda表达式中默认只能使用参数列表中的变量或lambda内定义的,如果想使用外层作用域中的变量就需要进行捕捉。
- 第一种方式是显式捕捉,在捕捉列表中可以显式传值捕捉和传引用捕捉 ,捕捉的多个变量用逗号分隔,比如
[x, y, &z]
表示x和y进行传值捕捉,lambda内部改变x和y不会改变原变量,而z是传引用捕捉,对z的改变会改变原变量。 - 第二种捕捉方式是隐式捕捉,我们在[ ]中写一个 = 表示隐式值捕捉,写一个 & 表示隐式引用捕捉。这样我们的lambda的表达式中使用了哪些外部变量,编译器就会自动捕捉那些变量。
- 第三种捕捉列表是混合捕获,可以组合使用隐式捕获和显式捕获,实现更精细的控制。[=, &x] 表示x进行引用捕捉,其余变量都为值捕捉;[&, x, y] 表示x和y进行值捕捉,其余变量进行引用捕捉。使用混合捕捉时,第一个元素必须是=或&,第一个是=时,后面的捕捉变量必须是引用捕捉;第一个是&时,后面的捕捉变量必须是值捕捉。
lambda表达式中如果在局部域时,可以捕捉到它之前定义的变量,不能捕捉静态局部变量和全局变量,因为这两种变量本来就不需要捕捉也能在内部直接使用,lambda表达式内部可以直接使用。lambda表达式如果定义在全局位置,捕捉列表必须为空。
默认情况下,lambda捕捉列表是const属性的,也就是说传值捕捉来的对象不能被修改,在参数列表后加上修饰符mutable
就可以取消其常性,但是修改还是改变的形参对象,不会影响实参。使用该修饰符后参数列表不能省略。
cpp
int x = 0;
// 捕捉列表必须为空,因为全局变量不⽤捕捉就可以⽤,没有可被捕捉的变量
auto func1 = []()
{
x++;
};
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;
// 混合捕捉1
auto func4 = [&, a, b]
{
//a++;
//b++;
c++;
d++;
return a + b + c + d;
};
func4();
cout << a << " " << b << " " << c << " " << d << endl;
// 混合捕捉1
auto func5 = [=, &a, &b]
{
a++;
b++;
/*c++;
d++;*/
return a + b + c + d;
};
func5();
cout << a << " " << b << " " << c << " " << d << endl;
// 局部的静态和全局变量不能捕捉,也不需要捕捉
static int m = 0;
auto func6 = []
{
int ret = x + m;
return ret;
};
// 传值捕捉本质是⼀种拷⻉,并且被const修饰了
// mutable相当于去掉const属性,可以修改了
// 但是修改了不会影响外⾯被捕捉的值,因为是⼀种拷⻉
auto func7 = [=]()mutable
{
a++;
b++;
c++;
d++;
return a + b + c + d;
};
cout << func7() << endl;
cout << a << " " << b << " " << c << " " << d << endl;
return 0;
}
三、lambda的应用
学习lambda表达式之前,我们使用的可调用对象只有函数指针和仿函数,它们的定义都相对麻烦,使用lambda表达式去定义可调用对象,就十分简单方便了。
cpp
vector<pair<int, int>> v = { {1,2}, {3,4}, {8,3}, {5,1} };
//遇到这样的场景,假如我们想要用多种排序规则进行排序,以前是需要写不同的仿函数传给sort
/*struct Compare_first
{
bool operator()(const pair<int, int>& p1, const pair<int, int>& p2)
{
return p1.first < p2.first;
}
};
struct Compare_second
{
bool operator()(const pair<int, int>& p1, const pair<int, int>& p2)
{
return p1.second < p2.second;
}
};
sort(v.begin(), v.end(), Compare_first());
sort(v.begin(), v.end(), Compare_second());*/
//有了lambda表达式后,就方便许多了
sort(v.begin(), v.end(), [](const pair<int, int>& p1, const pair<int, int>& p2) {return p1.first < p2.first; });
for (auto pa : v)
{
cout << pa.first << ":" << pa.second << " ";
}
cout << endl;
sort(v.begin(), v.end(), [](const pair<int, int>& p1, const pair<int, int>& p2) {return p1.second < p2.second; });
for (auto pa : v)
{
cout << pa.first << ":" << pa.second << " ";
}
cout << endl;

本篇完,感谢阅读。