• C++:函数:匿名函数Lambda


    1: 匿名函数 Lambda定义

    C++11新增了很多特性,Lambda表达式(Lambda expression)就是其中之一,很多语言都提供了 Lambda 表达式,如 Python,Java ,C#等。本质上, Lambda 表达式是一个可调用的代码单元[1]^{[1]}[1]。实际上是一个闭包(closure),类似于一个匿名函数,拥有捕获所在作用域中变量的能力,能够将函数做为对象一样使用,通常用来实现回调函数、代理等功能。Lambda表达式是函数式编程的基础,C++11引入了Lambda则弥补了C++在函数式编程方面的空缺。

    2: 语法格式

    Lambda 表达式就是一个可调用的代码单元,我们可以将其理解为一个未命名的内联函数。与任何函数类似,一个Lambda具有一个返回类型、一个参数列表和一个函数体。但与函数不同,Lambda可以定义在函数内部,其语法格式如下:

    [capture list](parameter list) mutable(可选) 异常属性->return type{function body}

    capture list(捕获列表)

          是一个Lambda所在函数中定义的局部变量的列表,通常为空,表示Lambda不使用它所在函数中的任何局部变量。也可以使用 “=”  ,表示函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是值传递方式(编译器自动为我们按值传递了所有局部变量)也可以使用 “this” , [this] 截取当前类中的this指针。如果已经使用了&或者=就默认添加此选项。也可以使用 “&”,表示 以引用方式捕获外部作用域中所有变量

         捕获列表

     

    parameter list  (参数列表) 相当于函数入参

    mutable  参数选项

    ret  返回值类型

    body   函数体

    其中Lambda表达式必须的部分只有capture list和function body。

    1. 在Lambda忽略参数列表时表示指定一个空参数列表,
    2. 忽略返回类型时,Lambda可根据函数体中的代码推断出返回类型。
    1. // 表示:我们定义一个 可调用的对象 f ,它不接受任何参数,返回值是42
    2. auto f=[]{return 42;}

    3:调用方式(直接调用和间接调用)

    1. main.cpp
    2. #include
    3. #include
    4. #include
    5. auto f = [] {return 42; };
    6. int a = 43;
    7. auto lamdba = [=]()->void {
    8. std::cout << "in lamdba: " << a << std::endl;
    9. };
    10. int main() {
    11. // 直接调用 Lamdba几种方式(1)
    12. std::cout << f() << std::endl;
    13. std::cout << [] {return 42; }() << std::endl;
    14. // 直接调用 Lamdba几种方式(2)
    15. lamdba();
    16. // 直接调用 Lamdba几种方式(3)有参
    17. // [](int a)-> void {
    18. // cout << a;
    19. // }(1);
    20. // 外部间接调用: Lambda函数变量 Vector
    21. [](int val) {
    22. cout << val;
    23. }
    24. //上面Lamdba表达式 是由 for_each() 调用的
    25. for_each(v.begin(),v.end(),[](int val)
    26. {
    27. cout << val;
    28. });
    29. }

    添加 mutable 选项修改变量值

    1. #include
    2. auto f = [] {return 42; };
    3. int a = 43;
    4. auto lamdba = [=]()->void {
    5. std::cout << "in lamdba: " << a << std::endl;
    6. };
    7. auto lamdba2 = [=]() mutable ->void {
    8. a = 45;
    9. std::cout << "in mutable lamdba: " << a << std::endl;
    10. };
    11. int main() {
    12. lamdba();
    13. // 添加 mutable 修改变量值
    14. lamdba2();
    15. }
    16. // 打印结果
    17. in lamdba: 43
    18. in mutable lamdba: 45

    4:Lamdba类型

    1. lambda函数的类型看起来和函数指针很像,都是把函数赋值给了一个变量。
    2. 实际上,lambda函数是用仿函数实现的,它看起来又像是一种自定义的类。而事实上,lambda类型并不是简单的函数指针类型或者自定义类型,lambda函数是一个闭包(closure)的类,C++11标准规定,closure类型是特有的、匿名且非联合体的class类型。每个lambda表达式都会产生一个闭包类型的临时对象(右值)。
    3. 因此,严格来说,lambda函数并非函数指针,但是C++11允许lambda表达式向函数指针转换,前提是没有捕捉任何变量且函数指针所指向的函数必须跟lambda函数有相同的调用方式。
    1. #include
    2. using namespace std;
    3. typedef int(*pfunc)(int x, int y);
    4. int main()
    5. {
    6. auto func = [](int x, int y)->int {
    7. return x + y;
    8. };
    9. pfunc p1 = nullptr;
    10. p1 = func; //lambda表达式向函数指针转换
    11. std::cout << p1(1, 2) << std::endl;
    12. return 0;
    13. }
    14. // 打印结果 :3

    5:lambda的常量性和mutable关键字

    C++11中,默认情况下lambda函数是一个const函数,按照规则,一个const成员函数是不能在函数体内改变非静态成员变量的值。但是我一定要修改,那又怎么办了 ?(下面第二、第三、第四点均可以实现)
     (1)可以看到在const的lambda函数中无法修改按值捕捉到的变量。lambda函数是通过仿函数来实现的,捕捉到的变量相当于是仿函数类中的成员变量,而lambda函数相当于是成员函数,const成员函数自然不能修改普通成员变量;(不能修改捕获的值)
     (2)使用引用的方式捕获的变量在常量成员函数中值被更改则不会导致错误,其原因简单地说,由于const_ref_lambda 不会改变引用本身,而只会改变引用的值,所以编译通过;(通过捕获值得引用,改变引用的值,不修改值本身)
     (3)使用mutable修饰的mutable_val_lambda,去除了const属性,所以可以修改按值方式捕获到的变量;(去除const属性,就可以修改值)
     (4)按值传递参数的const_param_lambda修改的是传入lambda函数的实参,当然不会有问题。(修改参数,当然可以该值)

    1. #include
    2. using namespace std;
    3. int main()
    4. {
    5. int val = 0;
    6. // auto const_val_lambda = [=] { val = 3; }; // 编译失败,不能在const的lambda函数中修改按值捕获的变量val
    7. auto mutable_val_lambda = [=]() mutable { val = 3; };
    8. printf("mutable_val_lambda:%d", mutable_val_lambda);
    9. auto const_ref_lambda = [&]()-> int {
    10. val + 3;
    11. };
    12. printf("\nconst_ref_lambda:%d", const_ref_lambda);
    13. auto const_param_lambda = [](int v) { v = 4; };
    14. const_param_lambda(val);
    15. printf("\nconst_param_lambda:%d", const_param_lambda);
    16. return 0;
    17. }
    18. 打印结果
    19. mutable_val_lambda:0
    20. const_ref_lambda:6422284
    21. const_param_lambda:6422389

    这里提出一个疑问: 如何打印出 变量val 修改后的值

    6: Lamdba常用用法

    (1)Lambda函数和STL
     Lambda函数的引入为STL的使用提供了极大的方便。比如下面这个例子,当你想遍历一个vector的时候,原来你得这么写:

    很明显,相比于传统的for循环、函数指针和仿函数,使用lambda函数更加简洁。如果处理vector成员的业务代码更加复杂,那么更能凸显Lambda函数的便捷。而且这么写之后执行效率反而会提高,因为编译器有可能使用循环展开来加速执行过程。

    1. #include
    2. #include
    3. #include
    4. using namespace std;
    5. vector<int> v = { 1,2,3,4,5,6,7,8,9 };
    6. void printFunc(int v) {
    7. cout << v;
    8. };
    9. // 循环vector
    10. int main() {
    11. //传统的for循环
    12. for (auto itr = v.begin(), end = v.end(); itr != end; itr++)
    13. {
    14. cout << *itr;
    15. }
    16. cout << endl;
    17. //“函数指针”
    18. for_each(v.begin(), v.end(), printFunc);
    19. cout << endl;
    20. //仿函数
    21. struct CPrintFunc
    22. {
    23. void operator() (int val)const { cout << val; }
    24. };
    25. for_each(v.begin(), v.end(), CPrintFunc());
    26. cout << endl;
    27. // Lamdba 表达式
    28. for_each(v.begin(), v.end(), [](int val)
    29. {
    30. cout << val;
    31. });
    32. }

  • 相关阅读:
    Java架构师内功计算机网络
    【排序】十大排序算法
    使用Web 在IntelliJ IDEA 下创建、部署、运行及更改端口号、添加 Tomcat 依赖
    Pytorch学习笔记(四)官方60min入门教程之图像分类器
    MATLAB中expm1函数用法
    XXL-JOB逻辑自测及执行参数配置踩坑
    Elasticsearch 开放 inference API 增加了对 OpenAI chat completions 的支持
    Mac配置iTerm样式终端
    DataTable使用Linq进行分组汇总,将Linq结果集转化为DataTable
    Mac安装redis
  • 原文地址:https://blog.csdn.net/u013620306/article/details/127890253