跳到主要内容

C++ Lambda 表达式

Lambda 表达式是 C++11 引入的一种创建匿名函数对象的方式。它让我们可以在需要函数的地方直接定义函数,大大简化了代码,特别是在使用 STL 算法和回调函数时。

Lambda 基础语法

基本形式

Lambda 表达式的完整语法:

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

各部分说明:

部分说明是否必需
[capture]捕获列表,指定如何捕获外部变量必需
(parameters)参数列表可选(无参数时可省略)
mutable允许修改捕获的变量(值捕获时)可选
-> return_type返回类型可选(可自动推导)
{ body }函数体必需

最简单的 Lambda

#include <iostream>

int main() {
// 最简单的 lambda:无捕获、无参数
auto greet = []() {
std::cout << "Hello, Lambda!" << std::endl;
};
greet(); // 调用 lambda

// 更简洁:直接调用
[]() { std::cout << "直接调用" << std::endl; }();

// 带参数的 lambda
auto add = [](int a, int b) {
return a + b;
};
std::cout << add(3, 5) << std::endl; // 8

// 自动推导返回类型
auto multiply = [](int a, int b) {
return a * b; // 返回类型推导为 int
};

// 显式指定返回类型
auto divide = [](int a, int b) -> double {
return static_cast<double>(a) / b;
};
std::cout << divide(10, 3) << std::endl; // 3.33333

return 0;
}

捕获外部变量

捕获列表是 Lambda 最核心的特性,它决定了 Lambda 如何访问外部作用域的变量。

捕获方式

#include <iostream>

int main() {
int x = 10;
int y = 20;

// 1. 不捕获任何变量
auto f1 = []() {
// 不能使用 x 或 y
return 42;
};

// 2. 值捕获指定变量
auto f2 = [x]() {
return x * 2; // 使用 x 的副本
};

// 3. 引用捕获指定变量
auto f3 = [&x]() {
x = 100; // 修改外部变量 x
};
f3();
std::cout << x << std::endl; // 100

// 4. 值捕获所有变量
auto f4 = [=]() {
return x + y; // 使用 x 和 y 的副本
};

// 5. 引用捕获所有变量
auto f5 = [&]() {
x = 1; // 修改外部变量
y = 2;
};

// 6. 混合捕获
auto f6 = [=, &x]() { // 默认值捕获,x 引用捕获
x = y + 1; // x 是引用,y 是值
};

auto f7 = [&, x]() { // 默认引用捕获,x 值捕获
y = x + 1; // y 是引用,x 是值
};

// 7. 初始化捕获(C++14)
int z = 30;
auto f8 = [z = z + 10]() { // 捕获表达式的结果
return z; // 40
};

// 8. 移动捕获(C++14)
auto ptr = std::make_unique<int>(42);
auto f9 = [p = std::move(ptr)]() {
return *p;
};

return 0;
}

捕获方式对比

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

void captureDemo() {
int value = 100;

// 值捕获:捕获变量的副本
auto byValue = [value]() {
// value 是副本,无法修改外部变量
return value;
};

// 引用捕获:捕获变量的引用
auto byReference = [&value]() {
value = 200; // 修改外部变量
};

// 选择原则:
// - 只读使用:值捕获更安全
// - 需要修改:引用捕获
// - 生命周期问题:值捕获或智能指针
}

捕获陷阱

#include <iostream>
#include <functional>

// 错误示例:返回引用捕获的 lambda
std::function<int()> badFactory() {
int x = 42;
return [&x]() { return x; }; // 危险!x 已销毁
}

// 正确示例:返回值捕获的 lambda
std::function<int()> goodFactory() {
int x = 42;
return [x]() { return x; }; // 安全:捕获副本
}

// 悬空引用示例
void danglingDemo() {
std::function<void()> func;
{
int local = 10;
func = [&local]() { // 危险!
std::cout << local << std::endl;
};
} // local 销毁
func(); // 未定义行为!访问已销毁的变量
}

// 正确做法:值捕获
void safeDemo() {
std::function<void()> func;
{
int local = 10;
func = [local]() { // 安全
std::cout << local << std::endl;
};
}
func(); // 正常工作
}

mutable 关键字

默认情况下,值捕获的变量在 Lambda 内是只读的。使用 mutable 可以修改这些副本:

#include <iostream>

int main() {
int counter = 0;

// 没有 mutable:不能修改捕获的值
// auto f1 = [counter]() { counter++; }; // 错误!

// 有 mutable:可以修改捕获的值的副本
auto f2 = [counter]() mutable {
counter++; // 修改的是副本
return counter;
};

std::cout << f2() << std::endl; // 1
std::cout << f2() << std::endl; // 2
std::cout << f2() << std::endl; // 3
std::cout << counter << std::endl; // 0(外部变量不变)

// 引用捕获不需要 mutable
auto f3 = [&counter]() {
counter++; // 直接修改外部变量
};
f3();
std::cout << counter << std::endl; // 1

return 0;
}

Lambda 与 STL 算法

Lambda 在 STL 算法中应用广泛,可以简化大量代码。

排序

#include <algorithm>
#include <vector>
#include <string>
#include <iostream>

void sortDemo() {
std::vector<int> nums = {5, 2, 8, 1, 9};

// 升序排序
std::sort(nums.begin(), nums.end());

// 降序排序
std::sort(nums.begin(), nums.end(),
[](int a, int b) { return a > b; });

// 自定义排序
std::vector<std::string> names = {"Alice", "Bob", "Charlie"};
std::sort(names.begin(), names.end(),
[](const std::string& a, const std::string& b) {
return a.length() < b.length(); // 按长度排序
});

// 结构体排序
struct Person {
std::string name;
int age;
};

std::vector<Person> people = {
{"Alice", 30}, {"Bob", 25}, {"Charlie", 35}
};

std::sort(people.begin(), people.end(),
[](const Person& a, const Person& b) {
return a.age < b.age; // 按年龄排序
});
}

查找和遍历

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

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

// 查找第一个偶数
auto it = std::find_if(nums.begin(), nums.end(),
[](int n) { return n % 2 == 0; });
if (it != nums.end()) {
std::cout << "第一个偶数: " << *it << std::endl; // 2
}

// 查找所有偶数
std::vector<int> evens;
std::copy_if(nums.begin(), nums.end(), std::back_inserter(evens),
[](int n) { return n % 2 == 0; });

// 遍历打印
std::for_each(nums.begin(), nums.end(),
[](int n) { std::cout << n << " "; });
std::cout << std::endl;

// 带状态的遍历
int sum = 0;
std::for_each(nums.begin(), nums.end(),
[&sum](int n) { sum += n; });
std::cout << "总和: " << sum << std::endl;

// 检查是否所有元素都满足条件
bool allPositive = std::all_of(nums.begin(), nums.end(),
[](int n) { return n > 0; });

// 检查是否存在满足条件的元素
bool hasEven = std::any_of(nums.begin(), nums.end(),
[](int n) { return n % 2 == 0; });
}

变换和过滤

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

void transformDemo() {
std::vector<int> nums = {1, 2, 3, 4, 5};
std::vector<int> result(nums.size());

// 变换:每个元素乘以 2
std::transform(nums.begin(), nums.end(), result.begin(),
[](int n) { return n * 2; });
// result = {2, 4, 6, 8, 10}

// 过滤:移除偶数
nums.erase(
std::remove_if(nums.begin(), nums.end(),
[](int n) { return n % 2 == 0; }),
nums.end()
);
// nums = {1, 3, 5}

// 替换:将所有 3 替换为 30
std::replace_if(nums.begin(), nums.end(),
[](int n) { return n == 3; }, 30);

// 分区:将偶数放前面
std::vector<int> nums2 = {1, 2, 3, 4, 5, 6, 7, 8};
std::partition(nums2.begin(), nums2.end(),
[](int n) { return n % 2 == 0; });
}

Lambda 作为回调函数

Lambda 作为回调函数使用非常方便:

#include <functional>
#include <vector>
#include <iostream>

// 使用 std::function 存储回调
class Button {
std::function<void()> onClick;

public:
void setOnClick(std::function<void()> callback) {
onClick = callback;
}

void click() {
if (onClick) onClick();
}
};

// 使用示例
void callbackDemo() {
Button btn;
int clickCount = 0;

btn.setOnClick([&clickCount]() {
clickCount++;
std::cout << "按钮点击次数: " << clickCount << std::endl;
});

btn.click(); // 输出:按钮点击次数: 1
btn.click(); // 输出:按钮点击次数: 2
}

// 事件处理
class EventEmitter {
std::vector<std::function<void(int)>> handlers;

public:
void on(std::function<void(int)> handler) {
handlers.push_back(handler);
}

void emit(int value) {
for (auto& handler : handlers) {
handler(value);
}
}
};

void eventDemo() {
EventEmitter emitter;

// 注册多个处理器
emitter.on([](int x) { std::cout << "处理器1: " << x << std::endl; });
emitter.on([](int x) { std::cout << "处理器2: " << x * 2 << std::endl; });
emitter.on([](int x) { std::cout << "处理器3: " << x * x << std::endl; });

emitter.emit(5);
// 输出:
// 处理器1: 5
// 处理器2: 10
// 处理器3: 25
}

高级用法

立即调用的 Lambda

#include <iostream>

int main() {
// 立即调用
int result = [](int a, int b) {
return a + b;
}(3, 5); // 直接传参并调用

std::cout << result << std::endl; // 8

// 用于初始化 const 变量
const int value = [&]() {
int x = computeSomething();
int y = computeOther();
return x + y;
}();

// 替代复杂的初始化逻辑
const std::string config = []() {
std::ifstream file("config.txt");
std::string content((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
return content;
}();

return 0;
}

递归 Lambda

#include <functional>
#include <iostream>

int main() {
// 方式1:使用 std::function
std::function<int(int)> factorial = [&factorial](int n) -> int {
return n <= 1 ? 1 : n * factorial(n - 1);
};
std::cout << factorial(5) << std::endl; // 120

// 方式2:Y 组合子(高级技巧)
auto Y = [](auto f) {
return [f](auto&&... args) {
return f(f, std::forward<decltype(args)>(args)...);
};
};

auto fib = Y([](auto self, int n) -> int {
return n <= 1 ? n : self(self, n - 1) + self(self, n - 2);
});
std::cout << fib(10) << std::endl; // 55

return 0;
}

泛型 Lambda(C++14)

#include <iostream>

int main() {
// 泛型 lambda:参数使用 auto
auto add = [](auto a, auto b) {
return a + b;
};

std::cout << add(1, 2) << std::endl; // 3 (int)
std::cout << add(1.5, 2.5) << std::endl; // 4.0 (double)
std::cout << add(std::string("hello"), std::string(" world")) << std::endl;

// 泛型 lambda 的多种用法
auto print = [](const auto& value) {
std::cout << value << std::endl;
};

print(42);
print(3.14);
print("hello");

// 复杂的泛型 lambda
auto process = []<typename T>(T value) { // C++20 模板语法
if constexpr (std::is_integral_v<T>) {
return value * 2;
} else {
return value;
}
};

return 0;
}

constexpr Lambda(C++17)

// C++17: lambda 可以是 constexpr
constexpr auto square = [](int n) { return n * n; };
static_assert(square(5) == 25, "5 squared should be 25");

// 在编译时使用
constexpr int arr[square(3)] = {0, 1, 2, 3, 4, 5, 6, 7, 8};

Lambda 的类型

Lambda 的类型是什么?

每个 Lambda 表达式都有唯一的类型,这是一个编译器生成的匿名类:

#include <type_traits>

int main() {
auto f1 = []() {};
auto f2 = []() {};

// f1 和 f2 的类型不同
static_assert(!std::is_same_v<decltype(f1), decltype(f2)>);

// 不捕获的 lambda 可以转换为函数指针
auto f3 = [](int x) { return x * 2; };
int (*funcPtr)(int) = f3; // 正确

// 捕获的 lambda 不能转换为函数指针
int y = 10;
auto f4 = [y](int x) { return x + y; };
// int (*funcPtr2)(int) = f4; // 错误!

return 0;
}

存储 Lambda

#include <functional>
#include <vector>
#include <iostream>

int main() {
// 使用 std::function 存储
std::function<int(int, int)> op;

op = [](int a, int b) { return a + b; };
std::cout << op(3, 5) << std::endl; // 8

op = [](int a, int b) { return a * b; };
std::cout << op(3, 5) << std::endl; // 15

// 存储 lambda 的 vector
std::vector<std::function<int(int)>> functions = {
[](int x) { return x + 1; },
[](int x) { return x * 2; },
[](int x) { return x * x; }
};

for (auto& f : functions) {
std::cout << f(5) << std::endl; // 6, 10, 25
}

return 0;
}

性能考虑

Lambda 的性能特点

#include <algorithm>
#include <vector>

void performanceDemo() {
std::vector<int> vec = {1, 2, 3, 4, 5};

// 1. 无捕获 lambda:通常内联,零开销
std::sort(vec.begin(), vec.end(),
[](int a, int b) { return a < b; });

// 2. 值捕获:小对象通常内联,大对象有拷贝开销
int smallValue = 42;
std::sort(vec.begin(), vec.end(),
[smallValue](int a, int b) { return a < b + smallValue; });

// 3. 引用捕获:零开销,但要注意生命周期
int& ref = smallValue;
std::sort(vec.begin(), vec.end(),
[&ref](int a, int b) { return a < b + ref; });

// 4. std::function 有类型擦除开销
std::function<bool(int, int)> cmp = [](int a, int b) { return a < b; };
std::sort(vec.begin(), vec.end(), cmp); // 可能无法内联
}

避免不必要的捕获

// 不好的做法:捕获不必要的变量
void badExample() {
int a = 1, b = 2, c = 3;
auto f = [=]() { // 捕获了 a, b, c
return a + b; // 但只用到了 a 和 b
};
}

// 好的做法:只捕获需要的变量
void goodExample() {
int a = 1, b = 2, c = 3;
auto f = [a, b]() { // 只捕获 a 和 b
return a + b;
};
}

实用示例

自定义循环

#include <iostream>

void repeat(int n, std::function<void(int)> action) {
for (int i = 0; i < n; i++) {
action(i);
}
}

int main() {
repeat(5, [](int i) {
std::cout << "第 " << i << " 次" << std::endl;
});

return 0;
}

延迟执行

#include <functional>
#include <iostream>

class Defer {
std::function<void()> action;

public:
Defer(std::function<void()> f) : action(f) {}
~Defer() { action(); }
};

int main() {
std::cout << "开始" << std::endl;

{
Defer cleanup([]() {
std::cout << "清理资源" << std::endl;
});
std::cout << "处理中" << std::endl;
} // cleanup 析构,执行清理

std::cout << "结束" << std::endl;
return 0;
}

链式调用

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

template<typename T>
class Pipeline {
std::vector<T> data;

public:
Pipeline(std::vector<T> v) : data(std::move(v)) {}

Pipeline& filter(std::function<bool(const T&)> pred) {
data.erase(
std::remove_if(data.begin(), data.end(),
[&pred](const T& x) { return !pred(x); }),
data.end()
);
return *this;
}

Pipeline& map(std::function<T(const T&)> func) {
std::transform(data.begin(), data.end(), data.begin(), func);
return *this;
}

void forEach(std::function<void(const T&)> action) {
std::for_each(data.begin(), data.end(), action);
}
};

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

Pipeline<int>(nums)
.filter([](int x) { return x % 2 == 0; }) // 过滤偶数
.map([](int x) { return x * x; }) // 平方
.forEach([](int x) { std::cout << x << " "; }); // 输出: 4 16 36 64 100

return 0;
}

小结

本章学习了:

  1. Lambda 语法:完整的 Lambda 表达式形式
  2. 捕获机制:值捕获、引用捕获和混合捕获
  3. mutable 关键字:修改值捕获的变量
  4. STL 应用:在算法中使用 Lambda
  5. 回调函数:Lambda 作为回调
  6. 高级用法:递归、泛型、constexpr Lambda
  7. 类型和存储:Lambda 的类型和 std::function
  8. 性能考虑:正确使用 Lambda 以保证性能

下一步

接下来让我们学习 C++ 现代 C++ 特性,全面了解 C++11/14/17/20 的新特性。