• c++11 lambda表达式(二)



    前言

    上篇文章简单介绍了lambda表达式的使用,接下来简述lambda表达式的其他使用方法。
    c++11 lambda表达式(一)

    一、容器中使用lambda表达式

    lambda表达式对于容器中的算法实现非常方便,在c++11之前仿函数被广泛地用于STL中,同样的,在C++11中,lambda也在标准库中被广泛地使用。由于其书写简单,通常可以就地定义,因此用户常可以使用lambda代替仿函数来书写代码。

    下面代码是将 nums 中大于 ubound 变量 的 成员写入 large_nums 中 :

    #include 
    #include 
    #include 
    
    using namespace std;
    
    vector<int> nums = {12, 14, 32, 22, 5, 7, 2, 16};
    vector<int> large_nums;
    
    const int ubound = 10;
    
    void print_large_nums()
    {
        for(const auto &i : large_nums){
            cout<< i << " ";
        }
        cout << endl;
        large_nums.clear();
    }
    
    inline void large_nums_func(int i)
    {
        if(i > ubound)
        large_nums.push_back(i);
    }
    
    void above()
    {
    	//(1) 使用for循环,手写算法
        for(auto iter = nums.begin(); iter != nums.end(); ++iter){
            if(*iter >= ubound)
            large_nums.push_back(*iter);
        }
        print_large_nums();
    
    	//(2) 使用for_each容器算法和函数指针
        for_each(nums.begin(), nums.end(), large_nums_func);
        print_large_nums();
    
    	//(3) 使用for_each容器算法和lambda表达式
        for_each(nums.begin(), nums.end(), [=](int i){
            if(i > ubound)
            large_nums.push_back(i);
        });
        print_large_nums();
    }
    
    int main()
    {
        above();
    
        return 0;
    }
    

    结果显示:
    在这里插入图片描述

    for_each函数用来对容器中的每个元素都调用第三个参数,即function object,可以是一个函数指针、仿函数或者lambda表达式。其中for_each函数原型:

      /**
       *  @brief Apply a function to every element of a sequence.
       *  @ingroup non_mutating_algorithms
       *  @param  __first  An input iterator.
       *  @param  __last   An input iterator.
       *  @param  __f      A unary function object.
       *  @return   @p __f
       *
       *  Applies the function object @p __f to each element in the range
       *  @p [first,last).  @p __f must not modify the order of the sequence.
       *  If @p __f has a return value it is ignored.
      */
      template<typename _InputIterator, typename _Function>
        _Function
        for_each(_InputIterator __first, _InputIterator __last, _Function __f)
        {
          // concept requirements
          __glibcxx_function_requires(_InputIteratorConcept<_InputIterator>)
          __glibcxx_requires_valid_range(__first, __last);
          for (; __first != __last; ++__first)
    	__f(*__first);
          return __f; // N.B. [alg.foreach] says std::move(f) but it's redundant.
        }
    

    上述三种方式都能完成需求。对于第一中传统的for循环,我认为是代码最清晰的,但是使用for_each算法相较于手写的循环在效率、正确性、可维护性上都具有一定优势。最典型的,程序员不用关心iterator,或者说循环的细节,只需要设定边界,作用于每个元素的操作,就可以在近似“一条语句”内完成循环,正如函数指针版本和lambda版本完成的那样。

    因此传统的for循环和for_each算法相比较,应该偏向于使用for_each算法。

    而函数指针和lambda表达式,函数指针的方式看似简洁,不过却有很大的缺陷。第一点是函数定义在别的地方,比如很多行以前(后)或者别的文件中,这样的代码阅读起来并不方便。第二点则是出于效率考虑,使用函数指针很可能导致编译器不对其进行inline优化(inline对编译器而言并非强制),在循环次数较多的时候,内联的lambda和没有能够内联的函数指针可能存在着巨大的性能差别。因此,相比于函数指针,lambda拥有无可替代的优势。

    同时该算法比较简洁,只在该容器for_each算法中用到,其它地方不会用到,没必要定义成一个普通的函数,同时使用lambda还可以省去函数命名。就地定义,就地使用该匿名函数。

    二、map + function + lambda

    c++有几种可调用的函数对象:函数、函数指针、lambda表达式、bind创建的对象以及重载了函数调用运算符的类。

    两种不同类型的可调用对象可能共享同一种调用形式,调用形式指明了调用返回的类型以及传递给调用的实参类型。一种调用形式对应一种调用类,比如:

    int (int ,int)
    

    是一个函数类型,接受两个int 参数,返回一个int。

    对于不同类型的可调用对象共享同一种调用形式的情况,可以看成具有相同的类型。

    下面就是用map来定义一个函数表,用于存储指向这些可调用对象的指针。当需要执行某个特定的操作时,从表中查找该调用的函数,如下所示:

    #include 
    #include 
    #include 
    #include 
    
    using namespace std;
    
    //用户自定义add函数
    int add(int i, int j)
    {
        return i+j;
    }
    
    //用户自定义函数对象类
    struct divide
    {
        int operator()(int denominator, int divisor)
        {
            return denominator/divisor;
        }
    };
    
    //用户自定义lambad
    auto mod = [](int i, int j)
    {
        return i%j;
    };
    
    int main()
    {
        map<string, function<int (int ,int)>> int_ops = {
            {"+", add},  //用户自定义函数指针
            {"-", std::minus<int>()}, //标准库中的minus函数对象
            {"/", divide()}, //用户自定义的函数对象
            {"*", [](int i, int j) {return i * j ;}},  //未命名的lambda
            {"%", mod} //命名的lambda
        };
    
        cout << int_ops["+"](4, 2) << endl;
        cout << int_ops["-"](4, 2) << endl;
        cout << int_ops["/"](4, 2) << endl;
        cout << int_ops["*"](4, 2) << endl;
        cout << int_ops["%"](4, 2) << endl;
    
        return 0;
    }
    

    其中function是一个模板,当创建一个具体的类型时,需要提供一些信息,比如:

    function<int (int, int)>
    

    声明了一个function类型,它接受两个int参数,返回一个int的可调用对象,使用如下:

    #include 
    #include 
    #include 
    #include 
    
    using namespace std;
    
    int add(int i, int j)
    {
        return i+j;
    }
    
    struct divide
    {
        int operator()(int denominator, int divisor)
        {
            return denominator/divisor;
        }
    };
    
    auto mod = [](int i, int j)
    {
        return i%j;
    };
    
    int main()
    {
        function<int (int, int)> fun1 = add;
        function<int (int, int)> fun2 = std::minus<int>();
        function<int (int ,int)> fun3= divide();
        function<int (int, int)> fun4= [](int i, int j) {return i * j ;};
        function<int (int, int)> fun5 = mod;
    
        cout << fun1(4, 2) <<endl;
        cout << fun2(4, 2) <<endl;
        cout << fun3(4, 2) <<endl;
        cout << fun4(4, 2) <<endl;
        cout << fun5(4, 2) <<endl;
    
        return 0;
    }
    

    可以用 map+lambda 的方式来替换难以维护的 if/else/switch,可读性要比大量的分支语句好得多。

    总结

    lambda 表达式是一个闭包,能够像函数一样被调用,像变量一样被传递;
    可以使用 auto 自动推导类型存储 lambda 表达式,但 C++ 鼓励尽量就地匿名使用,缩小作用域;lambda 表达式使用“[=]”的方式按值捕获,使用“[&]”的方式按引用捕获,空的“[]”则是无捕获(也就相当于普通函数);
    捕获引用时必须要注意外部变量的生命周期,防止变量失效;
    C++14 里可以使用泛型的 lambda 表达式,相当于简化的模板函数。

    lambda表达式对于只在某个函数重复的一小块代码段,其它地方也不会用到该代码段,对于这一小块代码段又没必要封装成一个函数,同时避免写重复的代码,lambda表达式在这样的场景十分方便。

    匿名的内联函数对象,lambda由类的operator重载而来,闭包,把数据和逻辑打包传递。

    参考资料

    C++ Primer中文版
    极客时间:罗剑锋的 C++ 实战笔记
    深入理解C++11:C++11新特性解析与应用

  • 相关阅读:
    Egg.js 中 Service 的使用
    【信息检索与数据挖掘】期末笔记(一)
    关于yii2 hasOne()、hasMany()用法,参数说明,注意事项
    【Linux私房菜】第五期 —— 定时任务与磁盘
    微信支付后页面跳转
    HTML5期末大作业:用DIV+CSS技术设计的网页与实现(剪纸传统文化网页设计主题)
    Java小树的参天成长(this关键字以及使用方法)
    Windows10下Vmware开机蓝屏解决办法,亲测有效
    341.扁平化嵌套列表迭代器 | N叉树的最大深度
    CASS11.0.0.4 for AutoCAD2010-2023免狗使用方法
  • 原文地址:https://blog.csdn.net/weixin_45030965/article/details/126956002