• 第17课 lambda表达式


    一. lambda表达式

    (一)语法定义[capture](paramters) mutable ->returnType{statement}

      1.[capture]:捕获列表

      (1)lambda函数只能捕获父作用域中的局部变量或形参。而捕获非父作用域或静态变量则会出错。(这里的父作用域指的是包含lambda函数的语句块,如main函数作用域)

        ①[]:表示不捕获;[=]和[&]:分别表示按值和按引用捕获所有父作域变量(包括this);

        ②[var]、[&var]分别表示按值和按引用捕获var。注意,默认是无法修改按值捕获的变量的值(因为lambda表达式的operator()默认为const)。

        ③[=,&foo]:表示按引用捕获foo变量,按值捕获父作用域中所有其它变量。

        ④[this]:捕获当前类中的this指针,让lambda表达式拥有和当前类成员函数同样访问权限。捕获this的目的是可以在lambda中会使用当前类的成员函数和成员变量。

      (2)注意事项:

        ①捕获列表不允许变量重复传递。如[=,a]、[&,&this]其中的a和this都被重复传递。

        ②lambda表达式的按值捕获,是在声明lambda表达式的一瞬间就被复制了如果希望lambda表达式在调用时能即时的访问外部变量,应该使用按引用捕获

        ③默认情况下,按值捕获的变量是不可以被修改的,因为lambda表达式的operator()是个const函数。

        ④lambda不能捕获非父作用域变量或static变量。即它们不能被进入捕获列表中,但可在lambda的函数体内直接访问。

        2.(parameters):参数列表。

      (1)与普通函数的参数列表一致。如果不需要参数传递,则可以连同括号()一起省略。

      (2)参数列表不支持默认值,也不支持可变参数。所有的参数必须有参数名。

      (3)C++14中,参数类型可以声明为auto类型

        3.mutable默认下,lambda函数总是一个const函数,mutable可以取消其常量性。在使用该修饰符时,参数列表不可省略(即使参数为空)

        4.returnType返回值类型。用追踪返回类型形式声明函数的返回类型。在返回类型明确的情况下,也可以省略该部分,让编译器对返回类型进行自动推导。如果没有return语句则返回void。

        5.{statement}函数体。内容与普通函数一样,除了可以使用参数之外,还可以使用所有捕获的变量。

    (二)lambda表达式与仿函数

     

        1. 仿函数是编译器实现lambda表达式的一种方式。在现阶段,通常编译器都会把lambda表达式转化成为一个仿函数对象。因此,在C++11中,lambda可以视为仿函数的一种等价形式或叫“语法糖”。

        2. 两者虽然在语法层面上不同,但却有着相同的内涵为——都可以捕捉一些变量作为初始状态并接受参数进行运算。

        3. lambda表达式在C++11中被称为闭包类型(Closure Type),可以认为是个仿函数,带有const属性的operator()它的捕获列表捕获的任何外部变量最终均会变为仿函数的成员变量。由闭包类型定义的对象称为“闭包”(是个右值)。

      4. 没有捕获变量的lambda表达式可以直接转换为函数指针,而捕获变量的lambda表达式则不能转换为函数指针

    【编程实验】lambda初体验

    #include <iostream>
    
    using namespace std;
    
    int gVal = 0;
    
    //捕获this指针
    class Test
    {
    private: 
        int i = 0;
    public:
        void func(int x, int y)
        {
            int a = 0;
            //auto lamb1 = [] {return i; }; //error,无捕获列表。
            //auto lamb2 = [&i] {return i; }; //error, 不能捕获父作用域(func域)以外的变量(i)
            auto lamb3 = [=] {return i; }; //ok,按值捕获(含this指针),因此可以访问类中的成员变量(i)。
            auto lamb4 = [&] {return i + x + a; }; //ok,按引用捕获(含this指针),可以使用类中的成员变量(i)
                                                   //同时,也捕获到形参x和局部变量a。
            auto lamb5 = [this] {return i; };  //ok,直接捕获this指针。
    
            auto lamb6 = [] {return gVal++; }; //ok,可以使用直接使用全局变量,无须也不能捕获它。
        }
    };
    
    int main()
    {
        int a = 3;
        int b = 4;
    
        //1. lambda表达式初体验
        auto lamb1 = [] {};   //最简单的lambda表达式
    
        auto lamb2 = [=] {return a + b; };//省略参数列表和返回类型
        cout << lamb2() << endl; //7
    
        auto lamb3 = [&](int c) {b = a + c; };//省略返回类型,为void。
        //cout << lamb3(5) << endl; //error,返回void
    
        auto lamb4 = [] {return 1; }; //省略参数列表
        cout << lamb4() << endl;
    
        auto lamb5 = [=, &b](int c)->int {return b += a + c; }; //各部分完整的lambda表达式
        cout << "lamb5(2) = "<<lamb5(2) << ", b = " << b<< endl; //9, 9
    
        //2. lambda表达式的常量性及mutable关键字
        a = 1;
        //auto f1 = [] {return a++; };  //error,没有捕获外部变量
        //auto f2 = [=]() { a = 1;};    //error,const函数不能修改按值捕获的变量
        auto f2 = [=]() mutable { a = 2; }; //ok,被mutable修饰
        auto f3 = [&a]() { a = 3; };       //ok,按引用传递。const函数时影响引用本身,表示其不可修改
                                           //但其引用的内容不受const影响,仍可修改。
        //3. 捕获的时间点
        int x = 10;
        auto lambByVal = [x] {return x + 1; }; //按值捕获:声明时,x被复制一下
        auto lambByRef = [&x] {return x + 1; };//按引用捕获:x的值是随外部x的变化而变化。
        cout << "lambByVal() = "<< lambByVal() << endl; //11
        cout << "lambByRef() = "<< lambByRef() << endl; //11
        
        ++x;
    
        cout << "lambByVal() = " << lambByVal() << endl; //11
        cout << "lambByRef() = " << lambByRef() << endl; //12
    
        //4. lambda表达式转换为函数指针
        using FuncX = int(*)(int);
        using FuncXY = int(*)(int, int);
    
        int k = 1;
        auto lambN = [](int x, int y) {return x + y; };       //无捕获列表
        auto lambK = [&k](int x, int y) {return x + y + k; }; //有捕获列表
    
        FuncXY funcXY;
        funcXY = lambN; //ok,无捕获列表的lambda可转化为函数指针
        //lambN = funcXY; //error,不能将函数指针转为lambda
        //funcXY = lambK; //error,有捕获列表的lambda不能转为函数指针
    
        //5. 捕获this指针(见Test类)
    
        return 0;
    }
    初识lambda

    (三)泛型lambda表达式

        1. 概述:

     

      (1)泛型lambda的格式形如 [](auto x, auto y){}; 或 [](auto&& x, auto&& y){} 等。

      (2)由于auto&&类型的形参没有可用的T类型,泛型lambda采用forward+decltype来转发param

      (3)当param被左值实参初始化时,param被推导为左值引用,即decltype(param)为左值引用类型。同理,当param被右值初始化时,即decltype(param)为右值引用类型。

      (4)decltype(param)作为模板形参传入std::forward时,会发生引用折叠,从而能正确根据实参的左/右值特性进行转发。

      2. 泛型lambda的优势

      (1)使用auto作为lambda函数的参数类型修饰符,增加泛型编程能力

      (2)泛型lambda允许带auto参数的lambda函数能够转化为函数指针

    【编程实验】泛型lambda

    #include <iostream>
    #include <vector>
    #include <algorithm>
    using namespace std;
    
    int main()
    {
        //1. 泛型局部函数
        auto f1 = [](auto x, auto y) {return x + y; };
        cout <<"f1(1, 3) = " << f1(1, 3) << endl;
        cout <<"f1(4.5, 6.3) = " << f1(4.5, 6.3) << endl;
        cout << "f1("abc", "def") = " << f1(string{ "abc" }, "def") << endl;
    
        //2. 泛型回调函数
        auto f2 = [](auto x) {cout << x << ","; };
        vector<int> v1{1, 2, 3};
        vector<string> v2{ "a","b","c" };
        for_each(v1.begin(), v1.end(), f2); cout << endl;
        for_each(v2.begin(), v2.end(), f2); cout << endl;
    
        //3. 泛型lambda与函数指针的转换
        auto f3 = [](auto x){ return x; };
        using Func = int(*)(int);
        Func pf = f3;
        cout << pf(5) << endl;  //5
    
        cout << "----------------------------------"<< endl;
        //4. 利用泛型lambda模拟实现tuple。
        //(1)make_tuple接受可变参数包,返回一个tuple对象(lambda)。
        //   如,auto tp = make_tuple(1, '2', "3");即tp = [](auto access){return access(1, '2', "3")};
        //   可见参数包己被展开,并保存在tp中,可随时供访问器使用。
        //(2)该tuple的使用方式是:tuple(access),向其传入某种功能的访问器(如打印,求长度函数)
        //   这种通过tuple+access的方式可以达到访问tuple中元素的目的。注意,access为可接受可变参数包的
        //   可调用对象。
        auto make_tuple = [](auto ...xs) {
            return [=](auto access) { return access(xs...); };
        };
    
        //4.1 求tuple的长度
        auto length = [](auto xs) { //length(tp),传入tuple
            return xs([](auto ...z) { return sizeof...(z); }); //通过tp(access)向其传入计算元素个数的访问器。
        };
    
        //4.2 fmap的功能: 将一个tuple(tpSrc)通过func函数映射成另一个新的tuple。如通过对元素*2,
        //将tuple(1,2,3,4)映射成tuple(2,4,6,8)
        auto fmap = [=](auto func) {  //捕获make_tuple
            return [=](auto tpSrc) {  //捕获func和make_tuple
                return tpSrc([=](auto... xs) { return make_tuple(func(xs)...); }); //tuple+访问器方式
            };
        };
    
        auto tp = make_tuple(1, '2', "3");
        std::cout << length(tp) << std::endl; // 3
        int len = tp([](auto...z) { return sizeof...(z); }); //通过向tuple传入访问器的方式来使用tuple。
                                                          //注意访问器必须可接受可变参数包。
        std::cout << len << std::endl;
    
        auto twice = [](auto i) { return 2 * i; };  //映射函数
        auto print = [](auto i) { std::cout << i << " "; return i; };
        auto tp1 = make_tuple(1, 2, 3, 4);
        auto tp2 = fmap(twice)(tp1); //将tp1通过twice函数映射成tp2
        auto tp3 = fmap(print)(tp2); //将tp2通过print函数映射成tp3,并通过print将tp2元素打印出来。
                                     //make_tuple(func(xs)...) 等价于make_tuple(func(xs1),func(xs2),...)
                                     //因参数按从右向左依次传入,所以最终打印结果为8,6,4,1
    
        return 0;
    }
    /*输出结果
    f1(1, 3) = 4
    f1(4.5, 6.3) = 10.8
    f1("abc", "def") = abcdef
    1,2,3,
    a,b,c,
    5
    ----------------------------------
    3
    3
    8 6 4 2
    */

    二. lambda表达式的优势

    (一)使代码更加简化、逻辑更清晰。

    (二)使程序更灵活,在需要的时间和地点实现闭包

    (三)简化仿函数的使用,使得STL算法的使用更加容易

    【编程实验】lambda的优势

    #include <iostream>
    #include<algorithm>
    #include <functional>
    
    using namespace std;
    using namespace std::placeholders;
    
    int g_ubound = 10;
    vector<int> nums = { 8, 9, 10, 11, 12, 13, 14, 15, 16,17,18,19,20 };
    vector<int> largeNums;
    
    //显示vector中的元素
    void print(vector<int>& vec)
    {
        for (auto& elem : vec) {
            cout << elem << " ";
        }
    
        cout << endl;
    }
    
    //函数
    inline void LargNumsFunc(int i)
    {
        if (i > g_ubound)
        {
            largeNums.push_back(i);
        }
    }
    
    //仿函数
    class LargeNums
    {
    private: 
        int ubound;
    public:
        LargeNums(int u):ubound(u){}
    
        void operator()(int i) const
        {
            if (i > ubound)
            {
                largeNums.push_back(i);
            }
        }
    };
    void test(int ubound)
    {
        //1. 使用传统的for(缺点:需要直接使用全局变量g_ubound)
        for (auto iter = nums.begin(); iter != nums.end(); ++iter) {
            if (*iter > ubound) {
                largeNums.push_back(*iter);
            }
        }
        print(largeNums);
    
        largeNums.clear();
        //2.使用函数指针
        //缺点:函数定义在别的地方,代码阅读不方便。inline非强制性的,内联不一定成功。可
        //      能导致性能问题。且LargeNumFunc由于使用了全局变量,是个有状态函数,函数重用性不高。
        for_each(nums.begin(), nums.end(), LargNumsFunc);
        print(largeNums);
    
        largeNums.clear();
        //3. 使用仿函数
        //优点:仿函数可以拥有状态,由于for_each第3个参数只能传递一个可调用对象而不能传递额外的参数。
        //      因此,利用仿函数就可以克服这一不足。
        //缺点:需要单独定义一个仿函数类。
        for_each(nums.begin(), nums.end(), LargeNums(g_ubound));
        print(largeNums);
    
        largeNums.clear();
        //4. 使用lambda表达式
        //优点:比仿函数书写上更简洁,代码的功能更清晰。
        for_each(nums.begin(), nums.end(), [ubound](int i) 
        {
                if (i > ubound) {
                    largeNums.push_back(i);
                }
        });
        print(largeNums);
    }
    int main()
    {
        //1. lambda可简化标准库的调用(统计(50,73]之间的元素个数)
        vector<int> v{ 15, 37, 94, 50, 73, 58, 28, 98 };
        //1.1 组合使用bind
        auto f1 = std::bind(std::logical_and<bool>(),
                            std::bind(std::greater<int>(), _1, 50),
                            std::bind(std::less_equal<int>(), _1, 73)
                            );
        cout << count_if(v.begin(), v.end(), f1) << endl; //2
    
        //1.2 使用lambda表达式
        auto f2 = [](int x) {return (50<x) && (x<=73); };
        cout << count_if(v.begin(), v.end(), f2) << endl; //2
        int cnt = count_if(v.begin(), v.end(), [](int x)
                  {
                     return  (50 < x) && (x <= 73);
                  });
        cout << cnt << endl; //2
    
        //2. lambda与其他可调用对象使用上的比较
        test(g_ubound);
    
        return 0;
    }
    /*输出结果
    2
    2
    2
    11 12 13 14 15 16 17 18 19 20
    11 12 13 14 15 16 17 18 19 20
    11 12 13 14 15 16 17 18 19 20
    11 12 13 14 15 16 17 18 19 20
    */
  • 相关阅读:
    More Effective C++ 条款31 让函数根据一个以上的对象类型来决定如何虚化
    定点数表示方法——原码,补码,反码,移码
    More Effective C++ 条款30 Proxy classes(替身类,代理类)
    More Effective C++ 条款29 Reference counting(引用计数)
    More Effective C++ 条款28 Smart Pointers(智能指针)
    More Effective C++ 条款27 要求(禁止)对象产生与heap之中
    More Effective C++ 条款26 限制某个class所能产生的对象数量
    C/C++:对象/变量初始化相关
    More Effective C++ 条款25 将constructor和non-member function虚化
    origin作图,避免里面有Type 3 字体
  • 原文地址:https://www.cnblogs.com/5iedu/p/11387211.html
Copyright © 2020-2023  润新知