C++ Lambda表达式
参考链接:
匿名函数
没有和标识符绑定的函数定义叫做匿名函数(anonymous function),也可以叫做Lambda表达式(lambda expression)。匿名函数一般会用作高阶函数的参数,或者在需要返回一个函数的场景下使用。
如果有个函数只需要用一次或者几次,那么采用匿名函数会比使用具名函数更加简便。
参考链接:
匿名函数抽象出一种运算,比如,想给数组中的每个元素加1,可以这样写:
#include <vector>
int main() {
std::vector<int> a = {1, 2, 3, 4, 5};
for (auto &i : a) i = i + 1;
}
但是写成下面这样会更清楚地看出,语句是对数组中的每个元素做了一个“加1”的操作;
#include <vector>
#include <algorithm>
int main() {
std::vector<int> a = {1, 2, 3, 4, 5};
std::for_each(a.begin(), a.end(), [](auto & x){ x = x + 1; });
}
参考链接:
函数对象
函数对象,有时也被称作仿函数(functor),顾名思义,就是一个使用起来像函数,但其实并不是函数的东西。它是通过重载类的()
运算符来实现的这种效果。
#include <iostream>
struct add_n {
int num;
add_n(int _n) : num(_n) {}
int operator()(int val) const { return num + val; }
};
int main() {
add_n add_16(16);
std::cout << add_16(16); // 输出32
}
上面的代码片中重载了add_n
类的operator()
方法,也就是()
运算符,然后声明了一个add_n
类的实例add_16
,接着调用了实例的operator()
方法,看起来就像是使用了一个名为add_16
的函数。
参考链接:
- C++ lambda表达式与函数对象 - 简书 (jianshu.com) (👈这个文章写的挺不错,下面的内容参照了这篇文章)
闭包
闭包可以理解成一保存着函数及其运行环境/状态的包。像上文的add_n
(的实例)就可以看成是一个闭包。
使用Lambda表达式
先来看C++中Lambda表达式的几种形式,其中captures是表示需要捕获的变量,也就是匿名函数中需要用到的变量,params是需要接收的参数,ret是返回值类型,body则是函数体
原型 | 说明 |
---|---|
[ captures ] ( params ) -> ret { body } | Lambda表达式的原型 |
[ captures ] ( params ) { body } | 返回值可以自动推导,所以可以不用指明返回值 |
[ captures ] { body } | 如果函数不要参数,那么参数也可以省略掉 |
先从比较简单的几个例子说起:
#include <iostream>
int main() {
auto hello = []{ std::cout << "Hello"; }; // 定义
hello(); // 调用,输出"Hello"
auto add = [](int a, int b) -> int { return a + b; }; // 指明返回类型
auto multiply = [](int a, int b) { return a * b; };
std::cout << "2 + 3 = " << add(2, 3);
std::cout << "2 * 3 = " << multiply(2,3);
}
比如使用标准库的排序的时候也可以用到匿名函数:
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
int main() {
using item_id_pair = std::pair<std::string, int>;
std::vector<item_id_pair> items = {
{"Melon", 5}, {"Apple", 1}, {"Cherry", 3}
};
auto sortByID = [](const item_id_pair & a, const item_id_pair & b){
return a.second < b.second;
};
std::sort(items.begin(), items.end(), sortByID);
for (auto i : items) {
std::cout << "ID: " << i.second << "\t" << i.first << '\n';
}
}
那么[]
有什么用呢(不是为了好看),联系之前提到的的“闭包”,其实[]
是用来捕获(capture)作用域中的变量的。捕获的变量就会被封装进这个“包”里面。捕获可以是值传递的方式,也可以是引用。
在[]
中写变量名时,该变量就会被以值传递的方式捕捉进闭包类中,这种情况下无法修改捕捉到的值。如果想要改动值传递捕获的值,需要使用mutable
关键字。
#include <iostream>
int main() {
int x = 10;
// auto foo = [x](int a) { x += a; return x; }; // error!
auto foo = [x](int a) mutable {
x += a;
return x;
};
std::cout << foo(2) << '\n'; // 输出 12
std::cout << foo(3) << '\n'; // 输出 15
std::cout << x; // 输出 10
}
在变量名前加&
则为引用捕获:
#include <iostream>
int main() {
int x = 10;
auto foo = [&x](int a) { x += a; return x; };
std::cout << foo(2) << '\n'; // 输出 12
std::cout << foo(3) << '\n'; // 输出 15
std::cout << x; // 输出 15
}
在[]
中写=
表示默认以值捕获所有变量,写&
表示默认以引用捕获所有变量,如果有例外情况写在后面就好,以逗号,
分割,如:
[=, &x]
:默认以值捕获所有变量,但是x
是例外,通过引用捕获;[&, x]
:默认以引用捕获所有变量,但是x
是例外,通过值捕获;
引用捕获不会延长变量生存期,因此有可能出现“悬挂引用(Dangling references)”,比如这样:
auto make_function(int x) {
return [&](int a) { return x + a; };
}
int main() {
auto foo = make_function(5);
foo(3);
}
在调用函数foo
的时候,因为临时变量x
已经被销毁,所以会返回奇奇怪怪的结果。
讲到这里,本篇文章就不深入讨论了,未尽事宜请阅读前文给出的链接,或自行查阅资料。
参考链接: