跳到主要内容

C++ Lambda表达式

介绍

Lambda表达式是C++11引入的一种新特性,它允许我们定义一个匿名函数对象,可以在需要函数的地方直接内联定义短小的函数,而不需要按照传统方式单独定义一个命名函数。Lambda表达式使代码更加紧凑、可读,并且提高了开发效率。

对于初学者来说,Lambda表达式可能看起来有些复杂,但掌握它会让你的C++编程能力更上一层楼。本文将从基础概念出发,逐步讲解Lambda表达式的语法、用法及其在实际开发中的应用场景。

Lambda表达式基础语法

一个Lambda表达式的基本语法如下:

cpp
[capture clause] (parameters) -> return_type { 
// 函数体
}

各部分解释:

  • [capture clause]: 捕获列表,用于捕获外部变量
  • (parameters): 参数列表,与普通函数的参数列表相同
  • -> return_type: 返回类型(可选),编译器通常能自动推导
  • { // 函数体 }: 函数的实现代码

让我们通过一个简单的例子来理解Lambda的基本用法:

cpp
#include <iostream>

int main() {
// 定义一个简单的Lambda表达式并立即调用
auto result = [](int a, int b) {
return a + b;
}(5, 3);

std::cout << "结果: " << result << std::endl;

// 将Lambda存储在变量中
auto add = [](int x, int y) {
return x + y;
};

std::cout << "5 + 3 = " << add(5, 3) << std::endl;
std::cout << "10 + 20 = " << add(10, 20) << std::endl;

return 0;
}

输出:

结果: 8
5 + 3 = 8
10 + 20 = 30

捕获列表详解

捕获列表是Lambda表达式的一个重要组成部分,它允许Lambda函数使用其定义作用域内的变量。

捕获有几种不同的方式:

  1. 值捕获: [var] - 按值捕获变量var
  2. 引用捕获: [&var] - 按引用捕获变量var
  3. 隐式值捕获所有变量: [=] - 按值捕获所有外部变量
  4. 隐式引用捕获所有变量: [&] - 按引用捕获所有外部变量
  5. 混合方式: [=, &var] - 除var按引用捕获外,其他变量按值捕获
  6. 混合方式: [&, var] - 除var按值捕获外,其他变量按引用捕获

下面是一个展示不同捕获方式的例子:

cpp
#include <iostream>

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

// 值捕获
auto lambda1 = [x](){
// x不能被修改
return x;
};
std::cout << "值捕获: " << lambda1() << std::endl;

// 引用捕获
auto lambda2 = [&x](){
x = 100; // 可以修改x
return x;
};
std::cout << "引用捕获后x的值: " << lambda2() << std::endl;
std::cout << "原始x的值也被修改: " << x << std::endl;

// 隐式值捕获所有变量
auto lambda3 = [=](){
return x + y; // 使用x和y,但不能修改它们
};
std::cout << "隐式值捕获所有: " << lambda3() << std::endl;

// 隐式引用捕获所有变量
auto lambda4 = [&](){
x += 5;
y += 10;
return x + y;
};
std::cout << "隐式引用捕获所有: " << lambda4() << std::endl;
std::cout << "修改后的x: " << x << ", y: " << y << std::endl;

return 0;
}

输出:

值捕获: 10
引用捕获后x的值: 100
原始x的值也被修改: 100
隐式值捕获所有: 120
隐式引用捕获所有: 235
修改后的x: 105, y: 30
警告

使用引用捕获时要特别小心,确保被引用的变量在Lambda执行时仍然存在,否则会导致悬空引用。

mutable关键字

默认情况下,值捕获的变量在Lambda表达式内部是常量,不能被修改。如果需要修改,可以使用mutable关键字:

cpp
#include <iostream>

int main() {
int x = 10;

auto lambda = [x]() mutable {
x += 5; // 现在可以修改x了
return x;
};

std::cout << "Lambda内部修改后的x: " << lambda() << std::endl;
std::cout << "原始x不变: " << x << std::endl;

return 0;
}

输出:

Lambda内部修改后的x: 15
原始x不变: 10

Lambda作为函数参数

Lambda表达式可以作为函数参数传递,特别是在需要回调函数的场景中非常有用:

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

// 接受一个函数作为参数的函数
void processNumbers(const std::vector<int>& numbers, std::function<void(int)> processor) {
for (int num : numbers) {
processor(num);
}
}

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

// 将Lambda作为参数传递
processNumbers(nums, [](int n) {
std::cout << "处理数字: " << n * n << std::endl;
});

// 使用Lambda与算法库
std::cout << "\n使用for_each算法:" << std::endl;
std::for_each(nums.begin(), nums.end(), [](int n) {
std::cout << n << "的平方是: " << n * n << std::endl;
});

return 0;
}

输出:

处理数字: 1
处理数字: 4
处理数字: 9
处理数字: 16
处理数字: 25

使用for_each算法:
1的平方是: 1
2的平方是: 4
3的平方是: 9
4的平方是: 16
5的平方是: 25

Lambda与STL算法

C++标准库中的许多算法都可以接受函数对象作为参数,这使得Lambda表达式与STL算法配合使用变得非常方便。

实际应用示例:使用Lambda进行自定义排序

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

struct Person {
std::string name;
int age;
};

int main() {
std::vector<Person> people = {
{"张三", 25},
{"李四", 30},
{"王五", 20},
{"赵六", 35}
};

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

std::cout << "按年龄升序排序后:" << std::endl;
for (const auto& person : people) {
std::cout << person.name << ": " << person.age << "岁" << std::endl;
}

// 按姓名长度排序
std::sort(people.begin(), people.end(), [](const Person& a, const Person& b) {
return a.name.length() < b.name.length();
});

std::cout << "\n按姓名长度排序后:" << std::endl;
for (const auto& person : people) {
std::cout << person.name << ": " << person.age << "岁" << std::endl;
}

return 0;
}

输出:

按年龄升序排序后:
王五: 20岁
张三: 25岁
李四: 30岁
赵六: 35岁

按姓名长度排序后:
张三: 25岁
李四: 30岁
王五: 20岁
赵六: 35岁

实际应用示例:使用Lambda与标准算法处理数据

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

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

// 使用Lambda筛选偶数
std::vector<int> evenNumbers;
std::copy_if(numbers.begin(), numbers.end(), std::back_inserter(evenNumbers),
[](int n) { return n % 2 == 0; });

std::cout << "偶数有: ";
for (int num : evenNumbers) {
std::cout << num << " ";
}
std::cout << std::endl;

// 使用Lambda计算所有数字的平方和
int squareSum = std::accumulate(numbers.begin(), numbers.end(), 0,
[](int sum, int n) { return sum + n * n; });

std::cout << "所有数字的平方和: " << squareSum << std::endl;

// 使用Lambda将所有数字变为其两倍
std::transform(numbers.begin(), numbers.end(), numbers.begin(),
[](int n) { return n * 2; });

std::cout << "每个数字变为两倍后: ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;

return 0;
}

输出:

偶数有: 2 4 6 8 10 
所有数字的平方和: 385
每个数字变为两倍后: 2 4 6 8 10 12 14 16 18 20

Lambda表达式底层原理

Lambda表达式本质上是编译器生成的匿名函数对象(也称为闭包类型的对象)。每个Lambda表达式都会生成一个独特的类型,它重载了operator()使其可以像函数一样被调用。

例如,以下Lambda表达式:

cpp
auto add = [](int x, int y) { return x + y; };

编译器大致会将其转换为类似于以下代码:

cpp
class __lambda_unique_name {
public:
int operator()(int x, int y) const {
return x + y;
}
};

auto add = __lambda_unique_name();

这就是为什么Lambda表达式可以像函数一样被调用,并且可以存储在auto类型的变量中或传递给std::function

总结

Lambda表达式是C++11引入的强大特性,通过以下方面提高了代码质量:

  • 简化了短小函数的定义方式
  • 让代码更加紧凑和具有可读性
  • 增强了标准库算法的灵活性
  • 提供了便捷的回调机制

关键点回顾:

  1. Lambda表达式由捕获列表、参数列表、返回类型和函数体组成
  2. 捕获列表控制外部变量在Lambda中的访问方式(值捕获或引用捕获)
  3. 使用mutable关键字可以修改值捕获的变量
  4. Lambda表达式可以存储在变量中、作为参数传递或立即调用
  5. Lambda表达式在STL算法中特别有用

练习

  1. 编写一个Lambda表达式,计算一个整数的阶乘。
  2. 创建一个Lambda表达式,检查一个字符串是否为回文。
  3. 使用Lambda表达式和std::sort函数,对一个结构体数组按照多个条件排序(例如,先按年龄排序,年龄相同时按姓名排序)。
  4. 编写一个接受Lambda表达式作为参数的函数,该函数对一个整数数组中的每个元素应用Lambda表达式,并返回一个新数组。

延伸阅读

  1. Lambda表达式与标准库函数对象(如std::function)的结合使用
  2. C++14/17/20中Lambda表达式的新特性(如泛型Lambda、捕获this等)
  3. 递归Lambda表达式的实现方法
提示

掌握Lambda表达式可以让你的代码更简洁、更灵活,特别是在使用STL算法时能够发挥巨大优势。不过,也要避免过度使用Lambda导致代码难以理解和维护。合理使用是关键!