C++ lambda表达式与函数对象

lambda表达式是C++11中引入的一项新技术,利用lambda表达式可以编写内嵌的匿名函数,用以替换独立函数或者函数对象,并且使代码更可读。但是从本质上来讲,lambda表达式只是一种语法糖,因为所有其能完成的工作都可以用其它稍微复杂的代码来实现。但是它简便的语法却给C++带来了深远的影响。如果从广义上说,lamdba表达式产生的是函数对象。在类中,可以重载函数调用运算符(),此时类的对象可以将具有类似函数的行为,我们称这些对象为函数对象(Function Object)或者仿函数(Functor)。相比lambda表达式,函数对象有自己独特的优势。下面我们开始具体讲解这两项黑科技。

lambda表达式

我们先从简答的例子开始,我们定义一个可以输出字符串的lambda表达式,表达式一般都是从方括号[]开始,然后结束于花括号{},花括号里面就像定义函数那样,包含了lamdba表达式体:

// 定义简单的lambda表达式
auto basicLambda = [] { cout << "Hello, world!" << endl; };
// 调用
basicLambda(); // 输出:Hello, world!

上面是最简单的lambda表达式,没有参数。如果需要参数,那么就要像函数那样,放在圆括号里面,如果有返回值,返回类型要放在->后面,即拖尾返回类型,当然你也可以忽略返回类型,lambda会帮你自动推断出返回类型:

// 指明返回类型
auto add = [](int a, int b) -> int { return a + b; };
// 自动推断返回类型
auto multiply = [](int a, int b) { return a * b; }; int sum = add(2, 5); // 输出:7
int product = multiply(2, 5); // 输出:10

大家可能会想lambda表达式最前面的方括号的意义何在?其实这是lambda表达式一个很要的功能,就是闭包。这里我们先讲一下lambda表达式的大致原理:每当你定义一个lambda表达式后,编译器会自动生成一个匿名类(这个类当然重载了()运算符),我们称为闭包类型(closure type)。那么在运行时,这个lambda表达式就会返回一个匿名的闭包实例,就是一个右值。所以,我们上面的lambda表达式的结果就是一个个闭包。闭包的一个强大之处是其可以通过传值或者引用的方式捕捉其封装作用域内的变量,前面的方括号就是用来定义捕捉模式以及变量,我们又将其称为lambda捕捉块。看下面的例子:

int main()
{
int x = 10; auto add_x = [x](int a) { return a + x; }; // 复制捕捉x
auto multiply_x = [&x](int a) { return a * x; }; // 引用捕捉x cout << add_x(10) << " " << multiply_x(10) << endl;
// 输出:20 100
return 0;
}
lambda捕捉块为空时,表示没有捕捉任何变量。但是上面的add_x是以复制的形式捕捉变量x,而multiply是以引用的方式捕捉x。前面讲过,lambda表达式是产生一个闭包类,那么捕捉是回事?对于复制传值捕捉方式,类中会相应添加对应类型的非静态数据成员。在运行时,会用复制的值初始化这些成员变量,从而生成闭包。前面说过,闭包类也实现了函数调用运算符的重载,一般情况是:
class ClosureType
{
public:
// ...
ReturnType operator(params) const { body };
}

这意味着lambda表达式无法修改通过复制形式捕捉的变量,因为函数调用运算符的重载方法是const属性的。有时候,你想改动传值方式捕获的值,那么就要使用mutable,例子如下:

int main()
{
int x = 10; auto add_x = [x](int a) mutable { x *= 2; return a + x; }; // 复制捕捉x cout << add_x(10) << endl; // 输出 30
return 0;
}

这是为什么呢?因为你一旦将lambda表达式标记为mutable,那么实现的了函数调用运算符是非const属性的:

class ClosureType
{
public:
// ...
ReturnType operator(params) { body };
}

对于引用捕获方式,无论是否标记mutable,都可以在lambda表达式中修改捕获的值。至于闭包类中是否有对应成员,C++标准中给出的答案是:不清楚的,看来与具体实现有关。既然说到了深处,还有一点要注意:lambda表达式是不能被赋值的:

auto a = [] { cout << "A" << endl; };
auto b = [] { cout << "B" << endl; }; a = b; // 非法,lambda无法赋值
auto c = a; // 合法,生成一个副本

你可能会想ab对应的函数类型是一致的(编译器也显示是相同类型:lambda [] void () -> void),为什么不能相互赋值呢?因为禁用了赋值操作符:

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

但是没有禁用复制构造函数,所以你仍然可以用一个lambda表达式去初始化另外一个lambda表达式而产生副本。并且lambda表达式也可以赋值给相对应的函数指针,这也使得你完全可以把lambda表达式看成对应函数类型的指针。

闲话少说,归入正题,捕获的方式可以是引用也可以是复制,但是具体说来会有以下几种情况来捕获其所在作用域中的变量:

  • []:默认不捕获任何变量;
  • [=]:默认以值捕获所有变量;
  • [&]:默认以引用捕获所有变量;
  • [x]:仅以值捕获x,其它变量不捕获;
  • [&x]:仅以引用捕获x,其它变量不捕获;
  • [=, &x]:默认以值捕获所有变量,但是x是例外,通过引用捕获;
  • [&, x]:默认以引用捕获所有变量,但是x是例外,通过值捕获;
  • [this]:通过引用捕获当前对象(其实是复制指针);
  • [*this]:通过传值方式捕获当前对象;

在上面的捕获方式中,注意最好不要使用[=][&]默认捕获所有变量。首先说默认引用捕获所有变量,你有很大可能会出现悬挂引用(Dangling references),因为引用捕获不会延长引用的变量的声明周期:

后续补充完整,链接:

https://www.jianshu.com/p/d686ad9de817

【c++】c++ 11之lamba表达式的更多相关文章

  1. Java8中list.sort的lamba表达式

    最近写代码,需要对list集合排序,IDEA总是黄色警告: Reports calls to Collections.sort(list, comparator) which could be rep ...

  2. [C/C++11语法]_[0基础]_[lamba 表达式介绍]

    场景 lambda 表达式在非常多语言里都有一席之地,由于它的原因,能够在函数里高速定义一个便携的函数,或者在函数參数里直接高速构造和传递. 它能够说是匿名函数对象,一般仅仅适用于某个函数内,仅仅做暂 ...

  3. 「C++11」Lambda 表达式

    维基百科上面对于 lambda 的引入是如下描述的: 在标准 C++,特别是当使用 C++ 标准程序库算法函数诸如 sort 和 find.用户经常希望能够在算法函数调用的附近定义一个临时的述部函数( ...

  4. C++11之lambda表达式

    lambda表达式源于函数式编程的概念,它可以就地匿名定义目标函数或函数对象,不需要额外写一个命名函数或者函数对象.lambda表达式的类型在C++11中被称为"闭包类型",也可以 ...

  5. 11 JSP/EL表达式/EL函数

    JSP      * 概述: JSP(Java Server Pages)与Java Servlet一样,是在服务器端执行的不同的是先由服务器编译部署成Servlet执行      * JSP的运行原 ...

  6. C++11 constexpr常量表达式

    常量表达式函数 要求: 函数体内只有单一的return返回语句 例如: constexpr int data() { const int i=1; //含有除了return以外的语句 return i ...

  7. STL - C++ 11的Lambda表达式(上)

    Lambda始自C++ 11,是一种在表达式或语句内指定函数行为的定义式. 你可以定义函数行为作为对象,以inline实参的形式传给算法作为predicate(判断式). eg: std:transf ...

  8. linq 和lamba表达式

    一.什么是Linq(what)二.Linq的优点(why)三.Linq查询的步骤(how)四.查询基本操作五.結合實例代碼(具體聯繫用linqtosql來寫的增刪改查)一.什么是Linq(what). ...

  9. C++11 里lambda表达式的学习

    最近看到很多关于C++11的文档,有些是我不怎么用到,所以就略过去了,但是lambda表达式还是比较常用的,其实最开始学习python的时候就觉得lambda这个比较高级,为什么C++这么弱.果然C+ ...

随机推荐

  1. ABAP-计算器-动态表达式

    data:lv_value type string. call function 'EVAL_FORMULA' exporting formula = '90 <= 90' program = ...

  2. jquery循环方法

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  3. ACM__菜鸟之经典错误

    1:多组输入与单组输入 Input contains multiple test cases. Input contains a single test case. 2:  EOF=-1 while( ...

  4. Py designer 小程序实现实例

    前提 1.已安装Python(环境Win7+Python 2.7.12 ) 2.已安装插件pyqt4(插件获取路径:https://pan.baidu.com/s/1i50wAOH) 步骤 1.打开p ...

  5. HTTPConnectionPool(host:XX)Max retries exceeded with url

    爬虫多次访问同一个网站一段时间后会出现错误 HTTPConnectionPool(host:XX)Max retries exceeded with url '<requests.package ...

  6. 逻辑回归 logit

    [方法转]http://www.powerxing.com/logistic-regression-in-python/ http://blog.csdn.net/lipengcn/article/d ...

  7. centos7.5下yum 安装mariadb数据库

    前言 mariadb 和mysql就像亲兄弟的关系,各种语法.驱动啥的,在mysql上能上的,在mariadb上基本都可以直接使用.更多的细节在此不多说. 1.删除旧版本 centos7下默认安装有m ...

  8. JAVA回文

    package huiwen; import java.util.Scanner; public class Huiwen { public static void main(String[] arg ...

  9. cakephp 如何在一个模型里调用另一个模型

    $admin_users = ClassRegistry::init('AdminUsers'); $admin_name = $admin_users->find('list',array( ...

  10. React DevTools

    [React DevTools] 在应用商店搜索安装.F12可打开.如果是react应用,在最右排会显示react标签. 参考:https://github.com/facebook/react-de ...