zoukankan      html  css  js  c++  java
  • lambda表达式功能及相关使用介绍

    lambda表达式

    用途

    lambda 表达式定义了一个匿名函数,并且可以捕获一定范围内的变量。lambda 表达式的语法形式可简单归纳如下:

    [ capture ] ( params ) opt -> ret { body; };

    其中 capture 是捕获列表,params 是参数表,opt 是函数选项,ret 是返回值类型,body是函数体。

    因此,一个完整的 lambda 表达式看起来像这样:

    auto f = [](int a) -> int { return a + 1; };
    std::cout << f(1) << std::endl;  	// 输出: 2
    

    优点

    lambda 来源于函数式编程的概念,也是现代编程语言的一个特点。C++11 这次终于把 lambda 加进来了。

    lambda表达式有如下优点:

    • 声明式编程风格:就地匿名定义目标函数或函数对象,不需要额外写一个命名函数或者函数对象。以更直接的方式去写程序,好的可读性和可维护性。
    • 简洁:不需要额外再写一个函数或者函数对象,避免了代码膨胀和功能分散,让开发者更加集中精力在手边的问题,同时也获取了更高的生产率。
    • 在需要的时间和地点实现功能闭包,使程序更灵活。

    使用 lambda 表达式捕获列表

    lambda 表达式还可以通过捕获列表捕获一定范围内的变量:

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

    示例

    class A
    {
        public:
        int i_ = 0;
        void func(int x, int y)
        {
            auto x1 = []{ return i_; };                    // error,没有捕获外部变量
            auto x2 = [=]{ return i_ + x + y; };           // OK,捕获所有外部变量
            auto x3 = [&]{ return i_ + x + y; };           // OK,捕获所有外部变量
            auto x4 = [this]{ return i_; };                // OK,捕获this指针
            auto x5 = [this]{ return i_ + x + y; };        // error,没有捕获x、y
            auto x6 = [this, x, y]{ return i_ + x + y; };  // OK,捕获this指针、x、y
            auto x7 = [this]{ return i_++; };              // OK,捕获this指针,并修改成员的值
        }
    };
    int a = 0, b = 1;
    auto f1 = []{ return a; };               // error,没有捕获外部变量
    auto f2 = [&]{ return a++; };            // OK,捕获所有外部变量,并对a执行自加运算
    auto f3 = [=]{ return a; };              // OK,捕获所有外部变量,并返回a
    auto f4 = [=]{ return a++; };            // error,a是以复制方式捕获的,无法修改
    auto f5 = [a]{ return a + b; };          // error,没有捕获变量b
    auto f6 = [a, &b]{ return a + (b++); };  // OK,捕获a和b的引用,并对b做自加运算
    auto f7 = [=, &b]{ return a + (b++); };  // OK,捕获所有外部变量和b的引用,并对b做自加运算
    

    实际应用

    实际lambda表达式使用时,不会像上文里那样使用,显得多此一举,通常用于泛型编程、回调函数注册等场景。比如常配合std::for_each、std::count_if等函数模板使用,使代码更简洁优雅;也经常和std::function、std::bind一起使用。std::for_each、std::count_if、std::function、std::bind介绍可见下文。

    参考

    C++11 lambda表达式精讲

    std::for_each

    用途

    template< class InputIt, class UnaryFunction >
    UnaryFunction for_each( InputIt first, InputIt last, UnaryFunction f );
    

    按顺序应用给定的函数对象 f 到解引用范围 [first, last) 中每个迭代器的结果。

    示例

    #include <iostream>
    #include <vector>
    #include <algorithm>
    using namespace std;
    
    void myPrint(int i){ // function:
        std::cout << i << ' ';
    }
    
    struct myPrintClass{ // function object type:
        void operator()(int i) { std::cout << i << " "; }
    };
    
    int main()
    {
        vector<int> myVector{1,2,3,4}; //初始化列表
        int flags = 2;
    
        std::cout << "my vector contains:" << std::endl;
    
        //usage1: 不使用for each
        for (auto itor = myVector.begin(); itor != myVector.end(); ++itor)    {
            cout << *itor << ' ';
        }
        std::cout << std::endl;
    
        //usage2: 通过函数使用for each
        for_each(myVector.begin(), myVector.end(), myPrint);
        std::cout << std::endl;
    
        //usage3: 通过仿函数对象使用for each
        myPrintClass myPrintObj = myPrintClass();
        for_each(myVector.begin(), myVector.end(), myPrintObj);
        std::cout << '\n';
    
        //usage4: 通过lambda表达式使用for each
        //不仅代码更连贯,而且可以通过捕获功能,获取其他参数,如本处示例中的flag。
        //为比较综合、全面的方法.
        for_each(myVector.begin(), myVector.end(), [flags](int var)
                 { std::cout << var * flags << ' '; });
        std::cout << '\n';
    }
    

    std::count_if

    用途

    Returns the number of elements in the range [**first,last**) for which pred is true.

    其等价的简要实现(非完整版):

    template <class InputIterator, class UnaryPredicate>
      typename iterator_traits<InputIterator>::difference_type
        count_if (InputIterator first, InputIterator last, UnaryPredicate pred)
    {
      typename iterator_traits<InputIterator>::difference_type ret = 0;
      while (first!=last) {
        if (pred(*first)) ++ret;
        ++first;
      }
      return ret;
    }
    

    参数

    first, last

    Input iterators to the initial and final positions of the sequence of elements. The range used is [first,last), which contains all the elements between first and last, including the element pointed by first but not the element pointed by last.

    pred

    Unary function that accepts an element in the range as argument, and returns a value convertible to bool. The value returned indicates whether the element is counted by this function.
    The function shall not modify its argument.
    This can either be a function pointer or a function object.

    示例

    // count_if example
    #include <iostream>  // std::cout
    #include <algorithm> // std::count_if
    #include <vector>    // std::vector
    
    bool IsOdd(int i) { return ((i % 2) == 1); }
    
    int main()
    {
        std::vector<int> myvector;
        for (int i = 1; i < 10; i++)
            myvector.push_back(i); // myvector: 1 2 3 4 5 6 7 8 9
    
        //默认用法
        int mycount1 = count_if(myvector.begin(), myvector.end(), IsOdd);
    
        //配合lambda更为整体化
        int mycount2 = count_if(myvector.begin(), myvector.end(), [](int val)
                                { return (val % 2) == 1; });
    
        std::cout << "myvector contains " << mycount1 << " odd values.\n";
        std::cout << "myvector contains " << mycount2 << " odd values.\n";
    
        return 0;
    }
    

    std::function

    用途

    ​ C++中可调用对象的虽然都有一个比较统一的操作形式,但是定义方法五花八门,这样就导致使用统一的方式保存可调用对象或者传递可调用对象时,会十分繁琐。C++11中提供了std::function和std::bind统一了可调用对象的各种操作。

    示例

    不同类型可能具有相同的调用形式,如:

    // 普通函数
    int add(int a, int b){return a+b;} 
    
    // lambda表达式
    auto mod = [](int a, int b){ return a % b;}
    
    // 函数对象类
    struct divide{
        int operator()(int denominator, int divisor){
            return denominator/divisor;
        }
    };
    

    上述三种可调用对象虽然类型不同,但是共享了一种调用形式:

    int(int ,int)
    

    std::function就可以将上述类型保存起来,如下:

    std::function<int(int ,int)>  a = add; 
    std::function<int(int ,int)>  b = mod ; 
    std::function<int(int ,int)>  c = divide(); 
    

    参考

    C++11 中的std::function和std::bind

    std::bind

    用途

    可将std::bind函数看作一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。

    std::bind将可调用对象与其参数一起进行绑定,绑定后的结果可以使用std::function保存。std::bind主要有以下两个作用:

    • 将可调用对象和其参数绑定成一个防函数;
    • 只绑定部分参数,减少可调用对象传入的参数。

    示例

    绑定普通函数

    #include <iostream>
    #include <functional>
    
    void print_sub(int n1, int n2, int n3) { std::cout << n1 - n2 - n3 << '\n';}
    
    int main() 
    {
        auto f1 = std::bind(print_sub, 10, std::placeholders::_1, std::placeholders::_2); 
        auto f2 = std::bind(print_sub, std::placeholders::_1, 10, std::placeholders::_2); 
        auto f3 = std::bind(print_sub, std::placeholders::_1, std::placeholders::_2, 10);
    
        f1(5,6); // 等价于 10 - 5 -6
        f2(5,6); // 等价于 5 - 10 -6
        f3(5,6); // 等价于 5 - 6 - 10
    }
    

    绑定一个引用参数

    默认情况下,bind的那些不是占位符的参数被拷贝到bind返回的可调用对象中。但是,与lambda类似,有时对有些绑定的参数希望以引用的方式传递,或是要绑定参数的类型无法拷贝,就必须使用标准库提供的ref函数。关于std::ref的功能个人理解为可以把类似强转为引用。

    #include <iostream>
    #include <functional>
    #include <vector>
    #include <algorithm>
    #include <sstream>
    using namespace std::placeholders;
    using namespace std;
    
    ostream & print(ostream &os, const string& s, char c)
    {
        os << s << c;
        return os;
    }
    
    int main()
    {
        vector<string> words{"helo", "world", "this", "is", "C++11"};
        ostringstream os;
        char c = ' ';
        for_each(words.begin(), words.end(), 
                       [&os, c](const string & s){os << s << c;} );
        cout << os.str() << endl;
    
        ostringstream os1;
        // ostream不能拷贝,若希望传递给bind一个对象,
        // 而不拷贝它,就必须使用标准库提供的ref函数
        for_each(words.begin(), words.end(),
                       bind(print, ref(os1), _1, c));
        cout << os1.str() << endl;
    }
    

    参考

    C++11 中的std::function和std::bind

  • 相关阅读:
    关于异步IO与同步IO的写操作区别
    慢慢开始记录一些技术心得吧
    写了placement new就要写placement delete
    关于针对class自定义new操作符失败的函数处理
    operator->和operator->*
    关于继承中的拷贝构造函数
    关于g++编译模板类的问题
    关于互斥锁,条件变量的内核源码解析
    关于sigwait
    观察者设计模式
  • 原文地址:https://www.cnblogs.com/joyer/p/15629401.html
Copyright © 2011-2022 走看看