【Cpp基础】lambda表达式的结构

在Linux条件变量的使用时,经常用到C++的 lambda 表达式

1. Lambda 表达式的基本结构

// 最基本的 lambda 表达式
[]() {
    // 函数体
};

这就是一个完整的 lambda 表达式,相当于一个匿名函数

2. 逐部分讲解

① 捕获列表 []

定义 lambda 可以访问哪些外部变量

int a = 10;
int b = 20;

// 1. 不捕获任何变量
[]() { /* 不能访问 a 或 b */ };

// 2. 按值捕获(复制)
[a, b]() { 
    // 可以访问 a 和 b 的副本
    std::cout << a + b;  // 输出 30
};

// 3. 按引用捕获
[&a, &b]() {
    a = 100;  // 修改原始变量
    b = 200;
};

// 4. 捕获所有变量(按值)
[=]() {
    // 可以访问所有外部变量的副本
};

// 5. 捕获所有变量(按引用)
[&]() {
    // 可以访问并修改所有外部变量
};

// 6. 混合捕获
[=, &a]() {  // a 按引用,其他按值
    a = 100;  // 可以修改 a
    // b 是副本,不能修改原始 b
};

[&, a]() {  // a 按值,其他按引用
    // a 是副本,其他变量可修改
};

② 参数列表 ()

和普通函数一样

// 无参数
[]() { return 42; };

// 带参数
[](int x, int y) { return x + y; };

// 自动推导参数类型 (C++14+)
[](auto x, auto y) { return x + y; };

// 模板参数 (C++20+)
[]<typename T>(T x, T y) { return x + y; };

③ 函数体 {}

和普通函数体一样

[]() {
    int sum = 0;
    for (int i = 1; i <= 10; i++) {
        sum += i;
    }
    return sum;  // 返回 55
};

④ 返回类型(通常自动推导)

// 自动推导返回类型
[](int x, int y) { return x + y; };  // 返回 int

// 显式指定返回类型
[](int x, int y) -> double { 
    return (x + y) / 2.0; 
};  // 返回 double

// 多个 return 语句时,需要显式指定
[](bool flag) -> int {
    if (flag) {
        return 1;  // int
    } else {
        return 0.5;  // double,编译器会困惑
        // 需要指定返回类型为 double 或 auto
    }
};

⑤ mutable(可选)

允许修改按值捕获的变量

int counter = 0;

// ❌ 错误:不能修改按值捕获的变量
[=]() {
    // counter++;  // 编译错误
};

// ✅ 正确:使用 mutable
[=]() mutable {
    counter++;  // 可以修改副本,但不影响原始变量
    return counter;
};

3. 从简单到复杂的示例

示例1:最简单的 lambda

// 定义一个 lambda 并立即调用
auto result = []() {
    return "Hello, Lambda!";
}();  // 注意这里的 () 表示立即调用

std::cout << result;  // 输出: Hello, Lambda!

示例2:作为变量存储

// 定义一个 lambda 函数
auto add = [](int a, int b) {
    return a + b;
};

// 像普通函数一样使用
int sum = add(10, 20);  // sum = 30
std::cout << sum << std::endl;

// 另一个 lambda
auto greet = [](const std::string& name) {
    return "Hello, " + name + "!";
};

std::cout << greet("Alice") << std::endl;  // Hello, Alice!

示例3:捕获外部变量

cpp
int x = 5;
int y = 3;

// 捕获外部变量
auto multiply = [x, &y]() {
    // x 是按值捕获(副本)
    // y 是按引用捕获(原始变量)
    int result = x * y;
    y = 100;  // 可以修改 y,因为是引用
    return result;
};

std::cout << multiply() << std::endl;  // 输出: 15
std::cout << y << std::endl;          // 输出: 100(y 被修改)
std::cout << x << std::endl;          // 输出: 5(x 未被修改)

示例4:在 STL 算法中使用

cpp
#include <vector>
#include <algorithm>
#include <iostream>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    // 1. 使用 lambda 过滤偶数
    auto it = std::remove_if(numbers.begin(), numbers.end(), 
        [](int n) {
            return n % 2 == 0;  // 如果是偶数,移除
        });
    numbers.erase(it, numbers.end());
    
    // 2. 使用 lambda 排序(降序)
    std::sort(numbers.begin(), numbers.end(), 
        [](int a, int b) {
            return a > b;  // 降序排序
        });
    
    // 3. 使用 lambda 转换
    std::vector<int> squares;
    std::transform(numbers.begin(), numbers.end(), 
                   std::back_inserter(squares),
                   [](int n) {
                       return n * n;
                   });
    
    // 4. 使用 lambda 打印
    std::for_each(squares.begin(), squares.end(),
        [](int value) {
            std::cout << value << " ";
        });
    
    return 0;
}

示例5:实际应用场景

cpp
// 场景1:按钮点击事件(模拟)
class Button {
public:
    void setOnClick(std::function<void()> handler) {
        onClick = handler;
    }
    
    void click() {
        if (onClick) onClick();
    }
    
private:
    std::function<void()> onClick;
};

int main() {
    Button btn;
    int clickCount = 0;
    
    // 使用 lambda 作为事件处理器
    btn.setOnClick([&clickCount]() {
        clickCount++;
        std::cout << "按钮被点击了!次数: " << clickCount << std::endl;
    });
    
    btn.click();  // 输出: 按钮被点击了!次数: 1
    btn.click();  // 输出: 按钮被点击了!次数: 2
    
    return 0;
}
cpp
// 场景2:线程任务
#include <thread>
#include <vector>

int main() {
    std::vector<std::thread> threads;
    std::mutex mtx;
    
    // 创建 5 个线程,每个线程执行不同的任务
    for (int i = 0; i < 5; i++) {
        threads.emplace_back([i, &mtx]() {
            std::lock_guard<std::mutex> lock(mtx);
            std::cout << "线程 " << i << " 正在运行" << std::endl;
        });
    }
    
    // 等待所有线程完成
    for (auto& t : threads) {
        t.join();
    }
    
    return 0;
}

4. 学习路径建议

第1步:从最简单开始

cpp
// 先写一个什么都不做的 lambda
[]() {};

// 添加简单的返回
[]() { return 42; };

// 添加参数
[](int x) { return x * 2; };

第2步:练习捕获

cpp
int outside = 10;

// 尝试不同的捕获方式
[outside]() { /* 使用 outside 的副本 */ };
[&outside]() { /* 使用 outside 的引用 */ };
[=]() { /* 按值捕获所有 */ };
[&]() { /* 按引用捕获所有 */ };

第3步:在实际场景中使用

cpp
// 1. 排序
std::sort(vec.begin(), vec.end(), 
    [](auto a, auto b) { return a < b; });

// 2. 查找
auto it = std::find_if(vec.begin(), vec.end(),
    [](int x) { return x > 10; });

// 3. 条件变量(你的问题)
cv.wait(lock, [&]() { return !queue.empty(); });

5. 常见错误和注意事项

cpp
// ❌ 错误1:引用捕获局部变量(生命周期问题)
std::function<void()> createLambda() {
    int local = 42;
    return [&local]() {  // 危险!local 将被销毁
        std::cout << local;
    };
}

// ✅ 修正:按值捕获
std::function<void()> createLambda() {
    int local = 42;
    return [local]() {  // 安全:复制了 local
        std::cout << local;
    };
}

// ❌ 错误2:在 lambda 中修改按值捕获的变量(没有 mutable)
int x = 10;
[=]() {
    // x++;  // 编译错误
};

// ✅ 修正:使用 mutable
[=]() mutable {
    x++;  // 可以修改副本
};

// ❌ 错误3:复杂的 lambda 导致代码难读
// 如果 lambda 太长,考虑写成普通函数

6. 技巧和最佳实践

cpp
// 技巧1:给 lambda 起一个有意义的类型名
using CompareFunc = bool(*)(int, int);
CompareFunc lessThan = [](int a, int b) { return a < b; };

// 技巧2:使用 auto 避免复杂的类型声明
auto add = [](auto a, auto b) { return a + b; };

// 技巧3:lambda 可以作为返回值
auto makeMultiplier(int factor) {
    return [factor](int x) { return x * factor; };
}

auto timesTwo = makeMultiplier(2);
std::cout << timesTwo(5);  // 输出: 10

记住:lambda 表达式就是一个没有名字的函数,可以方便地写在需要函数的地方。 刚开始可能会觉得奇怪,但用多了就会发现它非常方便!

posted @ 2025-12-05 02:40  FBshark  阅读(24)  评论(0)    收藏  举报