cpp

C++11 lambda表达式

λ

Posted by kevin on October 11, 2019

Preface

虽然现在是2019年,但自从 C++11 问世之后,lambda 表达式就一直备受关注,对我来说,无非是配合 STL 使用,或者在写 GUI 界面时会用到 lambda 表达式,其实它的结构还是非常简单的,用起来也舒服,这篇文章就来简单介绍一下。

0x00

lambda 表达式 λ 的完整结构如下面所示

[capture] (params) mutable -> return-type {statements}
  • [capture] 是捕捉列表,能够捕捉上下文的变量以供 lambda 函数使用
  • (params) 是传入的参数列表,就跟普通的函数参数一样,如果不需要传递的话,可以连同括号一起省略
  • mutable 是修饰符,意思是可不可以改变捕捉的变量的值,因为 lambda 表达式默认是一个 const 函数,我们可以用 mutable 取消它的常量性 。注意,如果用了 mutable 关键字,我们一定要加上前面那个括号,不管里面有没有传参数。
  • -> return-type 显式地指定了函数的返回类型,用了追踪返回值类型(先不讲),如果不需要指定的话就可以直接将这一部分省略掉。
  • {statements} 里面就是函数的函数体了,写法也和普通的函数一样,但是它除了可以使用传入的参数之外还可以使用上下文中捕捉到的所有变量。

极限情况,所有可以省略的部分都省略,可以得到最简单的一个 lambda 表达式

[]{}

当然,这个函数一点卵用也没有。。。我们用一段代码讲一讲

int main()
{
	int a = 3, b = 4;
	[=] { return a + b; };	// 返回类型由编译器推断为 int
	auto fun1 = [&](int c) { b = a + c; };	// 无返回值的 lambda
	auto fun2 = [=, &b](int c) ->int { return b += a + c; };	// 完整的 lambda 表达式
}

0x01

在上面那段代码中,我们将 lambda 表达式赋值给 fun1 和 fun2 ,此时它们就相当于是一个函数,可以像普通函数一样进行调用,不同的是两者的捕捉列表,fun1 用 & 对所有变量进行了捕捉,传入到 lambda 表达式中的是引用值,可以改变其值, 而 fun2 使用 = 值传递捕捉变量,用 & 捕捉 变量 b,因此 b 的值是可以被修改的。

fun1(2);
cout << b << endl;
out: 5

语法上,捕捉列表可以由多个捕捉项组成,并不一定只有一个选项,通过组合还可以表示更加复杂的意思。

option meaning
[var] 以值传递方式捕捉变量 var
[=] 以值传递方式捕捉所有父作用域变量(包括 this)
[&var] 以引用传递方式捕捉变量 var
[&] 以引用传递方式捕捉父作用域的变量(包括 this)
[this] 以值传递方式捕捉当前的 this 指针
[=, &a, &b] 以引用传递捕捉 a 和 b,值传递捕捉其他所有变量
[&, a, this] 以值传递捕捉 a 和 this ,引用传递捕捉其他所有变量

#注意, 变量不能重复传递,如 [=, a] 这种就是不对的,因为 = 已经值传递了所有变量,其中已经包括了 a 变量

关于按值传递捕捉的变量不能被修改这一点,有人认为这算是 “闭包” 类型的名称的体现,即在复制了上下文中变量之后关闭了变量与上下文中变量的联系,变量只与 lambda 函数运算本身有关,不会影响 lambda 函数(闭包)之外的任何内容。

0x02

lambda 表达式其实可以说是 C++ 的仿函数的语法糖,下面是一个 C++ 的仿函数的形式

class _functor
{
public:
    int operator () (int x, int y){
        return x + y;
    }
};

int main()
{
    _functor demo;
    return demo(3,6);
}
// out: 9

可以看到,仿函数就是相当于重载了 () 运算符,使它用起来的时候跟函数感觉差不多,但其实上面的 demo 是一个类的对象,并不是一个函数。也可以说仿函数是编译器实现 lambda 表达式的一种方式,现阶段,当你写了一个 lambda 表达式时,编译器都会将 lambda 函数转化成一个仿函数对象,可以视为在 C++11 中,lambda 和仿函数是等价的。

0x03

lambda 表达式最常用的地方还是 QT GUI 编程和 C++ STL 中,它将 STL 算法的使用变得更加简单。lambda 表达式经常是配合 auto 关键字一起使用,这也是 C++11 的一个新特性,能够让编译器自动推导出变量的类型,也是一个非常有用的关键字。

STL 库的算法函数一般都会传入一个函数参数,看看 STL 中的 for_each 的一种实现原型。

for_each(iterator begin, iterator end, Function fn){
	for (iterator i = begin; i != end; fn(*i));
}

这里面的 fn 参数我们就可以传入一个 lambda 表达式让结构更加简单。用下面的代码做个例子,我们实现将 num 中大于 base 值的元素给送到 largeNum 中

vector<num>
vector<largeNum>
const int base = 10;
void select(){
	for_each(num.begin(), num.end(), 
	[=](int i){
	if (i > base)
		largeNum.push_back(i);
	});
}

关于 lambda 表达式就介绍到这里,实际编程中可以结合场景恰当使用 lambda,尤其是使用算法库的时候,但也不是哪儿都可以使用 lambda 表达式,捕捉列表那儿还是挺容易犯错的,不能一味的图方便省事。