C++ Lambda表达式
介绍
Lambda表达式是C++11引入的一种新特性,它允许我们定义一个匿名函数对象,可以在需要函数的地方直接内联定义短小的函数,而不需要按照传统方式单独定义一个命名函数。Lambda表达式使代码更加紧凑、可读,并且提高了开发效率。
对于初学者来说,Lambda表达式可能看起来有些复杂,但掌握它会让你的C++编程能力更上一层楼。本文将从基础概念出发,逐步讲解Lambda表达式的语法、用法及其在实际开发中的应用场景。
Lambda表达式基础语法
一个Lambda表达式的基本语法如下:
[capture clause] (parameters) -> return_type {
// 函数体
}
各部分解释:
[capture clause]
: 捕获列表,用于捕获外部变量(parameters)
: 参数列表,与普通函数的参数列表相同-> return_type
: 返回类型(可选),编译器通常能自动推导{ // 函数体 }
: 函数的实现代码
让我们通过一个简单的例子来理解Lambda的基本用法:
#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函数使用其定义作用域内的变量。
捕获有几种不同的方式:
- 值捕获:
[var]
- 按值捕获变量var - 引用捕获:
[&var]
- 按引用捕获变量var - 隐式值捕获所有变量:
[=]
- 按值捕获所有外部变量 - 隐式引用捕获所有变量:
[&]
- 按引用捕获所有外部变量 - 混合方式:
[=, &var]
- 除var按引用捕获外,其他变量按值捕获 - 混合方式:
[&, var]
- 除var按值捕获外,其他变量按引用捕获
下面是一个展示不同捕获方式的例子:
#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
关键字:
#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表达式可以作为函数参数传递,特别是在需要回调函数的场景中非常有用:
#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进行自定义排序
#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与标准算法处理数据
#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表达式:
auto add = [](int x, int y) { return x + y; };
编译器大致会将其转换为类似于以下代码:
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引入的强大特性,通过以下方面提高了代码质量:
- 简化了短小函数的定义方式
- 让代码更加紧凑和具有可读性
- 增强了标准库算法的灵活性
- 提供了便捷的回调机制
关键点回顾:
- Lambda表达式由捕获列表、参数列表、返回类型和函数体组成
- 捕获列表控制外部变量在Lambda中的访问方式(值捕获或引用捕获)
- 使用
mutable
关键字可以修改值捕获的变量 - Lambda表达式可以存储在变量中、作为参数传递或立即调用
- Lambda表达式在STL算法中特别有用
练习
- 编写一个Lambda表达式,计算一个整数的阶乘。
- 创建一个Lambda表达式,检查一个字符串是否为回文。
- 使用Lambda表达式和
std::sort
函数,对一个结构体数组按照多个条件排序(例如,先按年龄排序,年龄相同时按姓名排序)。 - 编写一个接受Lambda表达式作为参数的函数,该函数对一个整数数组中的每个元素应用Lambda表达式,并返回一个新数组。
延伸阅读
- Lambda表达式与标准库函数对象(如
std::function
)的结合使用 - C++14/17/20中Lambda表达式的新特性(如泛型Lambda、捕获this等)
- 递归Lambda表达式的实现方法
掌握Lambda表达式可以让你的代码更简洁、更灵活,特别是在使用STL算法时能够发挥巨大优势。不过,也要避免过度使用Lambda导致代码难以理解和维护。合理使用是关键!