【C++11】lambda表达式 包装器

这篇具有很好参考价值的文章主要介绍了【C++11】lambda表达式 包装器。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


1 lambda表达式

1.1 引例

在C++98中,如果想要对一个数据集合中的元素进行排序,可以使用std::sort方法:

#include <algorithm>
#include <functional>
int main()
{
	int array[] = { 4,1,8,5,3,7,0,9,2,6 };
	// 默认按照小于比较,排出来结果是升序
	std::sort(array, array + sizeof(array) / sizeof(array[0]));
	// 如果需要降序,需要改变元素的比较规则
	std::sort(array, array + sizeof(array) / sizeof(array[0]), greater<int>());
	return 0;
}

如果待排序元素为自定义类型,需要用户定义排序时的比较规则:

struct Goods
{
	string _name;  // 名字
	double _price; // 价格
	int _evaluate; // 评价
	Goods(const char* str, double price, int evaluate)
		:_name(str)
		, _price(price)
		, _evaluate(evaluate)
	{}
};
struct ComparePriceLess
{
	bool operator()(const Goods& gl, const Goods& gr)
	{
		return gl._price < gr._price;
	}
};
struct ComparePriceGreater
{
	bool operator()(const Goods& gl, const Goods& gr)
	{
		return gl._price > gr._price;
	}
};
int main()
{
	vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,
   3 }, { "菠萝", 1.5, 4 } };
	sort(v.begin(), v.end(), ComparePriceLess());
	sort(v.begin(), v.end(), ComparePriceGreater());
	return 0;
}

如果仿函数命名比较规范的话,像上面的命名方式的话那还好,如果遇到了像cmp1 cmp2 cmp3…这种命名方式而且还没有注释的话可以让人烦死,自己还得去找对应的源码实现,而如果在一个工程中有很多代码,找的代价也会比较大,所以C++11便新推出了一个语法就是lambda表达式。

1.2 lambda表达式的基本语法

lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement }

lambda表达式各部分说明:

  • [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
  • (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略。
  • mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
  • ->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
  • {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。

注意:

在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体不能省略并且可以为空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。

捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。

  • [var]:表示值传递方式捕捉变量var
  • [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
  • [&var]:表示引用传递捕捉变量var
  • [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
  • [this]:表示值传递方式捕捉当前的this指针

我们可以来实现一个简单的add来验证一下:

int main()
{
	int x, y;
	cin >> x >> y;
	auto add = [=]()
	{
		return x + y;
	};
	cout << add() << endl;
	return 0;
}

lambda表达式实际上可以理解为无名函数,该函数无法直接调用,如果想要直接调用,可借助auto将其赋值给一个变量。像上面的add你甚至还可以这样写:cout<< [=](){return x + y;}()<< endl;

我们可以来看看mutable的应用场景,比如下面的代码:

int main()
{
	int x = 10,y = 20;
	auto swapInt = [=] {int tmp = x; x = y; y = tmp; };
	swapInt();
	return 0;
}

当我们编译时会直接报错的:
【C++11】lambda表达式 包装器,C++进阶,c++,开发语言,lambda
为什么呢?因为我们是用值捕捉的方式捕捉到的变量,而捕捉到的变量是一份拷贝,并且默认是不让你你修改的(可以理解为增加了const属性),所以当你修改变量是会直接报错的,那假如我们想让其修改呢?我们就可以用mutable(意思是易变的):
【C++11】lambda表达式 包装器,C++进阶,c++,开发语言,lambda

注意:

  1. 父作用域指包含lambda函数的语句块。
  2. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量[&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量。
  3. 捕捉列表不允许变量重复传递,否则就会导致编译错误。比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复。
  4. 在块作用域以外的lambda函数捕捉列表必须为空。
  5. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。
  6. lambda表达式之间不能相互赋值,即使看起来类型相同。

前面的注意事项都很好理解,最后一个注意点我们可以验证下:

void (*PF)();
int main()
{
 auto f1 = []{cout << "hello world" << endl; };
 auto f2 = []{cout << "hello world" << endl; };

 //f1 = f2;   // 编译失败--->提示找不到operator=()
 // 允许使用一个lambda表达式拷贝构造一个新的副本
 auto f3(f2);
 f3();
 // 可以将lambda表达式赋值给相同类型的函数指针
 PF = f2;
 PF();
 return 0;
}

注意事项代码中都有注释。
至于为啥不允许赋值,我们后面讲解lambda表达式的原理时会给出解释。

1.3 lambda表达式的底层原理

函数对象,又称为仿函数,即可以像函数一样使用的对象,就是在类中重载了operator()运算符的类对象。

我们写一段代码来验证一下:

class Rate
{
public:

	Rate(double rate) : _rate(rate)
	{}
	double operator()(double money, int year)
	{
		return money * _rate * year;
	}

private:
	double _rate;
};

int main()
{
	//  函数对象
	double rate = 0.49;
	Rate r1(rate);
	r1(10000, 2);
	//  lamber
	auto r2 = [=](double monty, int year)->double { return monty * rate * year;};
	r2(10000, 2);
	return 0;
}

【C++11】lambda表达式 包装器,C++进阶,c++,开发语言,lambda从汇编的角度来看,我们不难发现lambda表达式在底层也是调用了operator来实现,那为什么lambda表达式不能够相互赋值呢?其本质是因为lambda表达式在底层的命名是采用uuid的方式生成唯一的类名,所以不同类型的对象自然不可以赋值了。

实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如
果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。

那考考大家:lambda对象的大小是多少字节呢❓
答案其实已经显而易见了,由于lambda表达式的底层是用仿函数实现的,而仿函数是一个没有内置成员变量的类(空类),大小就是1字节喽,你回答对了吗?


2 包装器

function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。

使用包装器前我们要引入头文件#include <functional>
类模板的原型如下:

// 类模板原型如下
template <class T> function;     // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;

那包装器我们日常是如何使用的呢?

// 使用方法如下:
#include <functional>
int f(int a, int b)
{
	return a + b;
}
struct Functor
{
public:
	int operator() (int a, int b)
	{
		return a + b;
	}
};

int main()
{
	// 函数名(函数指针)
	std::function<int(int, int)> func1 = f;
	cout << func1(1, 2) << endl;
	// 函数对象
	std::function<int(int, int)> func2 = Functor();
	cout << func2(1, 2) << endl;
	// lamber表达式
	std::function<int(int, int)> func3 = [](const int a, const int b)
	{return a + b; };
	cout << func3(1, 2) << endl;
	return 0;
}

我们可以用包装器来接受 函数指针 仿函数 lambda ,这样我们就可以用统一的类型来接受不同的参数,达到只实例化一份的目的。

但是在调用类中非静态成员函数(不包括仿函数)时要额外注意function的语法格式:
比如下面:

class Plus
{
public:
	static int plusi(int a, int b)
	{
		return a + b;
	}
	double plusd(double a, double b)
	{
		return a + b;
	}
};
int main()
{
	// 类的成员函数
	std::function<int(int, int)> func4 = &Plus::plusi;
	cout << func4(1, 2) << endl;
	std::function<double(Plus, double, double)> func5 = &Plus::plusd;
	cout << func5(Plus(), 1.1, 2.2) << endl;
	return 0;
}

我们知道静态成员函数是不包括this指针的,所以用之前的语法是没有问题的,但是由于成员函数有this指针,所以我们就要多给出一个额外的参数对象(我们一般喜欢给匿名对象来调用),通过参数对象来调用里面的成员函数。并且在指定类域是要加上&,这时语法的硬性规定。
【C++11】lambda表达式 包装器,C++进阶,c++,开发语言,lambda但是大家注意下面这种调用方式:
【C++11】lambda表达式 包装器,C++进阶,c++,开发语言,lambda我们也可以用对象指针来调用,但是这时候就不能够用匿名对象了,因为匿名对象是右值,是不能够&的,但是一般情况下我们不会选择这种方式。


3 bind

std::bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。

一般来说,我们使用bind有下面这两种情况:

  • 1️⃣调换参数顺序
  • 2️⃣改变参数个数

其中调换参数顺序其实一般很少用到,而改变参数个数很有意思,我们接下来一个一个来看:
比如下面这段程序:

void Print(int x, int y)
{
	cout << x << ":" << y << endl;
}

假设我们不改变Print函数的实现,而打印结果时交换参数顺序,我们可以怎么做?
我们可以用bind来处理:

int main()
{
	int x = 10, y = 20;
	Print(x, y);
	auto RPrint = bind(Print, placeholders::_2, placeholders::_1);
	RPrint(x, y);
	return 0;
}

这里面的_1 _2 是什么鬼呀?这其实是封装在placeholders命名空间中的一个占位符,正如我们直接理解的那样, _1 _2 ……分别代表着第一个参数,第二个参数……,我们想要交换哪些参数的位置可以直接通过交换占位符的顺序即可。

交换参数顺序的用法其实比较鸡肋,我们平时一般也不怎么用到,但是改变参数个数的场景我觉得还是比较有意思的,我们接下来看看这种情况:

void mul(double x, double y)
{
	cout<< x * y<<endl;
}

struct fun
{

	fun(double rate)
		:_rate(rate)
	{}

	void mulR(double x, double y)
	{
		cout << x * y * _rate << endl;
	}

	double _rate;
};


int main()
{
	int x = 10, y = 20;
	function<void(double, double)> f1 = mul;
	function<void(double, double)> f2 = [=](double x,double y) {cout<< x * y<<endl; };
	return 0;
}

当我们要求使用跟上面参数一样的格式来接受fun中的mulR时我们直接写是会直接报错的,在上面我们讲解function时已经详细解释了原理,这里就不在多说了。那我们可以通过bind来处理:

function<void(double, double)> f3 = bind(&fun::mulR,f, placeholders::_1, placeholders::_2);

我们可以通过上面的方式来绑定处理,将第一个参数绑定写死,然后我们就可以只用两个参数的包装器来接受了,是不是很妙。当然,我们不仅可以绑死第一个参数,第二个三个n个参数我们都可以通过bind来绑死,值得注意的小细节是不论我们绑死的是第几个参数,我们其他没有被绑定的参数只能从_1不断变大文章来源地址https://www.toymoban.com/news/detail-530557.html


到了这里,关于【C++11】lambda表达式 包装器的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 进阶JAVA篇- Lambda 表达式与 Lambda 表达式的省略规则

    目录         1.0 什么是 Lambda 表达式?         1.1 既然跟匿名内部类相关,先来回顾匿名内部类。          1.2 Lambda 表达式与匿名内部类之间的关系。         1.3 函数式接口         1.4 在具体代码中来操作一下         2.0 Lambda 表达式省略规则          Lambda 表达

    2024年02月08日
    浏览(52)
  • 【 C++11 】lambda表达式

    目录 1、lambda表达式的引入 2、lambda表达式         lambda表达式的语法         lambda表达式捕捉列表说明         使用lambda表达式排序自定义类型         lambda表达式的底层原理 1、lambda表达式的引入 在C++98中,如果想要对一个数据集合中的元素进行排序,可以

    2024年02月08日
    浏览(45)
  • C++ 11 Lambda表达式

    https://www.cnblogs.com/DswCnblog/p/5629165.html C++11的一大亮点就是引入了Lambda表达式。利用Lambda表达式,可以方便的定义和创建匿名函数。对于C++这门语言来说来说,“Lambda表达式”或“匿名函数”这些概念听起来好像很深奥,但很多高级语言在很早以前就已经提供了Lambda表达式的功

    2024年02月10日
    浏览(49)
  • C++11 lambda表达式

    lambda表达式是C++11或者更新版本的一个 语法糖 ,本身不是C++开发的。但是因其便利,很值得我们学习和使用。lambda有很多叫法,有lambda表达式、lambda函数、匿名函数,本文中为了方便表述统一用 lambda表达式 进行叙述。 在C++中,为了实现泛型编程,在一个类中,我们难免遇到

    2024年02月08日
    浏览(55)
  • C++11:lambda表达式

    lambda表达式实际上是一个匿名类的成员函数,该类由编译器为lambda创建,该函数被隐式地定义为内联。因此,调用lambda表达式相当于直接调用它的operator()函数,这个函数可以被编译器内联优化(建议)。 例如快速排序算法,STL允许用户自定义比较方式,在C++11之前,通常使用

    2024年02月14日
    浏览(29)
  • C++11_lambda表达式

    lambda表达式是C++11新引入的功能,它的用法与我们之前学过的C++语法有些不同。 [capture-list] (parameters) mutable - return-type { statement } [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来 判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的

    2024年02月02日
    浏览(49)
  • C++11的lambda表达式

    Lambda表达式是一种匿名函数,允许我们在不声明方法的情况下,直接定义函数。它是函数式编程的一种重要特性,常用于简化代码、优化程序结构和增强代码可读性。 lambda表达式的语法非常简单,具体定义如下: 举例: [ captures ] —— 捕获列表,它可以捕获当前函数作用域

    2024年02月03日
    浏览(42)
  • 【C++】C++11——lambda表达式

    我们之前都是通过函数指针、仿函数的方式可以像函数使用的对象,在C++11之后,就有了Lambda表达式 为了实现一个比较算法, 都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,看代码的人就遭殃了,非常的烦,这些都非常地不方便

    2024年01月17日
    浏览(52)
  • C++11新特性lambda 表达式

    Lambda 表达式的基本语法是:[] (参数列表) - 返回值类型 {函数体}。 方括号([])表示捕获列表,用来指定在 lambda 表达式中可以访问的外部变量。 参数列表和返回值类型与普通函数的参数列表和返回值类型相同。 函数体则是实际的代码逻辑。 不接受任何参数:[] { 函数体 } 接受

    2024年02月14日
    浏览(37)
  • 【C++】C++11语法 ~ lambda 表达式

    (꒪ꇴ꒪(꒪ꇴ꒪ )🐣,我是 Scort 目前状态:大三非科班啃C++中 🌍博客主页:张小姐的猫~江湖背景 快上车🚘,握好方向盘跟我有一起打天下嘞! 送给自己的一句鸡汤🤔: 🔥真正的大师永远怀着一颗学徒的心 作者水平很有限,如果发现错误,可在评论区指正,感谢🙏 🎉🎉

    2024年01月20日
    浏览(60)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包