跳到主要内容

C++ 11 Lambda表达式

什么是Lambda表达式?

Lambda表达式是C++11引入的一项重要特性,它允许我们定义匿名函数对象(又称闭包)。在需要短小函数的地方,使用Lambda表达式可以让代码更加简洁、易读,并且避免了编写额外的函数对象类。

Lambda表达式让C++具备了一些函数式编程的特点,为代码编写提供了更多灵活性。

备注

在C++11之前,若要创建临时使用的函数对象,通常需要定义一个完整的类或结构体,这会增加代码的复杂度。Lambda表达式解决了这一问题。

Lambda表达式的基本语法

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

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

各部分解释:

  • [capture-list]:捕获列表,指定哪些外部变量可以在Lambda函数体中使用
  • (parameters):参数列表,与普通函数的参数列表类似
  • specifiers:可选的函数说明符(如mutablenoexcept等)
  • -> return_type:可选的返回类型声明
  • { body }:函数体,包含Lambda的实际代码

最简单的Lambda表达式

让我们从一个最简单的例子开始:

cpp
#include <iostream>

int main() {
// 一个简单的Lambda表达式,不接受参数,返回整数5
auto lambda = []() { return 5; };

// 调用Lambda表达式
std::cout << "Lambda返回值: " << lambda() << std::endl;

return 0;
}

输出:

Lambda返回值: 5

捕获列表详解

Lambda表达式的捕获列表用于指定Lambda可以访问的外部变量。捕获有以下几种形式:

  1. []:不捕获任何外部变量
  2. [=]:按值捕获所有外部变量
  3. [&]:按引用捕获所有外部变量
  4. [var]:按值捕获变量var
  5. [&var]:按引用捕获变量var
  6. [=, &var]:按值捕获所有外部变量,但var按引用捕获
  7. [&, var]:按引用捕获所有外部变量,但var按值捕获

按值捕获

cpp
#include <iostream>

int main() {
int x = 10;

// 按值捕获x
auto lambda = [x]() {
std::cout << "x的值为: " << x << std::endl;
// x = 20; // 错误!按值捕获的变量是只读的
};

lambda(); // 调用Lambda
x = 20; // 修改外部变量x
lambda(); // 再次调用Lambda,输出的x仍为10

return 0;
}

输出:

x的值为: 10
x的值为: 10

按引用捕获

cpp
#include <iostream>

int main() {
int x = 10;

// 按引用捕获x
auto lambda = [&x]() {
std::cout << "x的值为: " << x << std::endl;
x = 20; // 可以修改x的值
};

lambda(); // 调用Lambda,输出x为10,然后修改为20
std::cout << "修改后外部x的值为: " << x << std::endl;

return 0;
}

输出:

x的值为: 10
修改后外部x的值为: 20

使用mutable修饰符

如果需要在Lambda内部修改按值捕获的变量,可以使用mutable关键字:

cpp
#include <iostream>

int main() {
int x = 10;

// 使用mutable修饰符
auto lambda = [x]() mutable {
x += 5;
std::cout << "Lambda内部x的值为: " << x << std::endl;
};

lambda(); // 调用Lambda,输出x为15
lambda(); // 再次调用,输出x为20
std::cout << "外部x的值仍为: " << x << std::endl; // 外部x不受影响

return 0;
}

输出:

Lambda内部x的值为: 15
Lambda内部x的值为: 20
外部x的值仍为: 10

Lambda与算法库结合使用

Lambda表达式与标准库算法结合使用是其最常见的应用场景之一。

使用Lambda进行自定义排序

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

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

// 使用Lambda表达式进行降序排序
std::sort(numbers.begin(), numbers.end(), [](int a, int b) {
return a > b;
});

// 输出排序结果
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;

return 0;
}

输出:

5 4 3 2 1

结合find_if查找元素

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

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

// 查找第一个大于5且能被3整除的数
auto it = std::find_if(numbers.begin(), numbers.end(), [](int n) {
return n > 5 && n % 3 == 0;
});

if (it != numbers.end()) {
std::cout << "找到符合条件的数: " << *it << std::endl;
} else {
std::cout << "未找到符合条件的数" << std::endl;
}

return 0;
}

输出:

找到符合条件的数: 6

使用Lambda处理容器元素

使用for_each遍历容器

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

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

std::cout << "将所有元素平方:" << std::endl;

// 使用Lambda和for_each处理每个元素
std::for_each(numbers.begin(), numbers.end(), [](int& n) {
n = n * n;
});

// 输出结果
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;

return 0;
}

输出:

将所有元素平方:
1 4 9 16 25

使用Lambda计算累积值

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

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

// 使用Lambda计算总和
int sum = std::accumulate(numbers.begin(), numbers.end(), 0,
[](int total, int value) {
return total + value;
}
);

std::cout << "元素总和: " << sum << std::endl;

// 使用Lambda计算乘积
int product = std::accumulate(numbers.begin(), numbers.end(), 1,
[](int total, int value) {
return total * value;
}
);

std::cout << "元素乘积: " << product << std::endl;

return 0;
}

输出:

元素总和: 15
元素乘积: 120

Lambda表达式的实际应用场景

自定义GUI事件处理

在GUI编程中,Lambda表达式可以简化事件处理代码:

cpp
#include <iostream>
#include <functional>
#include <string>

// 模拟一个简单的按钮类
class Button {
public:
Button(const std::string& label) : m_label(label) {}

// 设置点击事件处理函数
void setOnClickHandler(std::function<void()> handler) {
m_clickHandler = handler;
}

// 模拟按钮被点击
void click() {
std::cout << "按钮 '" << m_label << "' 被点击" << std::endl;
if (m_clickHandler) {
m_clickHandler();
}
}

private:
std::string m_label;
std::function<void()> m_clickHandler;
};

int main() {
Button saveButton("保存");
Button cancelButton("取消");

int saveCount = 0;

// 使用Lambda设置点击处理函数
saveButton.setOnClickHandler([&saveCount]() {
++saveCount;
std::cout << "文件已保存!总共保存了 " << saveCount << " 次。" << std::endl;
});

cancelButton.setOnClickHandler([]() {
std::cout << "操作已取消!" << std::endl;
});

// 模拟用户点击
saveButton.click();
cancelButton.click();
saveButton.click();

return 0;
}

输出:

按钮 '保存' 被点击
文件已保存!总共保存了 1 次。
按钮 '取消' 被点击
操作已取消!
按钮 '保存' 被点击
文件已保存!总共保存了 2 次。

数据转换与过滤

Lambda表达式适合用于数据处理的场景,尤其是数据转换和过滤:

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

struct Student {
std::string name;
int score;
};

int main() {
std::vector<Student> students = {
{"Alice", 85},
{"Bob", 72},
{"Charlie", 90},
{"David", 65},
{"Eva", 88}
};

// 找出分数>=80的学生并输出
std::cout << "优秀学生名单:" << std::endl;
std::for_each(students.begin(), students.end(), [](const Student& s) {
if (s.score >= 80) {
std::cout << s.name << " - " << s.score << " 分" << std::endl;
}
});

// 计算平均分
int totalScore = 0;
std::for_each(students.begin(), students.end(), [&totalScore](const Student& s) {
totalScore += s.score;
});
double averageScore = static_cast<double>(totalScore) / students.size();

std::cout << "\n班级平均分: " << averageScore << std::endl;

return 0;
}

输出:

优秀学生名单:
Alice - 85 分
Charlie - 90 分
Eva - 88 分

班级平均分: 80

递归Lambda表达式

从C++14开始,我们可以创建递归的Lambda表达式:

cpp
#include <iostream>
#include <functional>

int main() {
// 计算斐波那契数列的递归Lambda
std::function<int(int)> fibonacci = [&fibonacci](int n) {
if (n <= 1) return n;
return fibonacci(n-1) + fibonacci(n-2);
};

std::cout << "斐波那契数列的前10个数:" << std::endl;
for (int i = 0; i < 10; ++i) {
std::cout << fibonacci(i) << " ";
}
std::cout << std::endl;

return 0;
}

输出:

斐波那契数列的前10个数:
0 1 1 2 3 5 8 13 21 34
警告

递归Lambda可能导致性能问题,特别是在没有尾递归优化的编译器中。对于复杂的递归算法,通常还是建议使用命名函数。

Lambda表达式与std::function

Lambda表达式可以被存储到std::function对象中,这使得我们可以将Lambda作为参数传递或存储:

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

void processData(const std::vector<int>& data, std::function<void(int)> processor) {
for (int value : data) {
processor(value);
}
}

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

// 创建两个不同的处理函数
std::function<void(int)> printSquare = [](int x) {
std::cout << x * x << " ";
};

std::function<void(int)> printDouble = [](int x) {
std::cout << x * 2 << " ";
};

std::cout << "原始数据的平方:";
processData(numbers, printSquare);
std::cout << std::endl;

std::cout << "原始数据的两倍:";
processData(numbers, printDouble);
std::cout << std::endl;

return 0;
}

输出:

原始数据的平方:1 4 9 16 25 
原始数据的两倍:2 4 6 8 10

总结

Lambda表达式是C++11引入的一项强大特性,它大大简化了代码编写,特别是在需要使用小型、临时函数对象的地方。主要优点包括:

  1. 代码简洁:无需创建单独的函数或函数对象类
  2. 就地定义:可以在使用的地方立即定义函数逻辑
  3. 捕获上下文:可以访问作用域内的变量
  4. 提高可读性:使算法的意图更加明确

在使用Lambda表达式时,需要注意:

  • 合理选择捕获方式,避免不必要的拷贝开销
  • 注意按值捕获的变量默认是只读的,需要修改时使用mutable
  • 复杂逻辑仍然建议使用命名函数,以提高代码可读性和可维护性
编程建议

Lambda表达式最适合用于简短的、一次性使用的函数逻辑。如果函数逻辑复杂或需要在多个地方重用,应考虑定义命名函数或函数对象类。

练习

  1. 编写一个Lambda表达式,计算一个整数向量中所有偶数的和。
  2. 使用Lambda表达式结合std::sort,按照字符串长度对一个字符串向量进行排序。
  3. 创建一个Lambda表达式,检查一个字符串是否是回文(正着读和倒着读一样)。
  4. 使用Lambda表达式和std::transform,将一个整数向量中的所有元素转换为它们的绝对值。
  5. 实现一个简单的计算器函数,接受两个数和一个Lambda表达式作为操作。

进一步阅读

  • 《C++ Primer》(第5版)中关于Lambda表达式的章节
  • C++官方文档对Lambda表达式的解释
  • Effective Modern C++(作者:Scott Meyers)中关于Lambda表达式的条款

通过掌握Lambda表达式,你将能够编写更简洁、更现代的C++代码,特别是在与算法库结合使用时,能显著提高代码的表达能力和可读性。