C++ 中的lambda表达式

C++中的lambda与函数对象

lambda表达式是C++11中引入的一项新技术,利用lambda表达式可以编写内嵌的匿名函数,用以替换独立函数或者函数对象,并且使代码更可读。但是从本质上来讲,lambda表达式只是一种语法糖,因为所有其能完成的工作都可以用其它稍微复杂的代码来实现。但是它简便的语法却给C++带来了深远的影响。

如果从广义上说,lambda表达式产生的是函数对象。函数对象的本质上是一个类而不是一个函数,在类中,对象重载了函数调用运算符(),从而使对象能够项函数一样被调用,我们称这些对象为函数对象(Function Object)或者仿函数(Functor)。相比lambda表达式,函数对象有自己独特的优势。下面我们开始具体讲解这两项黑科技。

lambda表达式

先从一个简单的例子开始,我们定义一个输出字符串的lambda表达式,如下所示,表达式一般都是从方括号[]开始,然后结束于花括号{}:

auto basic_lambda = [] {cout << "Hello Lambda" << endl;}; //定义简单的lambda表达式 basic_lambda(); //调用

下面分别是包含参数和返回类型的lambda表达式:

auto add = [] (int a, int b)->int { return a + b;}; //返回类型需要用`->`符号指出 auto multiply = [](int a, int b) {return a * b;} //一般可以省略返回类型,通过自动推断就能得到返回类型

lambda表达式最前面的方括号提供了“闭包”功能。每当定义一个lambda表达式以后,编译器会自动生成一个 匿名类 ,并且这个类重载了()运算符,我们将其称之为闭包类型(closure type)。在运行时,这个lambda表达式会返回一个匿名的闭包实例,并且该实例是一个右值。闭包的一个强大之处在于其可以通过传值或引用的方式捕捉其封装作用域内的变量,lambda表达式前面的方括号就是用来定义捕捉模式以及变量的lambda捕捉块,如下所示:

int main() { int x = 10; // 定义作用域内的x,方便下面的lambda捕捉 auto add_x = [x](int a) { return a + x;}; // 传值捕捉x auto multiply_x = [&x](int a) {return a * x;}; //引用捕捉x }

当lambda捕捉块为空时,表示没有捕捉任何变量。对于传值方式捕捉的变量x,lambda表达式会在生成的匿名类中添加一个非静态的数据成员,由于闭包类重载()运算符是使用了const属性,所以不能在lambda表达式中修改传值方式捕捉的变量,但是如果把lambda标记为mutable,则可以改变(但是这里的改变只会对 lambda 表达式内部的代码有影响, 对外部不起作用),如下所示:

int x = 10; auto add_x = [x](int a) mutable { x * = 2; return a + x;}; cout << add_x(10) << endk; //输出30 return 0;

而对于引用方式捕捉的变量,无论是否标记为mutable,都可以对变量进行修改,并且修改的值会影响到外部, 至于会不会在匿名类中创建数据成员,需要看不同编译器的具体实现。

lambda表达式只能作为右值,也就是说,它是不能被赋值的

auto a = [] { cout << "A" << endl; }; auto b = [] { cout << "B" << endl; }; a = b; // 非法,lambda表达式变量只能做右值 auto c = a; // 合法,生成一个副本

造成以上原因是因为禁用了赋值运算符:

ClosureType &operator=(const ClosureType &) = delete;

但是没有禁用复制构造函数,所以仍然可以用是一个lambda表达式去初始化另一个(通过产生副本)。

关于lambda的捕捉块,主要有以下用法:

[]:默认不捕捉变量

[=]:默认以值捕捉所有变量(最好不要用)

[&]:默认以引用捕捉所有变量(最好不要用)

[x]:仅以值捕捉变量x,其他变量不捕捉

[&x]:仅以引用捕捉x,其他变量不捕捉

[=, &x]:默认以值捕捉所有变量,但是x是例外,通过引用捕捉

[&, x]:默认以引用捕捉所有变量,但是x是例外,通过值捕捉

[this]:通过引用捕捉当前对象(其实是复制指针)

[* this]:通过传值方式捕捉当前对象

通过以上的说明,可以看到lambda表达式可以作为返回值,赋值给对应类型的函数指针,但是使用函数指针貌似并不是那么方便,于是STL在头文件<functional>中定义了一个多态的函数对象封装std::function,其功能类似于函数指针。它可以绑定到任何类函数对象,只要参数与返回类型相同。如下面的返回一个bool且接收两个int的函数包装器:

std::function<bool(int, int)> wrapper = [](int x, int y) { return x < y; };

lambda表达式还有一个很重要的应用是其可以作为函数的参数,如下所示:

int value = 3; vector<int> v{1, 2, 3, 4, 5, 6, 7}; int count == std::count_if(v.begin, v.end(), [value](int x) {return x > value;});

下面给出lambda表达式的完整语法:

// 完整语法 [ capture - list ] ( params ) mutable(optional) constexpr(optional)(c++17) exception attribute -> ret { body } // 可选的简化语法 [ capture - list ] ( params ) -> ret { body } [ capture - list ] ( params ) { body } [ capture - list ] { body }

capture-list:捕捉列表,这个不用多说,前面已经讲过,记住它不能省略;

params:参数列表,可以省略(但是后面必须紧跟函数体);

mutable:可选,将lambda表达式标记为mutable后,函数体就可以修改传值方式捕获的变量;

constexpr:可选,C++17,可以指定lambda表达式是一个常量函数;

exception:可选,指定lambda表达式可以抛出的异常;

attribute:可选,指定lambda表达式的特性;

ret:可选,返回值类型;

body:函数执行体。

lambda新特性(C++14)

在C++14中,lambda又得到了增强,一个是泛型lambda表达式,一个是lambda可以捕捉表达式。

lambda捕捉表达式

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zydzzd.html