• lambda表达式介绍


    前言

            lambda表达式是C++11标准才支持的,有了它以后在一些地方进行使用会方便很多,尤其在一些需要仿函数的地方,lambda表达式完全可以替代它的功能。代码的可读性也会提高。

    目录

    1.lambda表达式

    2.lambda表达式语法

    3.函数对象和lambda表达式


    1.lambda表达式

            在C++98中如果想要对一个数据集合进行排序,可以使用std::sort的方法 

    1. //仿函数
    2. template<class T>
    3. struct Greater
    4. {
    5. bool operator()(const T& nums1,const T& nums2)
    6. {
    7. return nums1 > nums2;
    8. }
    9. };
    10. //函数
    11. bool FunGreater(const int&nums1,const int&nums2)
    12. {
    13. return nums1 > nums2;
    14. }
    15. void TestSort()
    16. {
    17. int array[] = { 4,1,8,5,3,7,0,9,2,6 };
    18. std::sort(array, array + sizeof(array)/sizeof(array[0]) );
    19. //如果按照默认的方式进行排序那么排序的结果就是升序
    20. //如果我们需要进行降序排列怎么办呢?
    21. //这时候就需要用到仿函数了
    22. //我们需要通过仿函数或者函数指针来改变排序的规则
    23. for (auto& e : array)
    24. cout << e << " ";
    25. cout << endl;
    26. //用仿函数生成函数对象
    27. Greater<int> gr;
    28. std::sort(array, array + sizeof(array) / sizeof(array[0]),gr);
    29. for (auto& e : array)
    30. cout << e << " ";
    31. //用函数指针传参进行排序
    32. std::sort(array, array + sizeof(array) / sizeof(array[0]), FunGreater);
    33. //仿函数和函数指针的效果是相同的
    34. }

            但是如果要排序的元素为自定义类型时,需要用户定义排序规则。 

    1. struct Goods
    2. {
    3. string _name;//名字
    4. double _price;//价格
    5. int _num;//数量
    6. Goods(const char* str, double price,int num)
    7. :_name(str)
    8. ,_price(price)
    9. ,_num(num)
    10. { }
    11. };
    12. void TestSort1()
    13. {
    14. vector v1 = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,3 }, { "菠萝", 1.5, 4 } };
    15. //现在需要对自定义类型进行排序
    16. //如果按照_name排序我们需要实现至少两个仿函数
    17. //按照_price排序也需要两个仿函数
    18. //按照_num排序也需要两个仿函数
    19. //这样的话就需要我们实现很多的仿函数,如果仿函数的命名风格不好的话,别人对于这些仿函是干什么的必然不好理解,从这里也可以看出好的命名风格也是很重要的
    20. }

           现在需要对自定义类型进行排序,如果按照_name排序我们需要实现至少两个仿函数,按照_price排序也需要两个仿函数, 按照_num排序也需要两个仿函数, 这样的话就需要我们实现很多的仿函数,如果仿函数的命名风格不好的话,别人对于这些仿函是干什么的必然不好理解,从这里也可以看出好的命名风格也是很重要的。 

    1. struct Goods
    2. {
    3. string _name;//名字
    4. double _price;//价格
    5. int _num;//数量
    6. Goods(const char* str, double price,int num)
    7. :_name(str)
    8. ,_price(price)
    9. ,_num(num)
    10. { }
    11. };
    12. struct ComparePriceLess
    13. {
    14. bool operator()(const Goods &g1, const Goods &g2)
    15. {
    16. return g1._price < g2._price;
    17. }
    18. };
    19. struct ComparePriceMore
    20. {
    21. bool operator()(const Goods& g1, const Goods& g2)
    22. {
    23. return g1._price > g2._price;
    24. }
    25. };
    26. struct CompareNumLess
    27. {
    28. bool operator()(const Goods& g1, const Goods& g2)
    29. {
    30. return g1._num < g2._num;
    31. }
    32. };
    33. struct CompareNumMore
    34. {
    35. bool operator()(const Goods& g1, const Goods& g2)
    36. {
    37. return g1._num > g2._num;
    38. }
    39. };
    40. struct CompareNameLess
    41. {
    42. bool operator()(const Goods& g1, const Goods& g2)
    43. {
    44. return g1._name < g2._name;//string重载了operator>和operator<所以可以直接比较
    45. }
    46. };
    47. struct CompareNameMore
    48. {
    49. bool operator()(const Goods& g1, const Goods& g2)
    50. {
    51. return g1._name > g2._name;
    52. }
    53. };
    54. void TestSort1()
    55. {
    56. vector v1 = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,3 }, { "菠萝", 1.5, 4 } };
    57. //现在需要对自定义类型进行排序
    58. //如果按照_name排序我们需要实现至少两个仿函数
    59. //按照_price排序也需要两个仿函数
    60. //按照_num排序也需要两个仿函数
    61. //这样的话就需要我们实现很多的仿函数,如果仿函数的命名风格不好的话,别人对于这些仿函是干什么的必然不好理解,从这里也可以看出好的命名风格也是很重要的
    62. std::sort(v1.begin(), v1.end(), ComparePriceMore());
    63. std::sort(v1.begin(), v1.end(), CompareNumMore());
    64. std::sort(v1.begin(), v1.end(), CompareNameMore());
    65. }
    66. int main()
    67. {
    68. TestSort1();
    69. return 0;
    70. }

            为了提高代码的可读性,所以在这里引入了lambda表达式。 

    2.lambda表达式语法

            上述的问题也可以使用lambda表达式来解决。

    1. void TestSort2()
    2. {
    3. vector v1 = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,3 }, { "菠萝", 1.5, 4 } };
    4. std::sort(v1.begin(), v1.end(), [](const Goods& g1, const Goods& g2) ->bool {return g1._name > g2._name;});
    5. std::sort(v1.begin(), v1.end(), [](const Goods& g1, const Goods& g2)->bool {return g1._num > g2._num;});
    6. std::sort(v1.begin(), v1.end(), [](const Goods& g1, const Goods& g2)->bool {return g1._price > g2._price;});
    7. }

            实际上我们可以看到lambda表达式就是匿名函数。 

            lambda表达式的语法: 

            lambda表达式书写格式:[ capture-list ]( parameters ) mutable -> return -type{statement} 

            1.lambda表达式各部分说明 

            [capture]:捕捉列表,该列表总是出现在lambda表达式的开头位置 ,编译器根据[]来判断接下来的代码是否为lambda表达式,捕捉列表能够捕捉上下文中的变量供lambda函数使用

            [paremeters]:参数列表。与普通函数的参数列表一样,如果不需要参数传递,则可以连同()一起省略。

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

            ->returntype:返回值类型。用来追踪返回类型形式声明函数的返回值类型,若果没有返回值时该部分可以省略,返回值类型明确的情况下也可以省略,由编译器对返回值类型进行推导。

            {statement}:函数体。该函数体内除了可以使用其参数外,还可以使用所捕获到的变量。

    注意:

    在lambda函数定义中,参数列表和返回值类型是可选部分,而捕捉列表和函数体可以为空。因此C++11最简单的lambda表达式为[]{};该lambda表达式不做任何事情

            

    1. int main()
    2. {
    3. //最简单的lambda表达式
    4. [] {};//但是这个表达式没有意义
    5. //省略了参数列表和返回值类型返回值类型由编译器推导为int
    6. int a = 3,b = 4;
    7. [=] {return a + b;};
    8. //省略了返回值类型,无返回值类型
    9. auto fun1 = [&] (int c){b = a + c;};//由auto自动推导的一个对象
    10. fun1(10);
    11. cout << b << endl;
    12. int c = 0;
    13. //各部分都很完善的lambda表达式
    14. auto fun2 = [=, &b]()->int {return b += a + c;};
    15. //复制捕捉x
    16. int x = 10;
    17. auto add_x = [x](int a)mutable ->int {x *= 2;return a + x;};
    18. return 0;
    19. }

             通过上述的例子可以看出实际上lambda表达式可以理解为无名函数,该函数无法直接调用,需要通过auto将其赋值给一个变量。

            2.捕捉列表说明

            捕捉列表描述了上下文中哪些数据可以被lambda表达式使用,以及使用的方式是传值还是传引用。

            [val]:表示值传递捕获变量val。

            [=]:表示值传递捕获所有父作用域中的变量(包括this)。

            [&val]:表示引用捕获变量val。

            [&]:表示引用捕获所有的变量(包括this)。

            [this]:表示值传递的方式捕获this指针。

            注意:

            a.父作用域指包含lambda表达式的语句块 

            b.语法上捕捉列表由多个捕捉项组成,并以逗号分隔

            比如:[=,&b,&b];以引用方式捕捉变量a和b,使用值捕捉的方式捕捉其它变量。

                       [&,a,this]:以值捕捉的方式捕捉a和this指针,以引用的捕捉方式捕捉其它的值。 

            c.捕捉列表不允许变量重复传递,否则就会导致编译错误

            比如:[=,a];以值的方式捕捉所有变量,然后又捕捉a变量重复了。

            d. 在块作用域以外的lambda函数的捕捉列表必须为空

            e.在块作用域中的lambda表达式仅能捕捉父作用域中的局部变量,捕捉任何非此局部域的或者非局部变量都会导致编译报错

            f.lambda表达式之间不能互相赋值,即使看起来类型相同

    1. void (*PF)();
    2. int main()
    3. {
    4. auto f1 = [] {cout << "hello world" << endl;};
    5. auto f2 = [] {cout << "hello world" << endl;};
    6. //f1 = f2;//此处会编译失败,因为lambda表达式的底层编译器会将它替换成一个仿函数;
    7. auto f3(f2);//允许构造拷贝一个新的副本
    8. f3();
    9. //可以将lambda表达式赋值给相同类型的指针
    10. PF = f2;
    11. PF();
    12. return 0;
    13. }

     

    3.函数对象和lambda表达式

            函数对象又叫做仿函数,可以像函数一样使用的对象,就是在类中重载了operator()运算符的类对象。

    1. class Rate
    2. {
    3. public:
    4. Rate(double rate)
    5. :_rate(rate)
    6. {}
    7. double operator()(double year, double money)
    8. {
    9. return money * year * _rate;
    10. }
    11. private:
    12. double _rate;
    13. };
    14. int main()
    15. {
    16. //函数对象
    17. double rate = 0.90;
    18. Rate r1(rate);
    19. r1(10000, 2);
    20. //lambda
    21. auto r2 = [=](double money, double year)->double {return money * year * rate;};
    22. r2(10000, 2);
    23. return 0;
    24. }

            从使用方来看lambda表达式和函数对象完全一样,函数对象将rate作为其成员变量,定义时给初始值就好了,lambda表达式通过捕捉列表来对rate进行捕捉。

     

            实际上在底层编译器对lambda表达式的处理是完全按照函数对象进行处理的,即如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载operator()。 调用lambda表达式时,编译器会给它起一个名字lambda_uuid,(唯一的名字,然后去调用它)。

  • 相关阅读:
    OpenHarmony社区运营报告(2023年8月)
    Spring Boot 整合邮件服务
    约束(constraint)
    element UI 组件封装--搜索表单(含插槽和内嵌组件)
    引用类型详解
    Python元类(metaclass)
    dd命令用法学习,是一个功能强大的工具
    topk算法-leetcode java题解
    python数据容器
    redis 雪崩,穿透,击穿及解决方案
  • 原文地址:https://blog.csdn.net/m0_68641696/article/details/132723110