C++11 lambda函数和包装器

2023-12-28 10:49:38

目录

前言

一.lambda的引入

二、lambda函数的使用

1.一般使用

2.引用

三、包装器

1.包装普通对象

2.包装类成员对象

3.bind


前言

????????学习过python的同学应该对lambda函数不陌生,这是一个匿名函数,不需要写函数的名字。在不会多地方调用某个简单函数的地方,就可以使用lambda。

一.lambda的引入

? ? ? ? 在学习lambda函数之前,我们来看一个用例。这是一些商品,我们需要对商品进行排序。

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

由于商品是自定义类型,比较函数要我们自己写,在Goods类里面写operator>很不方面。

  1. 无法使用sort函数,需要自己写排序,
  2. 排序的方式有很多,比如价格升序和降序,评价的升序和降序。

?因此一般情况下我们都会写仿函数来帮助我们进行比较。例如下面两个仿函数

struct CompareEvaluateLess
{
	bool operator()(const Goods& gl, const Goods& gr)
	{
		return gl._evaluate < gr._evaluate;
	}
};
struct ComparePriceGreater
{
	bool operator()(const Goods& gl, const Goods& gr)
	{
		return gl._price > gr._price;
	}
};

给sort函数传递仿函数就可以按照我们的想法进行排序了。?

????????随着C++语法的发展,人们开始觉得上面的写法太复杂了,每次为了实现一个algorithm算法, 都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名, 这些都给编程者带来了极大的不便。因此,在C++11语法中出现了Lambda表达式。

二、lambda函数的使用

1.一般使用

lambda使用方法如下,有点长,先不用看,直接看后面的例子

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

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

举个例子,如下,[]为捕捉列表(先不捕捉,后续会讲),(int x)为参数,->int返回类型为int,{}里面存放函数内容

[](int x)->int {cout << x << endl; return 0; };

如下两个方法可以进行lambda函数的调用,

第一个是直接在后面给参数调用。

第二个是赋值给auto 变量,再使用该变量名进行调用。

对于之前商品排序,现在我们也会修改了,一下子就搞定了。

2.引用

方法一:参数传引用,这是我们熟悉的方法,由于不需要返回参数,因此省略->()。如下

?方法二:捕获列表

使用正常捕获,是const的无法修改,需要加mutable变为可修改(见二、1使用方法第三条)。使用引用捕获,不加mutable也可以修改(引用的目的大多是为了修改,这里编译器做了特殊处理)。如下引用捕获了x和y,同时也不需要传参了,因为我们使用了捕获列表,参数列表没有参数。

引用捕获列表还可以使用&,代表引用捕捉当前作用域中的所有变量,如下(这里只有x,y)。

三、包装器

1.包装普通对象

function包装器也叫作适配器。他的主要目的是为了包装可调用对象,主要的可调用对象有函数指针,仿函数,lambda。

为什么要包装可调用对象呢?我们来看看他们的弊端。

函数指针用起来太不方便了,写起来很难受。

仿函数需要在全局定义,不够简洁和美观。

lambda类型是匿名的,一般取不到类型。

如果现在我想像cmd命令一样,输入一个指令,计算机进行相应的操作,这个操作就可以是相关的函数,那么我可以进行如下定义。使用map的operator[]进行相关操作,红色方框的类型应该填什么呢?写函数指针和仿函数很不方便,写lambda类型都没有没办法写。这时就需要包装器上场了。

比如我要进行数字的加减乘除操作,我们可以这样传递第二个参数function<double(double a,double b)>,这样代表包装的可调用对象的类型。那么我们实际传参时,只要类型相同,既可以传递函数指针,又可以传递仿函数,还可以传递lambda匿名对象,这样非常方便。(注意添加头文件#include <functional>)如下所示

2.包装类成员对象

代码如下

class Add
{
public:
	static int addi(int a, int b)
	{
		return a + b;
	}
	double addb(double a, double b)
	{
		return a + b;
	}
};

int main()
{
	function<int(int, int)> f1 = &Add::addi;
	cout << f1(5, 3) << endl;
}

?包装类里面的静态函数,指定类域即可直接包装,&最好填,也可以不填。

如果是非静态呢? 由于类的非静态变量有this指针,因此这里编译不通过。

?我们可以给第一个参数传递类指针,再定义一个类对象,取地址传过去就可以了。

还有一种写法,算是编译器的特殊处理,可以不用再生成类对象。

但是始终这个方法不太好,本来我就只想传两个参数,你一定要让我传第一个一直固定的,不是多此一举嘛,这时bind函数就出场了。

3.bind

bind也是在头文件<functional>里面的,他的作用是绑定函数,让函数参数变成我们想要的个数和顺序

如下,绑定了一个减法lambda函数,第一个参数使用placeholders作用域_2(代表前面那个函数的第二个参数),第二个参数为该作用域下的_1(代表前面那个函数的第一个参数)。相当于10给到了y,3给到了x,因此输出结果是-7。

如果我们给lambda函数第一个参数x为固定值,如下给20,那么我们需要将前面function的参数个数减少一个,同时调用的时候也只需要传一个参数,也就是placeholders::_1(因为只有一个参数)。?

当然第二个参数也可以给固定值。

学会了基本用法,我们回过头来修改类的成员函数。将function只设置两个参数,同时将Add::addb函数的第一个参数固定死传Add(),后续再传placeholders::_1, placeholders::_2,代表实参的第一个和第二个就好。

这样就可以按照我们的想法进行传参了。

谢谢大家观看?

文章来源:https://blog.csdn.net/kkbca/article/details/132766313
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。