VC驿站

 找回密码
 加入驿站

QQ登录

只需一步,快速开始

有编程疑问吗?还请到提问专区发帖提问!
搜索
查看: 282|回复: 1

[转载] C++ 11中关于Lambda表达式(匿名函数)

[复制链接]
51_avatar_middle
online_admins Syc 发表于 2018-3-1 12:09:46 | 显示全部楼层 |阅读模式
C++ 98/03
C++ 98/03标准并不原生支持匿名函数。不过可以利用Boost库的Boost.Lambda来实现一个匿名函数。

C++11
初步了解:
很多语言都提供了 lambda 表达式,如 Python,Java 8。lambda 表达式可以方便地构造匿名函数,如果你的代码里面存在大量的小函数,而这些函数一般只被调用一次,那么不妨将他们重构成 lambda 表达式。

C++11 的 lambda 表达式规范如下:
[ capture ](params )mutableexception attribute -> ret{body } (1)
[ capture ](params )->ret {body} (2)
[ capture ](params ){body } (3)
[ capture ]{body } (4)


其中:
(1) 是完整的 lambda 表达式形式,
(2) const 类型的 lambda 表达式,该类型的表达式不能改捕获("capture")列表中的值。
(3)省略了返回值类型的 lambda 表达式,但是该 lambda 表达式的返回类型可以按照下列规则推演出来:
如果 lambda 代码块中包含了 return 语句,则该 lambda 表达式的返回类型由 return 语句的返回类型确定。
如果没有 return 语句,则类似 void f(...) 函数。
(4)省略了参数列表,类似于无参函数 f()。
mutable 修饰符说明 lambda 表达式体内的代码可以修改被捕获的变量,并且可以访问被捕获对象的 non-const 方法。

exception 说明 lambda 表达式是否抛出异常(noexcept),以及抛出何种异常,类似于void f()throw(X, Y)。
attribute 用来声明属性。
另外,capture 指定了在可见域范围内 lambda 表达式的代码内可见得外部变量的列表,具体解释如下:

另外,capture 指定了在可见域范围内 lambda 表达式的代码内可见得外部变量的列表,具体解释如下:

[a,&b] a变量以值的方式被捕获,b以引用的方式被捕获。
[this] 以值的方式捕获 this 指针。
[&] 以引用的方式捕获所有的外部自动变量。
[=] 以值的方式捕获所有的外部自动变量。
[] 不捕获外部的任何变量。

1、空。没有使用任何函数对象参数。
2、=。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。
3、&。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量)。
4、this。函数体内可以使用Lambda所在类中的成员变量。
5、a。将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的。要修改传递进来的a的拷贝,可以添加mutable修饰符。
6、&a。将a按引用进行传递。
7、a, &b。将a按值进行传递,b按引用进行传递。
8、=,&a, &b。除a和b按引用进行传递外,其他参数都按值进行传递。
9、&, a, b。除a和b按值进行传递外,其他参数都按引用进行传递。

此外,params 指定 lambda 表达式的参数。

详解:

C++11标准提供了匿名函数的支持,在《ISO/IEC 14882:2011》(C++11标准文档)中叫做lambda表达式[10]。一个lambda表达式有如下的形式:

  1. [capture] (parameters) mutable exception attribute -> return_type { body }
复制代码

必须用方括号括起来的capture列表来开始一个lambda表达式的定义。

lambda函数的形参表比普通函数的形参表多了3条限制:

参数不能有缺省值
不能有可变长参数列表
不能有无名参数
如果lambda函数没有形参且没有mutable、exception或attribute声明,那么参数的空圆括号可以省略。但如果需要给出mutable、exception或attribute声明,那么参数即使为空,圆括号也不能省略。

如果函数体只有一个return语句,或者返回值类型为void,那么返回值类型声明可以被省略:
  1. [capture](parameters){body}
复制代码

一个lambda函数的例子如下:
  1. [](int x, int y) { return x + y; } // 從return語句中隱式獲得的返回值類型
  2. [](int& x) { ++x; }   // 沒有return語句 -> lambda函數的返回值為void
  3. []() { ++global_x; }  // 沒有參數,僅僅是訪問一個全局變量
  4. []{ ++global_x; }     // 與前者相同,()可以被省略
复制代码

这个无名函数的返回值是decltype(x+y) (在上面的第一个例子中)。如果lambda函数体的形式是returnexpression,或者什么也每返回,或者所有返回语句用decltype都能检测到同一类型,那么返回值类型可以被省略。

返回值类型可以显式指定,如下所示:
  1. [](int x, int y) -> int { int z = x + y; return z; }
复制代码

在这个例子中,一个临时变量,z,被创建来储存中间过程。与一般的函数一样,中间值在调用的前后并不存在。什么也没有返回的lambda表达式无需显式指定返回值,没有必要写-> void代码。

lambda函数可以捕获lambda函数外的具有automatic storage duration的变量。函数体与这些变量的集合合起来称做闭包。这些外部变量在声明lambda表达式时列在在方括号[和]中。空的方括号表示没有外界变量被capture。这些变量被传值捕获或者引用捕获。对于传值捕获的变量,默认为只读。修改这些变量将导致编译报错。但在lambda表达式的参数表的圆括号后面使用mutable关键字,就允许lambda函数体内的语句修改传值引用的变量,这些修改与lambda表达式(实际上是用函数对象实现)有相同的生命期,但不影响被传值捕获的外部变量的值。lambda函数可以直接使用具有static存储期的变量。如果在lambda函数的捕获列表中给出了static存储期的变量,编译时会给出警告,仍然按照lambda函数直接使用这些外部变量来处理。因此具有static存储期的变量即使被声明为传值捕获,修改该变量实际上直接修改了这些外部变量。编译器生成lambda函数对应的函数对象时,不会用函数对象的数据成员来保持被“捕获”的static存储期的变量。示例:

  1. []        // 沒有定義任何變量,但必须列出空的方括号。在Lambda表達式中嘗試使用任何外部變量都會導致編譯錯誤。
  2. [x, &y]   // x是按值傳遞,y是按引用傳遞
  3. [&]       // 任何被使用到的外部變量都按引用傳入。
  4. [=]       // 任何被使用到的外部變量都按值傳入。
  5. [&, x]    // x按值傳入。其它變量按引用傳入。
  6. [=, &z]   // z按引用傳入。其它變量按值傳入。
复制代码


下面这个例子展示了lambda表达式的使用:
  1. std::vector<int> some_list{ 1, 2, 3, 4, 5 };
  2. int total = 0;
  3. std::for_each(begin(some_list), end(some_list),
  4.                  [&total](int x) {  total += x; }
  5.               );
复制代码

在类的非静态成员函数中定义的lambda表达式可以显式或隐式捕捉this指针,从而可以引用所在类对象的数据成员与函数成员。

lambda函数的函数体中,可以访问下述变量:

■ 函数参数
■ 局部声明的变量
■ 类数据成员(当函数声明在类中)
■ 具有静态存储期的变量(如全局变量)
■ 被捕获的外部变量
    ■ 显式捕获的变量
    ■ 隐式捕获的变量,使用默认捕获模式(传值或引用)来访问。
lambda函数的数据类型是函数对象,保存时必须用std::function模板类型,或用auto关键字。例如:

  1. #include <functional>
  2. #include <vector>
  3. #include <iostream>

  4. double eval(std::function <double(double)> f, double x = 2.0)
  5. {
  6.         return f(x);
  7. }

  8. int main()
  9. {
  10.         std::function<double(double)> f0    = [](double x){return 1;};
  11.         auto                          f1    = [](double x){return x;};
  12.         decltype(f0)                  fa[3] = {f0,f1,[](double x){return x*x;}};
  13.         std::vector<decltype(f0)>     fv    = {f0,f1};
  14.         fv.push_back                  ([](double x){return x*x;});
  15.         for(int i=0;i<fv.size();i++)
  16.                 std::cout << fv[i](2.0) << std::endl;
  17.         for(int i=0;i<3;i++)
  18.                 std::cout << fa[i](2.0) << std::endl;
  19.         for(auto &f : fv)
  20.                 std::cout << f(2.0) << std::endl;
  21.         for(auto &f : fa)
  22.                 std::cout << f(2.0) << std::endl;
  23.         std::cout << eval(f0) << std::endl;
  24.         std::cout << eval(f1) << std::endl;
  25.         std::cout << eval([](double x){return x*x;}) << std::endl;
  26.         return 0;
  27. }
复制代码


一个lambda函数的捕捉表达式为空,则可以用普通函数指针存储或调用。例如:

  1. auto a_lambda_func = [](int x) { /*...*/ };
  2. void (* func_ptr)(int) = a_lambda_func;
  3. func_ptr(4); //calls the lambda.
复制代码




上一篇:浅谈VC++中C语言函数入栈出栈的实现
下一篇:函数调用的开销

发帖求助前要善用论坛搜索功能,那里可能会有你要找的答案;

如果你已经在论坛发帖求助,并且从坛友或者管理的回复中解决了问题,请编辑帖子并把分类改成【已解决】

如何回报帮助你解决问题的坛友?可以给对方加【热心】【驿站币】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

您需要登录后才可以回帖 登录 | 加入驿站 qq_login

本版积分规则

关闭

站长提醒上一条 /1 下一条

QQ
QQ在线咨询
联系电话
13591366679
手机扫一扫 关注本站精彩内容
wxqrcode

QQ|小黑屋|手机版|VC驿站 ( 辽ICP备09019393号tongdun|网站地图wx_jqr

GMT+8, 2018-10-16 04:24

Powered by Discuz! X3.4

© 2009-2018 cctry.com

快速回复 返回顶部 返回列表