C++ lambda表达式

目录

一、Lambda表达式概述

1.介绍

2.作用

1)lambda表达式做回调

2.做并行计算

二、Lambda表达式语义分析

1.基本语法分析

2.捕获列表

3.匿名函数简写

三、使用注意


一、Lambda表达式概述

1.介绍

Lambda表达式是C++11标准引入的一种特性,它提供了一种方便的方式来定义匿名函数。Lambda表达式是一种能够捕捉外部变量并使用它们的函数对象。由捕获列表、参数列表、返回类型和函数体组成;其中,参数列表和返回类型可以忽略,但不可以忽略捕获列表和函数体。

捕获列表\](参数列表) -\> 返回类型 {函数体}

例如:auto f = []{ return 1 + 2; }; 或者 auto f = [](int x, int y)->int{ return x + y; };

(在后面章节会详细介绍使用规则.)

2.作用

在c++11中提出lambda特性,通过引入lambda表达式,C++11使得编写匿名函数变得更加简洁和方便,提高了代码的可读性和灵活性。其主要作用包括:简化代码、作为算法的参数、回调、并行计算等。以下举几个简单示例:

1)lambda表达式做回调

cpp 复制代码
#include <iostream>
#include <functional>
#include <vector>
#include <unistd.h>

using namespace std;

class EventHandle
{
public:
        void registerCallback(function<void()> callback)
        {
            callbacks.push_back(callback);
        }
        void triggerEvent()
        {
            for(auto &testCallback: callbacks)
            {
                testCallback();
            }
        }
private:
    vector<function<void()>>  callbacks; 
};

int main()
{
    EventHandle eventHandle;
    eventHandle.registerCallback([]{cout<<"run"<<endl;});

    sleep(3);
    eventHandle.triggerEvent(); //触发

    return 0;
}

main函数中,我们使用lambda表达式注册了一个回调函数,当事件被触发时,lambda表达式中的代码将执行,输出"run!"。这样,我们可以方便地在事件处理中使用lambda表达式,而不需要显式地定义独立的函数或类来处理回调

2.做并行计算

cpp 复制代码
#include <iostream>
#include <vector>
#include <numeric>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    int sum = std::accumulate(numbers.begin(), numbers.end(), 0,
        [](int a, int b) {
            return a + b;
        }
    );
    std::cout << "result:" << sum << std::endl;

    return 0;
}

lambda表达式中的操作是将两个数字相加,它接收两个参数(当前的累加结果和下一个元素),并返回它们的和。通过使用并行算法和lambda表达式,我们可以充分发挥多核处理器的能力,提高计算效率。

二、Lambda表达式语义分析

1.基本语法分析

捕获列表\](参数列表) -\> 返回类型 {函数体}

cpp 复制代码
auto Add = [](int a, int b) -> int {
        return a + b;
    };

上述代码表示[ ]传参列表为空,参数为int a和int b,返回值类型为int。函数体为return a+b 的一个匿名函数。一般情况下,编译器可以自动推断出lambda表达式的返回类型,所以我们可以不指定返回类型

cpp 复制代码
auto Add = [](int a, int b){
        return a + b;
    };

注意:如果匿名函数表达式内有多个return操作,编译器无法自动推断出返回类型,此时必须指定返回类型

2.捕获列表

如果需要在匿名函数内部使用外部变量而不想传参,可以用捕获列表来传参。

cpp 复制代码
int c = 12;
int d = 30;
auto Add = [c, d](int a, int b)->int { 
   cout << "d = " << d  << endl;
   return c;};

如果在匿名函数内部,加入对变量c,d修改的操作; 则无法编译通过。因为上述捕获列表是通过值进行传递,无法进行修改。那么如何进行修改呢?通过传入引用,这样就可以当作左值被用来修改

cpp 复制代码
int c = 12;
int d = 30;
auto Add = [&c, &d](int a, int b)->int { 
   d = 11;
   cout << "d = " << d  << endl;
   return c;};

以下是捕获列表分类:

|------------------------|------------------------------------------------------------------------------------------------------------|
| [] | 空捕获列表,Lambda不能使用所在函数中的变量。 |
| [names] | names是一个逗号分隔的名字列表,这些名字都是Lambda所在函数的局部 变量。默认情况下,这些变量会被拷贝,然后按值传递,名字前面如果使用 了&,则按引用传递 |
| [&] | 隐式捕获列表,Lambda体内使用的局部变量都按引用方式传递 |
| [=] | 隐式捕获列表,Lanbda体内使用的局部变量都按值传递 |
| [&,identifier_list] | identifier_list是一个逗号分隔的列表,包含0个或多个来自所在函数的变量, 这些变量采用值捕获的方式,其他变量则被隐式捕获,采用引用方式传递, identifier_list中的名字前面不能使用&。 |
| [=,identifier_list] | identifier_list中的变量采用引用方式捕获,而被隐式捕获的变量都采用按值 传递的方式捕获。identifier_list中的名字不能包含this,且这些名字面前必须 使用& |

3.匿名函数简写

匿名函数由捕获列表、参数列表、返回类型和函数体组成;可以忽略参数列表和返回类型,但不可以忽略捕获列表和函数体,如:auto num = []{ return 1 + 2; };

三、使用注意

  • 匿名函数构建的时候对于按值传递的捕获列表,会立即将当前可以取到的值拷贝一份作为常数(相当于一个右值),然后将该常数作为参数传递
  • 可变性(mutability):默认情况下,lambda表达式不能修改捕获的按值变量。如果需要修改,可以使用mutable关键字修饰lambda表达式。
  • 如果匿名函数表达式内有多个return操作,编译器无法自动推断出返回类型,此时必须指定返回类型
  • 生命周期问题:当lambda表达式捕获了外部变量时,需要注意外部变量的生命周期,以避免悬垂引用(dangling references)或访问已销毁的对象。

关于第四点,请看示例:

cpp 复制代码
int main() {
    int* danglingPtr = nullptr;

    {
        int *p = new int(2);
        auto lambda = [&]() {
            danglingPtr = p; // 捕获局部变量 p 的地址
        };
        lambda();
        delete p;
    } // 离开作用域,p 被销毁

    // 此时 danglingPtr 包含了一个悬垂的指针,访问它是未定义行为
    std::cout << *danglingPtr << std::endl; // 可能导致崩溃或不可预测的行为

    return 0;
}
相关推荐
xuanzdhc2 小时前
Linux 基础IO
linux·运维·服务器
愚润求学2 小时前
【Linux】网络基础
linux·运维·网络
不想写bug呀2 小时前
多线程案例——单例模式
java·开发语言·单例模式
bantinghy2 小时前
Linux进程单例模式运行
linux·服务器·单例模式
我不会写代码njdjnssj3 小时前
网络编程 TCP UDP
java·开发语言·jvm
小和尚同志3 小时前
29.4k!使用 1Panel 来管理你的服务器吧
linux·运维
ai小鬼头3 小时前
百度秒搭发布:无代码编程如何让普通人轻松打造AI应用?
前端·后端·github
考虑考虑3 小时前
@FilterRegistration和@ServletRegistration注解
spring boot·后端·spring
帽儿山的枪手3 小时前
为什么Linux需要3种NAT地址转换?一探究竟
linux·网络协议·安全
一只叫煤球的猫3 小时前
🔥 同事混用@Transactional和TransactionTemplate被我怼了,三种事务管理到底怎么选?
java·spring boot·后端