Lambda表达式基础

引言

我们在写一个排序函数,需要自定义比较规则。传统方式是:写一个单独的比较函数,或者写一个仿函数类。这两种方式都有点烦人——函数分离在别处,仿函数类又要写一堆样板代码。

然后,C++11带来了Lambda表达式,就像给你的代码送来了一台"一次性函数生成器"。

一句话总结:Lambda是匿名函数,可以就地定义和使用,特别适合作为回调函数和算法参数。


Lambda的基本语法

Lambda表达式的语法看起来有点吓人,但拆解后其实很清晰:

[capture](parameters) -> return_type { body }

各部分说明:

  • capture:捕获列表(后面章节详细讲)
  • parameters:参数列表(和普通函数一样)
  • return_type:返回类型(可以省略,编译器自动推导)
  • body:函数体

最简单的Lambda:

[]() { }              // 最简单:什么都不做
[]() { return 42; }   // 简单返回
[](int x) { return x * 2; }  // 带参数

实际使用:

auto add = [](int a, int b) { return a + b; };
int result = add(3, 4);  // result = 7

类型推导:自动识别返回类型

只要你的函数体只有一条return语句,编译器就能自动推导返回类型:

// 返回int,自动推导
auto square = [](int x) { return x * 2; };

// 返回double,自动推导
auto divide = [](int a, int b) { return static_cast<double>(a) / b; };

如果有多条语句,或者逻辑复杂,可以用->显式指定:

auto complex = [](int x) -> int {
    if (x > 0) {
        return x * 2;
    } else {
        return x;
    }
};

作为算法参数:Lambda最常用的场景

Lambda表达式最经典的用法是作为STL算法的参数。这在嵌入式开发中同样有用,比如处理数据数组:

#include <algorithm>
#include <vector>

void process_sensor_data() {
    std::vector<int> readings = {12, 45, 23, 67, 34, 89, 56};

    // 找出第一个超过阈值的读数
    auto it = std::find_if(readings.begin(), readings.end(),
                          [](int value) { return value > 50; });

    // 统计有多少个异常值(>80)
    int count = std::count_if(readings.begin(), readings.end(),
                             [](int value) { return value > 80; });

    // 对所有值进行处理
    std::transform(readings.begin(), readings.end(), readings.begin(),
                  [](int value) { return value * 2; });
}

这比传统写函数干净太多了——逻辑就在使用的地方,不用跳来跳去。


捕获外部变量:让Lambda"看见"外面

虽然下一章会详细讲捕获,但这里先简单介绍基础概念。

默认情况下,Lambda不能访问外部变量:

int threshold = 50;
// ❌ 编译错误:threshold不可访问
auto lambda = [](int value) { return value > threshold; };

需要通过捕获列表"引入"外部变量:

int threshold = 50;

// 值捕获:复制一份threshold
auto by_value = [threshold](int value) { return value > threshold; };

// 引用捕获:使用外部的threshold
auto by_ref = [&threshold](int value) { return value > threshold; };

// 捕获所有变量(值捕获)
auto capture_all = [=](int value) { return value > threshold; };

// 捕获所有变量(引用捕获)
auto capture_all_ref = [&](int value) { return value > threshold; };

嵌入式场景示例:

void configure_pwm(uint32_t base_addr, int frequency) {
    // Lambda捕获base_addr和frequency
    auto set_duty = [base_addr, frequency](int percent) {
        uint32_t period = 1000000 / frequency;  // 微秒
        uint32_t duty = period * percent / 100;
        *reinterpret_cast<volatile uint32_t*>(base_addr + 0x04) = duty;
    };

    set_duty(25);   // 25%占空比
    set_duty(50);   // 50%占空比
    set_duty(75);   // 75%占空比
}

Lambda的类型:它到底是什么?

Lambda表达式的类型是唯一的、未命名的类类型。你不能直接声明Lambda类型,但可以用auto或者std::function

auto lambda1 = []() { };
// lambda1的类型是编译器生成的某个唯一类型

// 用std::function存储(有运行时开销)
std::function<void()> lambda2 = []() { };

// 用模板参数传递
template<typename Func>
void call_func(Func f) {
    f();
}
call_func([]() { /* ... */ });

嵌入式提示std::function有动态分配开销,优先使用auto或模板。


实战示例:事件处理器

假设你在做一个简单的GPIO事件处理系统:

#include <cstdint>
#include <functional>

class GPIOManager {
public:
    using EventHandler = std::function<void(uint32_t timestamp)>;

    // 注册上升沿事件处理
    void on_rising_edge(int pin, EventHandler handler) {
        rising_handlers[pin] = handler;
    }

    // 模拟硬件中断触发
    void simulate_interrupt(int pin, uint32_t timestamp) {
        auto it = rising_handlers.find(pin);
        if (it != rising_handlers.end() && it->second) {
            it->second(timestamp);  // 调用注册的Lambda
        }
    }

private:
    std::array<EventHandler, 16> rising_handlers;  // 假设16个引脚
};

// 使用示例
void setup_gpio_system() {
    GPIOManager gpio;

    int debounce_counter = 0;
    uint32_t last_press_time = 0;

    // 注册按键处理Lambda
    gpio.on_rising_edge([&](uint32_t timestamp) {
        // 防抖逻辑
        if (timestamp - last_press_time > 50) {  // 50ms防抖
            debounce_counter++;
            last_press_time = timestamp;
            // 触发业务逻辑...
        }
    });

    // 注册LED控制Lambda
    gpio.on_rising_edge([](uint32_t timestamp) {
        // 翻转LED
        static bool led_state = false;
        led_state = !led_state;
        // GPIO_Write(LED_PIN, led_state);
    }, 5);  // 引脚5
}

实战示例:配置生成器

Lambda表达式可以用来创建"迷你配置语言":

struct TimerConfig {
    uint32_t prescaler = 0;
    uint32_t period = 0;
    bool auto_reload = false;
};

TimerConfig make_timer_config(std::function<void(TimerConfig&)> builder) {
    TimerConfig config;
    builder(config);
    return config;
}

// 使用:创建一个1kHz定时器配置
auto timer1_cfg = make_timer_config([](TimerConfig& c) {
    c.prescaler = 7200 - 1;     // 72MHz / 7200 = 10kHz
    c.period = 10 - 1;          // 10kHz / 10 = 1kHz
    c.auto_reload = true;
});

// 使用:创建一个PWM定时器配置
auto pwm_cfg = make_timer_config([](TimerConfig& c) {
    c.prescaler = 1 - 1;        // 不分频
    c.period = 1000 - 1;        // PWM周期
    c.auto_reload = true;
});

这种写法让配置代码非常清晰,所有参数都在一个地方集中管理。


泛型Lambda(C++14)

C++14让Lambda支持auto参数,变成模板函数:

// 泛型Lambda:可以接受任何可加类型
auto add = [](auto a, auto b) { return a + b; };

int x = add(3, 4);           // int
double y = add(3.5, 2.5);    // double

这在嵌入式开发中处理寄存器操作时特别有用:

auto write_reg = [](auto addr, auto value) {
    *reinterpret_cast<volatile decltype(value)*>(addr) = value;
};

write_reg(0x40000000, uint32_t(0x12345678));
write_reg(0x50000000, uint16_t(0xABCD));

注意事项与最佳实践

1. 复杂度控制

如果Lambda超过5行,考虑把它命名或者提取成函数:

// ❌ 太长了,难以阅读
std::vector<int> result = std::accumulate(/* ... */, [](int acc, int x) {
    // ... 20行复杂逻辑 ...
});

// ✅ 提取成命名函数
int transform_element(int acc, int x);
std::vector<int> result = std::accumulate(/* ... */, transform_element);

2. 注意捕获的生命周期

引用捕获的变量必须保证在Lambda执行时仍然有效:

// ❌ 危险:返回后局部变量销毁
auto get_lambda() {
    int local = 42;
    return [&local]() { return local; };  // 悬垂引用!
}

// ✅ 安全:值捕获
auto get_lambda_safe() {
    int local = 42;
    return [local]() { return local; };  // 复制了一份
}

3. 嵌入式环境中避免动态分配

避免用std::function存储Lambda,使用auto或模板:

// ❌ 可能动态分配
std::function<void(int)> f = [](int x) { /* ... */ };

// ✅ 零开销
auto f = [](int x) { /* ... */ };

小结

Lambda表达式是现代C++的重要特性:

  • 就地定义:代码在使用的地方,逻辑清晰
  • 匿名函数:不需要额外命名,减少命名污染
  • 捕获机制:灵活控制外部变量访问
  • 算法友好:与STL算法配合使用,威力强大

在下一章,我们将深入了解Lambda的捕获机制及其对性能的影响。

更多推荐