C++进阶篇6---lambda表达式

目录

一、lambda表达式

1.引入 

2、lambda表达式语法

二、包装器---function

1.引入

2.包装器介绍

三、bind


一、lambda表达式

1.引入 

class Person {
public:
	Person(int age,string name)
		:_age(age)
		,_name(name)
	{}
//private://方便后面的举例
	int _age;
	string _name;
};

int main()
{
	vectorv = { {10,"zhangsan"},{40,"wangwu"},{20,"lisi"} };
	//当我们要通过姓名/年龄排序时,我们应该怎么办?
	//sort(v.begin(),v.end());
	return 0;
}

根据我们之前学过的知识,我们可以写两个仿函数,分别对应姓名和年龄的比较,如下

struct comp_age {
	bool operator()(const Person& x, const Person& y) {
		return x._age < y._age;
	}
};

struct comp_name {
	bool operator()(const Person& x, const Person& y) {
		return x._name < y._name;
	}
};

但这种方法太麻烦了,而且一旦排序的标准多起来,给仿函数起什么名字都是个问题,所以出了lambada表达式,如下

int main()
{
	vectorv = { {10,"zhangsan"},{40,"wangwu"},{20,"lisi"} };
	sort(v.begin(), v.end(), [](const Person& x, const Person& y) {x._age < y._age; });
	sort(v.begin(), v.end(), [](const Person& x, const Person& y) {x._name < y._name; });
	return 0;
}

可以看出lambda表达式实际上一个匿名函数,跟函数很相似

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表达式

捕获列表说明

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

  • [var]:表示值传递方式捕捉变量var
  • [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
  • [&var]:表示引用传递捕捉变量var
  • [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
  • [this]:表示值传递方式捕捉当前的this指针
int main()
{
    // 最简单的lambda表达式, 该lambda表达式没有任何意义
    []{}; 
    
    // 省略参数列表和返回值类型,返回值类型由编译器推导为int
    int a = 3, b = 4;
    [=]{return a + 3; }; 
    
    // 省略了返回值类型,无返回值类型
    auto fun1 = [&](int c){b = a + c; }; 
    fun1(10)
    cout<int{return b += a+ c; }; 
    cout<

注意:

a. 父作用域指包含lambda函数的语句块

b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割

比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量

[&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量

c. 捕捉列表不允许变量重复传递,否则就会导致编译错误

比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复

d. 在块作用域以外的lambda函数捕捉列表必须为空

e. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。

f. lambda表达式之间不能相互赋值,即使看起来类型相同

void (*PF)();
int main()
{
	auto f1 = [] {cout << "hello world" << endl; };
	auto f2 = [] {cout << "hello world" << endl; };
    f1();
    f2();
	// 此处先不解释原因,等lambda表达式底层实现原理看完后,大家就清楚了
    //f1 = f2;   // 编译失败--->提示找不到operator=()
	// 允许使用一个lambda表达式拷贝构造一个新的副本
	auto f3(f2);
	f3();
	// 可以将lambda表达式赋值给相同类型的函数指针
	PF = f2;
	PF();
	return 0;
}

 规则其实并不复杂,就是细节比较多,主要是没咋见识过,用多了就会发现它真的很香

这是上面代码的反汇编调试信息,其实lambda表达式的底层实现就是仿函数,只是类型名很奇怪,但都是调用的operator()这个成员函数

 注意:一般lambda表达式的类型名都是class lambda_XXXXX这种样式的,采用的是lambda_uuid的编码方式,不同编译器的命名方式不同,但是我们也能看出它是一个类的函数调用

对比仿函数

C++进阶篇6---lambda表达式_第1张图片

我们就能进一步理解捕捉列表,它其实和仿函数的成员变量很相似,一个是自己初始化,一个是捕捉已经存在的

二、包装器---function

1.引入

截止到目前,我们已经学了很3种"函数"---普通函数、仿函数、lambda表达式,它们的类型各不相同,一旦有模板需要传函数,就会导致一个问题,传不同类型的"函数",会产生多个实例,如下
template
T useF(F f, T x)
{
	static int count = 0;
	cout << "count:" << ++count << endl;
	cout << "count:" << &count << endl;
	return f(x);
}
double f(double i)
{
	return i / 2;
}
struct Functor
{
	double operator()(double d)
	{
		return d / 3;
	}
};
int main()
{
	// 函数名
	cout << useF(f, 11.11) << endl;
	// 函数对象
	cout << useF(Functor(), 11.11) << endl;
	// lamber表达式
	cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;
	return 0;
}

C++进阶篇6---lambda表达式_第2张图片

而这一切都是因为类型不同,所以我们需要将它们的类型进行统一包装,避免这种情况发生,所以出了function包装器

2.包装器介绍

std::function 在头文件 < functional >
// 类模板 原型如下
template < class T > function ;     // undefined
template < class Ret , class ... Args >
class function < Ret ( Args ...) > ;
模板参数说明:
  • Ret: 被调用函数的返回类型
  • Args…:被调用函数的形参

function类型的对象能用函数指针,lambda表达式,仿函数赋值,前提是参数及返回值和function类型一样,如下

int main()
{
	// 函数名
	function func1 = f;
	cout << useF(func1, 11.11) << endl;
	cout << typeid(func1).name() << endl;
	 //函数对象
	function func2 = Functor();
	cout << useF(func2, 11.11) << endl;
	cout << typeid(func2).name() << endl;

	 //lamber表达式
	function func3 = [](double d)->double { return d / 4; };
	cout << useF(func3, 11.11) << endl;
	cout << typeid(func3).name() << endl;

	return 0;
}

C++进阶篇6---lambda表达式_第3张图片

可以看出模板只实例化了一份,有点类似多态,传不同的函数,会有不同的效果,而这都是因为function包装器的类型是统一的

有人可能觉得这个用处好像不是很大,但其实在大型项目中,还是很有必要的,而且它的应用场景远不止于此,我们来看看下面的应用场景

C++进阶篇6---lambda表达式_第4张图片

正常来说,我们得写if-else语句或者switch语句一个符号一个符号的匹配 

C++进阶篇6---lambda表达式_第5张图片

但是现在我们可以用包装器function来简化代码,使得它看起来更加优雅

C++进阶篇6---lambda表达式_第6张图片

3.类成员函数的包装

class Plus {
public:
	int ADDi(int x, int y)
	{
		return x, y;
	}
	static double ADDd(double x, double y)
	{
		return x + y;
	}
private:
	int a;
};

int main()
{
//1.注意类的成员函数的地址怎么取,&域名::函数名
//2.非静态成员函数,第一个参数可以是类,也可以是指针
	functionfunc1 = &Plus::ADDi;//可以是Plus,也可以是Plus*
	functionfunc2 = &Plus::ADDi;
	functionfunc3 = &Plus::ADDd;
	return 0;
}

 

三、bind

std::bind函数定义在头文件中,是一个 函数模板 ,它就像一个函数包装器(适配器),接受一个可 调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。一般而 言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M 可以大于N,但这么做没什么意义)参数的新函数。同时,使用std::bind函数还可以实现参数顺 序调整等操作。
// 原型如下:
template 
/* unspecified */ bind (Fn&& fn, Args&&... args);
// with return type (2) 
template 
/* unspecified */ bind (Fn&& fn, Args&&... args);

看着概念很复杂,我们来写几个看看,就大致明白了

int test(int x, int y)
{
	return x * 2 + y * 3;
}

int main()
{
    //placeholders::_xx代表的是funcion中的参数,_1代表第一个,_2代表第二个以此类推
	functionfun1 = bind(test, 2, placeholders::_1);
	cout << fun1(1) << endl;
	functionfun2 = bind(test, placeholders::_1, 2);
	cout << fun2(1) << endl;

	functionfun3 = bind(test, placeholders::_2, placeholders::_1);
	cout << fun3(2, 3) << endl;

	return 0;
}

 

这里解释一下,这三个打印结果,传参的对应关系如下

C++进阶篇6---lambda表达式_第7张图片

很显然,placeholders::_1,_2,…… 对应的是function中写的参数顺序,bind中第一个参数填函数名(当然仿函数对象,lambda表达式都可以),后面的参数分别对应函数参数的顺序。

function包装器的参数可以少于函数的参数,但是bind中传的参数一般要和原函数参数个数对应,我们可以通过bind来固定一些默认的参数值,或者调换一下参数的顺序,让我们用起函数来更加的"舒服"

成员函数的bind

class Plus {
public:
	int ADDi(int x, int y)
	{
		return x, y;
	}
	static double ADDd(double x, double y)
	{
		return x + y;
	}
private:
	int a = 0;
};

int main()
{
    //如果只是单纯想想使用该函数的功能,可以将第一个参数写死,虽然第一个参数是指针,这里也可以传对象,可以看作是特例
	functionfunc1 = bind(&Plus::ADDi, Plus(), placeholders::_1, placeholders::_2);
	cout << func1(1, 2) << endl;
	//这种是不行的,右值不能被取地址!!!
	//functionfunc2 = bind(&Plus::ADDi, &Plus(), placeholders::_1, placeholders::_2);
	//cout << func2(1, 2) << endl;

	functionfunc3 = bind(&Plus::ADDd, placeholders::_1, placeholders::_2);
	cout << func3(1.1, 2.3) << endl;
	return 0;
}

你可能感兴趣的:(c++,java,前端)