C++11引入了lambda表达式,它允许开发者在代码中定义匿名函数对象(也称为闭包)。Lambda表达式特别适用于需要定义小型、一次性的函数对象的情况,例如在算法中作为谓词(predicate)或映射(mapping)函数。
Lambda表达式的语法通常如下:
[capture-list](parameter-list) -> return-type { function-body }
- capture-list:捕获列表,用于捕获外部作用域中的变量。
- parameter-list:参数列表,和普通函数的参数列表一样。
- return-type:返回类型,当函数体中的表达式类型可以明确推断出时,可以省略。
- function-body:函数体,包含函数实现的代码。
关于值传递和引用传递,捕获列表决定了lambda如何捕获外部变量。值传递是通过复制变量来实现的,而引用传递则是通过引用外部变量来实现的。
值传递案例
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
int x = 10;
// 值传递的lambda,它捕获了x的副本
auto lambda_value = [x](int y) {
std::cout << "Inside lambda (value): x = " << x << ", y = " << y << std::endl;
// 这里对x的修改不会影响外部变量x
x = 20; // 修改的是lambda内部x的副本
};
// 调用lambda
lambda_value(5);
// 输出外部变量x的值,仍然为10
std::cout << "Outside lambda: x = " << x << std::endl;
return 0;
}
引用传递案例
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
int x = 10;
// 引用传递的lambda,它捕获了x的引用
auto lambda_ref = [&x](int y) {
std::cout << "Inside lambda (reference): x = " << x << ", y = " << y << std::endl;
// 这里对x的修改会影响外部变量x
x = 20; // 修改的是外部变量x
};
// 调用lambda
lambda_ref(5);
// 输出外部变量x的值,已经为20
std::cout << "Outside lambda: x = " << x << std::endl;
return 0;
}
值传递和引用传递说明
在上面的例子中,你可以看到lambda通过值捕获([x])和引用捕获([&x])之间的区别。值捕获会创建外部变量的副本,并在lambda内部使用,而引用捕获则允许lambda直接访问和修改外部变量。
注意,引用捕获的lambda必须确保在lambda的生命周期内,其引用的外部变量是有效的,否则可能会导致未定义行为。例如,在函数内定义了一个lambda并返回它,而这个lambda引用了函数内的局部变量,这是不安全的,因为局部变量在函数返回后可能会被销毁。在这种情况下,应该使用值捕获或捕获指向局部变量的指针(如果该指针在lambda外部保持有效)。
应用场景
C++11 中的 lambda 表达式提供了一种简洁的方式来定义匿名函数对象(即函数对象但没有名字)。它们在许多场景中都非常有用,以下是一些常见的应用场景:
- 回调函数:在许多库中,你可能会遇到需要传递回调函数的情况。例如,当你使用定时器、线程、网络请求或文件I/O等异步操作时,当某个事件发生时,你可能会想要执行一些自定义的操作。lambda 表达式提供了一种方便的方式来定义这些回调函数。
- STL 算法:STL(标准模板库)提供了大量的算法,这些算法中的许多都接受函数对象作为参数。例如,std::sort、std::find_if 和 std::for_each 等函数都接受一个函数对象作为参数,以定义排序、查找或迭代的行为。lambda 表达式可以方便地作为这些函数的参数。
- 事件处理:在图形用户界面(GUI)编程中,你可能会遇到需要处理各种事件(如按钮点击、鼠标移动等)的情况。lambda 表达式可以方便地定义这些事件的处理函数。
- 并发编程:在并发编程中,lambda 表达式可以用于定义线程的任务。例如,你可以使用 std::thread 创建一个新线程,并将一个 lambda 表达式作为该线程的任务。
- 算法和函数的内部实现:当你正在编写一个算法或函数,并且该算法或函数需要接受一个函数作为参数以定义其行为时,lambda 表达式可以作为一个方便的参数。例如,你可以编写一个函数,该函数接受一个函数对象作为参数,并使用该函数对象对一组数据进行某种操作。
- 闭包:lambda 表达式可以捕获其所在作用域中的变量,这使得它们能够访问和操作这些变量。这种特性使得 lambda 表达式在需要访问局部变量的上下文中特别有用。
- 简洁性:在某些情况下,你可能只需要执行一个简短的操作,而这个操作并不值得为其定义一个完整的函数或函数对象。在这种情况下,lambda 表达式提供了一种简洁的方式来定义这个操作。
总的来说,lambda 表达式提供了一种灵活、简洁的方式来定义匿名函数对象,这使得它们在许多编程场景中都非常有用。
接下来
展示一个应用了4、5、6条的代码案例(个人觉得还是比较典型的)
#pragma once
class Thread_Test
{
//基本操作演示
public:
Thread_Test();
~Thread_Test();
void DisplayThread_Test();
//定义个函数,被多个线程执行
void print_number(int id) {
for (int i = 0; i < 10; i++)
{
std::cout << "Thread-" << id << ": Times-" << i << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
};
Thread_Test::Thread_Test()
{
}
Thread_Test::~Thread_Test()
{
}
inline void Thread_Test::DisplayThread_Test()
{
//创建一个线程向量
std::vector<std::thread> vThreads;
for (int i = 0; i < 5; i++)
{
//Thread语法
//template <class Fn, class… Args> explicit thread(Fn && fn, Args && … args)
//创建一个线程,执行 fn 函数,args 为函数参数
// 问题在于你尝试直接从 Thread_Test 类的外部创建了一个 std::thread 对象,
// 而没有指定要调用哪个 Thread_Test 对象的 print_number 方法。
// std::thread 需要一个类的实例和该实例的成员函数才能正确地构造线程。
//vThreads.push_back(std::thread(print_number, i));
//你需要传递 Thread_Test 类的当前实例(this 指针)给 std::thread 的构造函数,
//以便它可以调用实例的成员函数。这可以通过使用 std::bind 或者 lambda 表达式来实现。
//lambda表达式
vThreads.push_back(std::thread([this, i]() {this->print_number(i); }));
//bind
//vThreads.push_back(std::thread(std::bind(&Thread_Test::print_number, this, i));
}
//for (auto& v : vThreads)
for (size_t i = 0; i < 5; i++)
{
vThreads[i].join();
}
}
当时编写这段代码遇到的问题,现在看来是:
- 在C++中,std::thread的构造函数需要一个可调用的对象(比如函数、成员函数指针与对象、lambda表达式、函数对象(也称为仿函数或functor)等)以及它的参数列表。当尝试使用this->print_number(i)时,实际上是在调用成员函数并尝试将返回值(如果有的话)传递给std::thread,但print_number是一个void函数,没有返回值,因此这将导致编译错误。
- 为了正确地将成员函数作为线程的目标函数,需要传递一个可调用的对象以及需要的参数给std::thread。
匿名函数
- 在上述场景中,lambda表达式实际上是创建了一个匿名函数(也被称为闭包),并且这个匿名函数被传递给了std::thread的构造函数。这个匿名函数包含了要在线程中执行的代码,以及可能捕获的任何外部变量(通过值或引用)。
- 在C++中,匿名函数(也称为lambda表达式或lambda函数)是通过语言本身提供的lambda语法糖(syntactic sugar)来实现的。这些语法糖使得程序员能够以简洁的方式定义小型的、内联的函数对象(function objects),而无需显式地定义一个完整的类。
- 在C++的上下文中,当你使用lambda表达式定义一个函数时,这个函数就是一个匿名函数。如果这个lambda表达式捕获了外部变量,那么它就形成了一个闭包。因此,在很多情况下,人们可能会将这些术语交替使用来描述lambda表达式的特性。但在严格意义上,它们各自有不同的定义和用途。
总结来说,闭包、匿名函数和lambda表达式在C++中通常是相互关联的,但它们各自有明确的定义和用途。在大多数上下文中,lambda表达式是实现匿名函数和闭包的一种手段。