返回顶部

C++ lambda表达式

语法:

[捕获](形参)约束(可选)->返回值类型(可选)

约束包括 说明符如mutable 异常说明如throw()

捕获

捕获形式

捕获形式 说明
[] 不捕获任何外部变量
[变量名 , …] 默认以值的形式捕获指定的多个外部变量并以逗号分隔
[this] 以值的形式捕获this指针
[=] 以值的形式捕获所有外部变量(需要拷贝)
[&] 以引用形式捕获所有外部变量
[= , &x] 变量x以引用形式捕获,其余变量以传值形式捕获
[& , x] 变量x以值的形式不会,其余变量以引用形式捕获

捕获示例

值捕获

值捕获和函数传参中的值传递类似,被捕获的变量的值在lambda表达式创建时通过值拷贝的方式传入,故随后在外部对该变量的修改不会对lambda表达式中的值产生影响。

int a = 10, b = 100, c = 1000;
auto f = [a, b](){
	cout << a << " " << b << endl;
	cout << c << endl;//error 变量c没有被捕获 不能在lambda表达式中使用
};
f();//调用lambda表达式

但该方法在lambda表达式内部不能对拷贝的变量的值进行修改,即:

int a = 10, b = 100, c = 1000;
auto f = [a, b](){
	a = 20;//error
	cout << a << " " << b << endl;
};
f();

为了能在lambda表达式中修改捕获的值的拷贝,需要加上关键字mutable,实例如下:

int a = 10, b = 100, c = 1000;
auto f = [a, b]()mutable{
	a = 20;
	cout << a << " " << b << endl;
};
f();
/**
输出:
20 100
*/

引用捕获

如果想在lambda表达式中改变外部变量的值,我们需要把变量的引用传入,类似于函数传参中的引用传递。

int a = 10;
auto f = [&a](){
	cout << "改变前: " << a << endl;
	a = 30;
	cout <<"改变后:" << a << endl;
};
f();
a = 40;
f();
/**
输出:
改变前: 10
改变后:30
改变前: 40
改变后:30
*/

隐式捕获

上面的值捕获和引用捕获都需要我们在捕获列表中显示列出Lambda表达式中使用的外部变量。除此之外,我们还可以让编译器根据函数体中的代码来推断需要捕获哪些变量,这种方式称之为隐式捕获。隐式捕获有两种方式,分别是[=]和[&]。[=]表示以值捕获的方式捕获所有外部变量,[&]表示以引用捕获的方式捕获所有外部变量。

隐式值捕获
int a = 10, b = 100, c = 1000;
auto f = [=]() {
	a = 20;//error
	cout << a << " " << b << " " << c << endl;
};
f();
/**
输出:10 100 1000
*/

想要修改捕获到的值,同样需要添加关键字mutable.

int a = 10, b = 100, c = 1000;
auto f = [=]()mutable {
	a = 20;//correct
	cout << a << " " << b << " " << c << endl;
};
f();
/**
输出:20 100 1000
*/
隐式引用捕获
int a = 10, b = 100, c = 1000;
auto f = [&](){
	a = 20;//correct
	cout << a << " " << b << " " << c << endl;
};
f();
/**
输出:20 100 1000
*/

混合捕获

在上述的例子中只使用了单一的形式,lambda表达式支持混合的方式捕获变量。实例:

int a = 10, b = 100, c = 1000;
auto f = [& , a](){
	a = 20;//error
	cout << a << " " << b << " " << c << endl;
};
f();

上述示例中,变量a以值的形式捕获其余变量以引用的形式捕获,由于没加mutable关键字故对a的拷贝进行修改非法。

int a = 10, b = 100, c = 1000;
auto f = [& , a]()mutable{
	a = 20;//correct
	cout << a << " " << b << " " << c << endl;
};
f();
/**
输出:20 100 1000
*/

参数

lambda表达式的参数与普通函数的参数几乎相同,仅有如下限制:

  • 参数列表中不能有默认参数 (在高版本C++中不再有此限制)
    auto func = [](int a, int b = 10) {
     	return a * b;
     };
    cout << func(2) << '\n';
    // 输出20
    
  • 不支持可变参数
  • 所有参数必须有参数名

约束

说明符

mutable ,允许函数体修改各个捕获对象的拷贝

constexprC++17起),显示指定函数调用符为constexpr ,当函数体满足constexpr函数要求时,即使未显式指定,也会是constexpr

异常说明

提供thrownoexpect字句

示例:

int a = 10, b = 100, c = 1000;
auto f = [&](int a, int b) mutable noexcept->int {
	return a + b;
};
cout << f(a, b) + f(b, c) << endl;
/**
输出:1210
*/

返回值类型

此处可以省略,让auto自动推导。

泛型lambda

C++14开始,可以使用auto x作为参数。则可以写出如下代码:

auto f = [](auto x) {
	cout << x << endl;
};
f(10);
f(10.11);
f("hello world!");

//输出

10
10.11
hello world!

该特性的引入可以方便我们实现递归函数,相关示例将在后面提及。

生命周期

lambda表达式相关对象的生命周期如下:

  • 全局,更外层作用域的生命周期不受影响。
  • 使用值捕获的情况,先于lambda表达式函数体构造对象,后于函数体执行完析构
  • 在lambda表达式函数体内的对象,在函数体执行时创建,在闭包析构函数内析构
  • lambda对象的生命周期为所在作用域结束,析构的顺序为声明的逆序析构

lambda表达式的实际使用

C++ 优先队列

C++优先队列中的使用参考 priority_queue优先队列

C++ 排序函数sort

#include<bits/stdc++.h>
using namespace std;

struct Point {
	int x, y;
	Point(int _x = 0 , int _y = 0):x(_x) , y(_y){}
};
int main() {
	std::vector<Point> points;
	for (int i = 0; i <= 10; ++i) {
		points.push_back({ rand() % 10 , rand() % 10 });
	}
	for (auto [x, y] : points) cout << x << " " << y << endl;
	cout << "After sort" << endl;
	//写法一
	auto cmp = [&](const Point& a, const Point& b) {
		//按照x为第一优先级 y为第二优先级从小到大排序
		return a.x < b.x || (a.x == b.x && a.y < b.y);
	};
	std::sort(points.begin(), points.end(), cmp);
	//写法二
	std::sort(points.begin(), points.end(), [&](const Point& a, const Point& b) {
		//按照x为第一优先级 y为第二优先级从小到大排序
		return a.x < b.x || (a.x == b.x && a.y < b.y);
		});
	//上述两种写法等价
	for (auto [x, y] : points) cout << x << " " << y << endl;
	return 0;
}

//输出

1 7
4 0
9 4
8 8
2 4
5 5
1 7
1 1
5 2
7 6
1 4
After sort
1 1
1 4
1 7
1 7
2 4
4 0
5 2
5 5
7 6
8 8
9 4

递归函数

#include<bits/stdc++.h>
#include<functional>
using namespace std;


int main() {
    //该部分的一些代码来自cpp 参考手册 lambda表达式部分 链接已经在文末给出
    std::cout << "模仿递归 lambda 调用:\n斐波那契数:";
    auto nth_fibonacci1 = [](int n) {
        std::function<int(int, int, int)> fib = [&](int a, int b, int n) {
            return n ? fib(a + b, a, n - 1) : b;
        };
        return fib(1, 0, n);
    };
    for (int i{ 1 }; i != 9; ++i) { std::cout << nth_fibonacci1(i) << ", "; }
    //传递 lambda 给泛型
    std::cout << "\n另一种 lambda 递归方案:\n斐波那契数:";
    auto nth_fibonacci2 = [](int n) {
        auto fib = [](auto self, int a, int b, int n) -> int {
            return n ? self(self, a + b, a, n - 1) : b;
        };
        return fib(fib, 1, 0, n);
    };
    for (int i{ 1 }; i != 9; ++i) { std::cout << nth_fibonacci2(i) << ", "; }
    //对于上述第二种递归方案也可以写成如下形式
    std::cout << "\n第二种 lambda 递归方案另一种形式:\n斐波那契数:";
    auto nth_fibonacci = [](auto nth_fibonacci, int a, int b, int n)-> int {
        return n ? nth_fibonacci(nth_fibonacci, a + b, a, n - 1) : b;
    };
    for (int i{ 1 }; i != 9; ++i) { std::cout << nth_fibonacci(nth_fibonacci , 1 , 0 , i) << ", "; }
	return 0;
}

//输出

模仿递归 lambda 调用:
斐波那契数:1, 1, 2, 3, 5, 8, 13, 21,
另一种 lambda 递归方案:
斐波那契数:1, 1, 2, 3, 5, 8, 13, 21,
第二种 lambda 递归方案另一种形式:
斐波那契数:1, 1, 2, 3, 5, 8, 13, 21,

参考资料

C++ LAMBDA的演化

C++ lambda 分析

cpp 参考手册 Lambda 表达式

posted @ 2022-01-21 16:08  cherish-lgb  阅读(392)  评论(0编辑  收藏  举报