【C++11 之lambda表达式-语法+值传递、引用传递+应用场景】附匿名函数

C++11引入了lambda表达式,它允许开发者在代码中定义匿名函数对象(也称为闭包)。Lambda表达式特别适用于需要定义小型、一次性的函数对象的情况,例如在算法中作为谓词(predicate)或映射(mapping)函数。

Lambda表达式的语法通常如下:

[capture-list](parameter-list) -> return-type { function-body }
  1. capture-list:捕获列表,用于捕获外部作用域中的变量。
  2. parameter-list:参数列表,和普通函数的参数列表一样。
  3. return-type:返回类型,当函数体中的表达式类型可以明确推断出时,可以省略。
  4. 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 表达式提供了一种简洁的方式来定义匿名函数对象(即函数对象但没有名字)。它们在许多场景中都非常有用,以下是一些常见的应用场景:

  1. 回调函数:在许多库中,你可能会遇到需要传递回调函数的情况。例如,当你使用定时器、线程、网络请求或文件I/O等异步操作时,当某个事件发生时,你可能会想要执行一些自定义的操作。lambda 表达式提供了一种方便的方式来定义这些回调函数。
  2. STL 算法:STL(标准模板库)提供了大量的算法,这些算法中的许多都接受函数对象作为参数。例如,std::sort、std::find_if 和 std::for_each 等函数都接受一个函数对象作为参数,以定义排序、查找或迭代的行为。lambda 表达式可以方便地作为这些函数的参数。
  3. 事件处理:在图形用户界面(GUI)编程中,你可能会遇到需要处理各种事件(如按钮点击、鼠标移动等)的情况。lambda 表达式可以方便地定义这些事件的处理函数。
  4. 并发编程:在并发编程中,lambda 表达式可以用于定义线程的任务。例如,你可以使用 std::thread 创建一个新线程,并将一个 lambda 表达式作为该线程的任务。
  5. 算法和函数的内部实现:当你正在编写一个算法或函数,并且该算法或函数需要接受一个函数作为参数以定义其行为时,lambda 表达式可以作为一个方便的参数。例如,你可以编写一个函数,该函数接受一个函数对象作为参数,并使用该函数对象对一组数据进行某种操作。
  6. 闭包:lambda 表达式可以捕获其所在作用域中的变量,这使得它们能够访问和操作这些变量。这种特性使得 lambda 表达式在需要访问局部变量的上下文中特别有用。
  7. 简洁性:在某些情况下,你可能只需要执行一个简短的操作,而这个操作并不值得为其定义一个完整的函数或函数对象。在这种情况下,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();
	}
}

当时编写这段代码遇到的问题,现在看来是:

  1. 在C++中,std::thread的构造函数需要一个可调用的对象(比如函数、成员函数指针与对象、lambda表达式、函数对象(也称为仿函数或functor)等)以及它的参数列表。当尝试使用this->print_number(i)时,实际上是在调用成员函数并尝试将返回值(如果有的话)传递给std::thread,但print_number是一个void函数,没有返回值,因此这将导致编译错误。
  2. 为了正确地将成员函数作为线程的目标函数,需要传递一个可调用的对象以及需要的参数给std::thread。

匿名函数

  1. 在上述场景中,lambda表达式实际上是创建了一个匿名函数(也被称为闭包),并且这个匿名函数被传递给了std::thread的构造函数。这个匿名函数包含了要在线程中执行的代码,以及可能捕获的任何外部变量(通过值或引用)。
  2. 在C++中,匿名函数(也称为lambda表达式或lambda函数)是通过语言本身提供的lambda语法糖(syntactic sugar)来实现的。这些语法糖使得程序员能够以简洁的方式定义小型的、内联的函数对象(function objects),而无需显式地定义一个完整的类。
  3. 在C++的上下文中,当你使用lambda表达式定义一个函数时,这个函数就是一个匿名函数。如果这个lambda表达式捕获了外部变量,那么它就形成了一个闭包。因此,在很多情况下,人们可能会将这些术语交替使用来描述lambda表达式的特性。但在严格意义上,它们各自有不同的定义和用途。

总结来说,闭包、匿名函数和lambda表达式在C++中通常是相互关联的,但它们各自有明确的定义和用途。在大多数上下文中,lambda表达式是实现匿名函数和闭包的一种手段。

  • 29
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

another heaven

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值