你知道的, 像函数指针, 函数对象(functor)这类都是非常方便的用于回调, 由于解耦的方式;
那么为什么还要弄一个lambda?因为这是 C++, 这理由充分不?
引子
函数指针, 灵活, 但是危险, 因为没有类型安全检查, 或者你可以按照自己的意愿进行强转.
函数对象, 或者仿函数, 个人觉得麻烦, 虽然我可以按照面向对象的方式传递一个对象, 但是我为之付出的代价是 operator()()
以及定义一个类.
其实我只是想传递一个代码块儿, 在预定的条件触发下, 做一些我指定的操作, 那为什么不弄个匿名函数呢? lambda现身了, 好听的 函数式编程
来了.
最初体验lambda表达式时在 groovy 这门 domain specific language
里, 不过它语法太过简单(没有太多强制书写rules), 所以可读性差(特别是代码复杂的时候).
然后到了 c++ 这里, 其实也差不多, 也就这个样了, 来看看 C++ 中的语法和使用吧.
正文
闭包
直接上代码: (简单的案例代码)
(注意编译方式 g++ -std=c++11 -g -Wall main.cpp -o main
)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
int main(void)
{
std::vector<int> v{1,2,3,4,5,6};
int x = 5;
//std::vector<int>::iterator
auto new_end = std::remove_if(v.begin(), v.end(),
[x](int n) { return n < x; });
auto old_end = v.end();
v.erase(new_end, old_end);
std::cout << "v: ";
for (auto i: v) {
std::cout << i << ' ';
}
std::cout << std::endl;
return 0;
}
看到啦1
2
3[x](int n){
return n<x;
}
这段匿名 闭包(closure)
替代了原来的 一元谓词函数
.
命名
lambda表达式其实就是一段匿名的闭包逻辑, 也可以称为匿名函数.
但是它能不能被命名呢? 可以, 如果你要多次使用这样的闭包逻辑的话(但是这是愚蠢的, 如果你要名字, 可以直接写一个函数呀)
方法1 : auto
使用auto
自行推断多好.1
2auto func = [](int i) { return ++i; };
std::cout << "func: " << func(6) << std::endl;方法2 : std::function
利用可调用函数对象的形式绑定1
2std::function<int(int)> func = [](int i) { return ++i; };
std::cout << "func: " << func(6) << std::endl;
果然还是觉得很傻, 劝你不要这样, 除非吧, 你要用 std::function
统一调用的形式.
详细语法
详细语法你可以参考 cppreference手册, 我这里简单的说吧.
完整的语法:1
[ capture ] ( params ) mutable exception attribute -> ret { body }
但是其中很多部分都是可以省略的, 就像我喜欢省略返回值类型一样:1
2
3[ capture ] ( params ) -> ret { body } //省略限定
[ capture ] ( params ) { body } //省略返回值类型
[ capture ] { body } //参数,返回值都省略
劝你还是写完整的吧.
详细说明:
- 省略限定时, 该类型的表达式不能改捕获 “capture” 列表中的值; 相当于一个const的闭包(默认就是不改变捕获对象的, 因为捕获对象属于外部域)
- 省略返回值时, 按照如下规则推断:
- 如果包含了 return 语句, 则该 lambda 表达式的返回类型由 return 语句的返回类型确定
- 如果没有 return 语句,则类似 void f(…) 函数
- 省略了参数列表,类似于无参函数 f()
限定列表的说明:
- mutable 修饰符说明 lambda 表达式体内的代码可以修改被捕获的变量,并且可以访问被捕获对象的 non-const 方法
- exception 说明 lambda 表达式是否抛出异常(noexcept),以及抛出何种异常,类似于void f() throw(X, Y)
- attribute 用来声明属性
capture列表说明: (就相当于外部传递给内部的实参)
[a,&b]
a变量以值的方式呗捕获,b以引用的方式被捕获[this]
以值的方式捕获 this 指针[&]
以引用的方式捕获所有的外部自动变量[=]
以值的方式捕获所有的外部自动变量[]
不捕获外部的任何变量
params 指定 lambda 表达式的参数, 就相当于形参呀.
尾巴
其实 lambda 表达式本身没有多少内容, 但是带来的改变却很大.
然而 C++ 做同一件事儿的方式从来都是多种多样的, 就看你怎么选了.