C++11:lambda表达式详细介绍

优点如下:

  • 声明式编程风格:就地匿名定义目标函数或函数对象,有更好的可读性和可维护性。
  • 简洁:不需要额外写一个命名函数或函数对象,,避免了代码膨胀和功能分散。
  • 更加灵活:在需要的时间和地点实现功能闭包。

概念及基本用法

[ capture ] ( params ) opt -> ret { body; };
  • capture:捕获列表
  • params:参数列表
  • opt:函数选项
  • ret:返回值类型
  • body:函数体

一个完整的lambda表达式是这样:

auto f = [](int a) -> int {return a + 1;};
cout << f(3) << endl;  //&#x8F93;&#x51FA;4

以上定义了一个完整的lambda,但是在实际的使用中,可以省略其返回值的定义,编译器会根据return语句进行自动推导返回值类型。
省略过后如下:

auto f = [](int a) {return a + 1;};

需要注意的是,初始化列表不能用于返回值的自动推导:
如: auto f = [](){return {1,2};}; //error:&#x65E0;&#x6CD5;&#x63A8;&#x5BFC;&#x8FD4;&#x56DE;&#x503C;&#x7C7B;&#x578B;

另外,如果表达式没有参数列表时,也可以省略,如:

auto f = []{return 1;};

捕获变量

lambda表达式可以通过捕获列表捕获一定范围内的变量,主要有以下几种情况:

  • [] 不捕获任何变量
  • [&]捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获)
  • [=]捕获外部作用域中所有变量,并作为副本在函数体重使用(按值捕获)
  • [=,&foo] 按值捕获外部作用域中所有变量,并按引用捕获foo变量
  • [bar] 按值捕获bar变量,同时不捕获其他变量
  • [this] 捕获当前类中的this指针,让表达式拥有和当前类成员函数同样的访问权限。如果已经使用了&或者=,就默认添加此选项。捕获this的目的是可以在lambda中使用当前类的成员变量和成员函数。

通过示例来看具体用法:

class A
{
public:
    int i_ = 0;

    void func(int x,int y)
    {
        auto x1 = []{return i_;};  // error,&#x6CA1;&#x6709;&#x6355;&#x83B7;&#x5916;&#x90E8;&#x53D8;&#x91CF;
        auto x2 = [=]{return i_ + x + y;}; //ok&#xFF0C;&#x6309;&#x503C;&#x6355;&#x83B7;&#x6240;&#x6709;&#x5916;&#x90E8;&#x53D8;&#x91CF;
        auto x3 = [&]{return i_ + x + y;}; //ok&#xFF0C;&#x6309;&#x5F15;&#x7528;&#x6355;&#x83B7;&#x6240;&#x6709;&#x5916;&#x90E8;&#x53D8;&#x91CF;
        auto x4 = [this]{return i_;}; //ok&#xFF0C;&#x6355;&#x83B7;this&#x6307;&#x9488;
        auto x5 = [this]{return i_ + x + y;}; //error,&#x6CA1;&#x6709;&#x6355;&#x83B7;x&#x548C;y&#x53D8;&#x91CF;
        auto x6 = [this,x,y]{return i_ + x + y;}; //ok&#xFF0C;&#x6355;&#x83B7;&#x4E86;this&#x6307;&#x9488;&#x548C;x&#x3001;y&#x53D8;&#x91CF;
        auto x7 = [this]{return i_++;}; //ok&#xFF0C;&#x6355;&#x83B7;&#x4E86;this&#x6307;&#x9488;&#xFF0C;&#x4FEE;&#x6539;&#x6210;&#x5458;&#x53D8;&#x91CF;&#x7684;&#x503C;
    }
};
int a = 0 , b = 0 ;
auto f1 = []{return a;}; // error,&#x6CA1;&#x6709;&#x6355;&#x83B7;&#x5916;&#x90E8;&#x53D8;&#x91CF;
auto f2 = [&]{return a++;}; //ok&#xFF0C;&#x6355;&#x83B7;&#x6240;&#x6709;&#x5916;&#x90E8;&#x53D8;&#x91CF;&#xFF0C;&#x5E76;&#x5BF9;a&#x53D8;&#x91CF;&#x81EA;&#x52A0;
auto f3 = [=]{return a;}; //ok&#xFF0C;&#x6355;&#x83B7;&#x6240;&#x6709;&#x5916;&#x90E8;&#x53D8;&#x91CF;&#xFF0C;&#x5E76;&#x8FD4;&#x56DE;a
auto f4 = [=]{return a++;}; //error,a&#x53D8;&#x91CF;&#x662F;&#x4EE5;&#x590D;&#x5236;&#x65B9;&#x5F0F;&#x6355;&#x83B7;&#x7684;&#xFF0C;&#x4E0D;&#x80FD;&#x4FEE;&#x6539;
auto f5 = [a]{return a+b;}; //error,&#x6CA1;&#x6709;&#x6355;&#x83B7;b&#x53D8;&#x91CF;
auto f6 = [a,&b]{return a+ (b++);}; //ok&#xFF0C;&#x6355;&#x83B7;a&#x4EE5;&#x53CA;b&#x7684;&#x5F15;&#x7528;&#xFF0C;&#x5BF9;b&#x8FDB;&#x884C;&#x81EA;&#x52A0;
auto f7 = [=,&b]{return a+ (b++);}; //ok, &#x6355;&#x83B7;&#x6240;&#x6709;&#x5916;&#x90E8;&#x53D8;&#x91CF;&#x548C;b&#x7684;&#x5F15;&#x7528;&#xFF0C;&#x5BF9;b&#x8FDB;&#x884C;&#x81EA;&#x52A0;

需要注意的是,lambda无法修改按值捕获的外部变量,如果需要修改外部变量,可以通过引用方式捕获。

关于lambda表达式的 延迟调用很容易出错,如下:

int a = 0;
auto f = [=]{return a;};
a += 1;
cout << f() << endl;

以上示例中,lambda按值捕获了所有外部变量,在捕获的时候 a的值就已经被复制到 f 中了,之后a被修改,但是f里面存储的a仍然是捕获时的值,所以最终输出的是 0.

如果希望lambda表达式在调用的时候能够访问外部变量,需要使用引用方式捕获。

所以简单来说,按值捕获,外部变量会被复制一份存储在lambda表达式变量中。

如果是按值捕获并且又想修改外部变量,可以显示指明lambda表达式为mutable:

int a = 0;
auto f1 = [=]{return a++;};  //error,&#x4FEE;&#x6539;&#x6309;&#x503C;&#x6355;&#x83B7;&#x7684;&#x5916;&#x90E8;&#x53D8;&#x91CF;
auto f2 = [=]() mutable {return a++;}; //ok

被mutable修饰的lambda表达式就算没有参数也要写明参数列表。

lambda表达式类型

lambda表达式的类型在C++11中被称为”闭包类型”,它是一个特殊的,匿名的非nunion的类型。

可以认为它是带有一个operator()的类,即仿函数。
我们可以通过std::function和std::bind来存储和操作lambda表达式:

std::function<int(int)> f1 = [](int a){return a;};
std::function<int(void)> f2 = std::bind([](int a){return a;},123);</int(void)></int(int)>

另外,对于没有捕获任何变量的lambda表达式,还可以被转换成一个普通的函数指针:

using func_t = int(*)(int);
func_t f = [](int a){return a;};
f(123);

lambda可以说是就地定义仿函数闭包的”语法 糖”。它的捕获列表捕获住任何外部变量,最终都会变为闭包类型的成员变量。而一个成员变量的类的operator(),如果能直接被转换为普通的函数指针,那么lambda表达式本身的this指针就丢掉了。而没有捕获任何外部变量的lambda表达式则不存在这个问题。

需要注意的是,没有捕获变量的lambda表达式可以直接转换为函数指针,而捕获变量的lambda表达式则不能转换为函数指针。如下:

typedef void(*Ptr)(int*);
Ptr p = [](int *p){delete p;};  //ok
Ptr p1 = [&](int *p){delete p;}; //error

前面说到的按值捕获无法修改捕获的外部变量,因为按照C++标准,lambda表达式的operator()默认是const的,一个const成员函数是无法修改成员变量的值,而mutable的作用,就是取消operator()的const限制。

声明式的编程风格

通过示例来看一下lambda的使用,在C++11之前,如果要用for_each函数将数组中的偶数数量打印出来,代码如下:

#include <vector>
#include <algorithm>
class Count
{
public:
    Count(int &val):num(val){}
    void operator()(int val){
        if(!(val & 1)){
            ++num;
        }
    }
private:
    int #
};

int main()
{
    std::vector<int> v = {1,2,3,4,5,6,7};
    int count = 0;
    for_each(v.begin(),v.end(),Count(count));
    std::cout << count << endl;
    return 0;
}</int></algorithm></vector>

如果使用lambda表达式,就可以简化一下,真正使用闭包概念来替换这里的仿函数。

#include <vector>
#include <algorithm>

int main()
{
    std::vector<int> v = {1,2,3,4,5,6,7};
    int count = 0;
    for_each(v.begin(),v.end(),[&count](int val){
        if(!(val & 1)){
            ++count;
        }
    });

    std::cout << count << endl;
    return 0;
}</int></algorithm></vector>

lambda表达式的价值在于,就地封装短小的功能闭包,方便地表达出我们希望执行的具体操作,并让上下文结合的更加紧,代码更加简洁,更灵活,也提高了开发效率及可维护性。

参考:《深入应用C++11》

Original: https://www.cnblogs.com/lidabo/p/16506310.html
Author: DoubleLi
Title: C++11:lambda表达式详细介绍

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/535410/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • C++中的三种继承关系

    先看类中声明成员时的三种访问权限 public : 可以被任意实体访问 protected : 只允许子类及本类的成员函数访问 private : 只允许本类的成员函数访问 在类继…

    C++ 2023年5月29日
    070
  • C++ STL std::copy 详解

    std::copy(start, end, std::back_inserter(container)); 这里,start和end是输入序列(假设有N个元素)的迭代器(itera…

    C++ 2023年5月29日
    064
  • c++自定义排序_lambda表达式

    class Solution { void quickSort(vector& strs, int l, int r) { if (l >= r) return; i…

    C++ 2023年5月29日
    049
  • C++引用 学习心得

    参数的传递本质上是一次赋值的过程,赋值就是对内存进行拷贝。所谓内存拷贝,是指将一块内存上的数据复制到另一块内存上。 对于像 char、bool、int、float 等基本类型的数据…

    C++ 2023年5月29日
    069
  • Delphi XE8,C++ Builder XE8,RAD Studio XE8 官方 ISO 文件下载,附激活工具

    RAD Studio XE8 v22.0.19027.8951 官方ISO下载(6.72G):http://altd.embarcadero.com/download/radstu…

    C++ 2023年5月29日
    078
  • C++实现的各种排序算法

    提起排序算法相信大家都不陌生,或许很多人已经把它们记得滚瓜烂熟,甚至随时可以写出来。最近在学习这一块, 索性就把各种内部排序算法总结归纳了一下: 1、 算法分类: 十种常见排序算法…

    C++ 2023年5月29日
    050
  • C#与C/C++的交互

    C#与C/C++的交互 最近在编写Warensoft3D游戏引擎,并预计明年年初发布测试版本,底层引擎使用DirectX和MONO来编写,上层的逻辑使用C#来编写,因此编写了大量C…

    C++ 2023年5月29日
    063
  • C#与c++对应的类型

    C#与c++对应的类型 csharp;gutter:true; C#调用C++的DLL搜集整理的所有数据类型转换方式-转载</p> <pre><cod…

    C++ 2023年5月29日
    041
  • C++11 列表初始化

    在我们实际编程中,我们经常会碰到变量初始化的问题,对于不同的变量初始化的手段多种多样,比如说对于一个数组我们可以使用 int arr[] = {1,2,3}的方式初始化,又比如对于…

    C++ 2023年5月29日
    053
  • [图形图像]C++实现的软件光栅器

    第1张: 从坐到右,从上到下。无纹理、无镜面光、无深度缓冲,依次是: 线框。 纯色。 即每个三角形一个颜色。 flat着色(相比上次,修正后的效果好多了)。也是每个三角形一个颜色,…

    C++ 2023年5月29日
    068
  • C++11智能指针处理Array对象

    //C++11的//完全可以避免写手动的delete代码,//但是它默认使用delete删除对象,//如果是数组对象,需要指定自定义的删除方法,支持delete[]std::sha…

    C++ 2023年5月29日
    042
  • EclipseC++学习笔记-5 隐藏cmd窗口

    每次启动eclipse后,会有一个cmd窗口,很影响整洁解决办法示例:powershell.exe -WindowStyle Hidden -c wsl — /root…

    C++ 2023年5月29日
    068
  • 【C++服务端技术】对象池

    代码没贴全,就少一个锁头文件,可以做设计参考 设计思想就是维护一个空闲链表,没有用的就重新申请,有的话就拿链表的头,使用完又还给空闲链表。 /* 一个分配固定大小内存的内存池,使用…

    C++ 2023年5月29日
    068
  • C#与C++之间类型的对应

    Windows Data Type .NET Data Type BOOL, BOOLEAN Boolean or Int32 BSTR String BYTE Byte CHAR…

    C++ 2023年5月29日
    084
  • 解决c++中delete后内存系统不回收

    一般new出来的内存,delete掉后。 此时如果看top内存没有减少,则可以使用下面函数让系统强制回收。 #include malloc_trim(0); Original: h…

    C++ 2023年5月29日
    081
  • [转][c++11]我理解的右值引用、移动语义和完美转发

    c++中引入了 &#x53F3;&#x503C;&#x5F15;&#x7528;和 &#x79FB;&#x52A8;&#x8…

    C++ 2023年5月29日
    064
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球