C++11学习之Lambda表达式

概述

C++的Lambda是C++11中引入的新特性,用于创建匿名函数,简化编程。一般用在需要传入函数的位置,而该函数在整个程序中只使用一次,定义它则失去了函数重用的价值,此时Lambda就派上用场了。

语法

[ capture ] ( params ) opt -> ret { body; };

参数解释如下:

  • capture 捕获列表,用于捕获表达式之外的参数
lambda 表达式通过捕获列表,捕获一定范围内的变量。
[] 不捕获任何变量。
[&] 捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获)。
[=] 捕获外部作用域中所有变量,并作为副本在函数体中使用(按值捕获)。
[=&task] 按值捕获外部作用域中所有变量,并按引用捕获 task 变量。
[flv] 按值捕获 flv 变量,同时不捕获其他变量。
[this] 捕获当前类中的 this 指针,让 lambda 表达式拥有和当前类成员函数同样的访问权限。如果已经使用了 & 或者 =,就默认添加此选项。捕获 this 的目的是可以在 lamda 中使用当前类的成员函数和成员变量。

eg:
socket_.async_read_some(boost::asio::buffer(buffer_),
   			[this, self](boost::system::error_code ec, std::size_t bytes_transferred)
   {
   	//body							
   });
  • params 参数列表,类似函数的形参

参数可以通过按值(如: (a, b))和按引用 (如: (&a, &b)) 两种方式进行传递。

  • opt 函数选项,比如mutable,可以省略

mutable 或 exception 声明,按值传递函数对象参数时,加上 mutable 修饰符后,可以修改传递进来的拷贝(注意是能修改拷贝,而不是值本身)。exception 声明用于指定函数抛出的异常,如抛出整数类型的异常,可以使用 throw(int)。

  • -> ret 返回值类型,如果没有返回值,此处可以省略。
  • body 函数体。
Lambda表达式优点
  • 声明式编程风格:匿名定义目标函数或函数对象,不需要额外写一个命名函数或者函数对象。以更直接的方式去写程序,好的可读性和可维护性。
  • 简洁:不需要额外再写一个函数或者函数对象,避免了代码膨胀和功能分散,让开发者更加集中精力在手边的问题,同时也获取了更高的生产率。
  • 在需要的时间和地点实现功能闭包,使程序更灵活。
使用方式
  1. 未使用捕获器,无法使用闭包之外的变量。如下示例,变量a在闭包外面定义,而x1未捕获任何变量, 因此使用是错误的。
    在这里插入图片描述
  2. 捕获外部所有变量【值传递】,使用 = 捕获列表
	void func(int x, int y)
	{
		auto x1 = [=]()->int {return a; };
	}
值传递不能修改本体的值,是以只读模式传入。如下使用会报错误。

在这里插入图片描述

  1. 捕获所有外部变量【引用传值】,使用 & 捕获列表
class LambdaA
{
public:
   int a = 0;

   void func(int x, int y)
   {
   	auto x1 = [&]() {return ++a; };
   }
}

引用传值可以修改本体的值,与C++引用传递相似。

  1. 捕获整个类对象,使用 this 捕获列表
class LambdaA
{
public:
   int a = 0;

   void func(int x, int y)
   {
   	auto x1 = [this]() { return ++this->a; };
   }
}

this只捕获当前类对象中的所有变量,其他变量仍然无法使用,比如上例中的形参 xy。this捕获的对象内部也是可以直接修改的。
在这里插入图片描述

  1. 如果需要获取 this 之外的变量,需要逐个捕获。

   int a = 0;
   void func(int x, int y)
   {
   	auto x1 = [this, x, y]() { return this->a + x + y; };
   }

那我单独获取函数形参后,能否直接修改其值呢?如下图错误提示是什么原因呢?
在这里插入图片描述
是因为捕获的值,默认是只读的,即值传递,如果需要修改本体,需要改为引用传值:

	int a = 0;

	void func(int x, int y)
	{
		auto x1 = [this, x, &y]()
		{ 
			++y;
			return this->a + x + y;
		};
	}
  1. 特别注意的是,Lambda是延后调用的,因此在一些需要涉及到拷贝和引用传值的位置,需要特别注意。如下代码段:
int main()
{
	int intA = 0; 

	auto x1 = [=]() { return intA;};

	++intA;

	std::cout << x1() << std::endl;
	return 0;
}

运行结果:
在这里插入图片描述

那么为什么不是1呢?因为后面intA执行了+1操作。分析这个问题其实很简单,因为 x1 中调用的intA是通过 拷贝 的方式传值的,在intA执行+1之前,副本已经被拷贝了,因此后续的任何操作都不会影响x1的结果。

改为引用传值,即可实现同步传值。如下代码段所示:

int main()
{
	int intA = 0; 

	auto x1 = [&]() { return intA;};

	++intA;

	std::cout << x1() << std::endl;
	return 0;
}

运行结果:
在这里插入图片描述
但是 ,通过值捕获的变量, 也可以当做左值参与运算,前提是通过使用关键字 mutable 修饰,将常量转换成可以参与计算的左值。如下代码段。

int main()
{
	int intA = 0; 

	auto x1 = [=]() mutable { return ++intA;}; 

	std::cout << "x1的值为:" << x1() << std::endl;
	std::cout << "intA的值为:" << intA << std::endl;
	return 0;
}

运行结果:
在这里插入图片描述
可以看出,用 mutable 修饰的变量,可以当做左值参与运算,但是并没有修改本体,只是将副本参与运算。

另外需要注意,被 mutable修改的Lambda表达式,不管有没有参数,都必须加上参数列表 (),否则会报错,如图所示:
在这里插入图片描述

Lambda表达式的类型

lambda 表达式的类型在 C++11 中被称为“闭包类型(Closure Type)”。它是一个特殊的,匿名的非 nunion 的类类型。可以认为它是一个带有 operator() 的类,即仿函数。因此,我们可以使用 std::function 和 std::bind 来存储和操作 lambda 表达式:

	std::function<int(int)> f1 = [=](int a) {return a + intA; };

	std::function<int(void)> f2 = std::bind(f1, 123);

	std::function<int(void)> f3 = std::bind([](int a) {return a;; }, 123);

lambda 表达式可以说是就地定义仿函数闭包的“语法糖”。它的捕获列表捕获住的任何外部变量,最终均会变为闭包类型的成员变量。而一个使用了成员变量的类的 operator(),如果能直接被转换为普通的函数指针,那么 lambda 表达式本身的 this 指针就丢失掉了。而没有捕获任何外部变量的 lambda 表达式则不存在这个问题。

这里也可以很自然地解释为何按值捕获无法修改捕获的外部变量。因为按照 C++ 标准,lambda 表达式的 operator() 默认是 const 的。一个 const 成员函数是无法修改成员变量的值的。而 mutable 的作用,就在于取消 operator() 的 const。

需要注意的是,没有捕获变量的 lambda 表达式可以直接转换为函数指针,而捕获变量的 lambda 表达式则不能转换为函数指针。看看下面的代码:

typedef void(*Ptr)(int*);
Ptr p = [](int* p){delete p;};  // 正确,没有状态的lambda(没有捕获)的lambda表达式可以直接转换为函数指针
Ptr p1 = [&](int* p){delete p;};  // 错误,有状态的lambda不能直接转换为函数指针
Lambda表达式实例

在STL中,遍历或者需要提供仿函数的位置,可以直接用Lambda代替,非常简洁方便,如下示例代码段:

int main()
{
	vector<int> v;

	v.push_back(2);
	v.push_back(1);
	v.push_back(3);
	v.push_back(5);
	v.push_back(4);

	// 排序
	sort(v.begin(), v.end(), [](int a, int b) {return a > b; });
	// 打印
	for_each(v.begin(), v.end(), [](int a) {std::cout << a<<" "; });
	
	return 0;
}

运行结果
在这里插入图片描述

以上排序和打印都用到了Lambda表达式,在未接触它之前,都是使用的仿函数支持排序和打印的,很繁琐。可以通过如下代码段体验一下仿函数的繁琐程度。

#include <iostream>
#include <functional>
#include<vector>
#include <algorithm>

// 提供Sort函数的仿函数,实现自定义排序
class MySort
{
public:
	bool operator()(int a,int b)
	{
		return a > b;
	}
};

// 提供for_each的打印函数支持
class MyForEach
{
public:
	void operator()(int a)
	{
		std::cout << a << " ";
	}
};

int main()
{	
	vector<int> v;

	v.push_back(2);
	v.push_back(1);
	v.push_back(3);
	v.push_back(5);
	v.push_back(4);

	sort(v.begin(), v.end(), MySort());

	for_each(v.begin(), v.end(), MyForEach());
	
	return 0;
}

运行结果
在这里插入图片描述
从以上连个版本可以看出,Lambda表达式确实比较方便,不用重新定义类、重载运算符。另外在C++11很多地方都用到了Lamdba表达式,如果要看懂C++11的代码,必须对Lambda 的规则等做一个比较深入的了解。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值