C++:C++11新特性--lambda表达式和包装器

文章目录

  • lambda表达式
    • lambda表达式的使用规则
    • lambda表达式的用法
    • lambda表达式的理解
    • 函数对象和lambda表达式
  • 包装器
  • bind

lambda表达式

首先介绍什么是lambda表达式,在介绍这个情景前,可以回忆一下算法库中的sort排序:

// lambda表达式
void lambda1()
{
	int arr[] = { 1,3,6,5,4,2,8,9,10 };
	for (auto e : arr)
		cout << e << " ";
	cout << endl;
	sort(arr, arr + sizeof(arr) / sizeof(arr[0]));
	for (auto e : arr)
		cout << e << " ";
	cout << endl;
}

这是可以实现排序的结果的,但排序的默认是使用升序排序,这是因为在算法库中最后一个参数给了缺省参数

C++:C++11新特性--lambda表达式和包装器_第1张图片
因此,如果想要实现降序排序,可以自己定义一种排序的方式

// lambda表达式
struct Compare
{
	bool operator()(int a, int b)
	{
		return a > b;
	}
};

void lambda1()
{
	int arr[] = { 1,3,6,5,4,2,8,9,10 };
	for (auto e : arr)
		cout << e << " ";
	cout << endl;
	sort(arr, arr + sizeof(arr) / sizeof(arr[0]), Compare());
	for (auto e : arr)
		cout << e << " ";
	cout << endl;
}

此时就可以实现一个降序排序

这样的实现是很有意义的,当遇到不能进行默认比较的时候,例如在比较pair类型参数等,就不可以直接进行比较,需要手动的定义比较的方式,这都是可以理解的

现在的问题是,这样写有一个不方便的地方,就是传入的Compare参数并不知道是按照什么规则进行排序的,是升序还是降序?这是不确定的

因此C++11就引入了lambda表达式来弥补这方面的措施,lambda表达式最早出现于Python语言,因此从某种意义来说可以把它当成是一种全新的语言来学习它,那么下面就介绍它的使用规则

lambda表达式的使用规则

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

对于上面表达式中的各部分写一个说明:

  1. [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。

  2. (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略

  3. mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。

  4. {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。

注意点:

lambda函数定义的过程中,参数列表和返回值的类型都是可以选择的,而捕捉列表和函数体也可以为空,那么空语句可以定义为[]{}表示这个lambda函数不做任何事

lambda表达式的用法

void lambda2()
{
	int arr[] = { 1,3,6,5,4,2,8,9,10 };
	for (auto e : arr)
		cout << e << " ";
	cout << endl;
	// lambda表达式的完全版写法
	sort(arr, arr + sizeof(arr) / sizeof(arr[0]), [](int x, int y) -> bool {return x < y; });
	// 缺省方式的写法
	sort(arr, arr + sizeof(arr) / sizeof(arr[0]), [](int x, int y) {return x < y; });
	for (auto e : arr)
		cout << e << " ";
	cout << endl;
}

上面演示的就是lambda表达式的一个例子,可以看到的是,整个函数的组成是由捕获列表,参数列表,mutable选项(这里没写),返回值类型,函数体所组成的,而事实上这也确实就是lambda表达式的组成,从这个表达式中看就能很明白的看出lambda表达式的功能是什么了

lambda表达式还可以用类似于函数的方式来完成:

void lambda3()
{
	auto fun1 = [](int x, int y)
	{
		cout << x + y << endl;
	};
	auto fun2 = [](int x, int y)
	{
		return x + y;
	};

	fun1(10, 20);
	cout << fun2(1, 2) << endl;
}

上面的表达式可以看出,fun1fun2接收了这个表达式,接着就可以用函数调用的方式来对lambda表达式进行执行,而事实上也确实成功的执行了,是有运行结果的

捕捉列表

捕捉列表是lambda表达式的一个重要组成部分,它表示了lambda表达式中可以使用哪些数据,以及使用的是传值还是传引用

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

lambda表达式的理解

lambda表达式从上面来看是有返回值的,那么返回值的类型是什么呢?

运行结果如下所示

void lambda4()
{
	auto fun1 = [](int x, int y)
	{
		cout << x + y << endl;
		return 0;
	};
	fun1(10, 20);
	cout << typeid(fun1).name() << endl;
}
30
class `void __cdecl lambda4(void)'::`2'::<lambda_1>

从中看出,lambda表达式的类型很奇怪,并不是想象中的是一种固定的模式,而是一种类似于随机值的机制,由此说明,lambda表达式之间是不可以进行相互赋值的,因为它们在底层是完完全全不一样的,不支持operator=

函数对象和lambda表达式

函数对象是什么?lambda表达式是什么?

函数对象又被叫做仿函数,如同它字面意思一样,可以像函数一样使用对象,简单来说就是前面在sort中写的Compare对象,在它里面重载了一个operator(),这样近似的可以理解成是把一个对象当成一个函数来使用

来举一个例子:

class Rate
{
public:
	Rate(double rate) : _rate(rate)
	{}

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

private:
	double _rate;
};

void lambda6()
{
	double rate = 1;
	Rate r1(rate);
	r1(1, 2);
	auto r2 = [=](double monty, int year)->double {return monty * rate * year;
	};
	r2(1, 2);
}

从使用的角度来看,函数对象和lambda表达式是一样的,都是用括号进行调用,函数对象将rate作为它的成员变量,在定义对象的时候给出初始值,而lambda表达式又可以通过捕获列表将该变量直接捕获到

而从它们的底层逻辑考虑,在底层看来表达式的处理方式是一样的,都是按照函数对象来处理的,也就是说,如果定义了一个lambda表达式,在系统的底层会自动生成一个类,在这个类中会重载一个operator()

包装器

什么是包装器?

function包装器,也叫做适配器,是C++中的一个类模板

为什么需要包装器?

回忆一下在学习C和C++的过程中,对于可调用对象的概念来说,可以如何进行调用?可以调用什么呢?

  1. 函数指针
  2. 仿函数
  3. lambda表达式

借助这三个内容,都可以把对象近似当成一个函数来调用,但是这是有弊端的,例如对于函数指针来说,它的使用非常的复杂,不方便使用,对于阅读者来说也很难进行阅读,对于仿函数来说它自身的包装很重,需要构造一个结构体,在里面实现一个函数的重载,而对于lambda表达式来说就更有弊端了,它本身是一个匿名的内容,想要实现调用也并不容易,因此这三种调用的方式都有一定的弊端,都不太容易进行调用

那么对此可以如何进行针对性的解决呢?C++11就引入了一个function包装器,简单来说就是把这些内容进行了一定程度的包装,在调用的时候直接调用

包装器的使用方法

// 包装器
// 定义几种方式用来实现两个数的相加过程
int func1(int x, int y)
{
	return x + y;
}

struct func2
{
	int operator()(int x, int y)
	{
		return x + y;
	}
};

class func3
{
public:
	static int add1(int x, int y)
	{
		return x + y;
	}

	double add2(double x, double y)
	{
		return x + y;
	}
};

void function1()
{
	// 把函数指针包装起来
	function<int(int, int)> fun1 = func1;
	cout << "函数指针" << fun1(10, 20) << endl;

	// 把仿函数包装起来
	function<int(int, int)> fun2 = func2();
	cout << "仿函数" << fun2(20, 30) << endl;

	// 把lambda表达式包装起来
	function<int(int, int)> fun3 = [](int x, int y) {return x + y; };
	cout << "lambda表达式" << fun3(40, 50) << endl;

	// 把类的成员函数包装起来
	function<int(int, int)> fun4 = &func3::add1;
	cout << "静态成员函数" << fun4(50, 60) << endl;
	function<double(func3, double, double)> fun5 = &func3::add2;
	cout << "非静态成员函数" << fun5(func3(), 1.1, 2.2) << endl;
}

上面演示的就是包装器的使用方法,它的好处之一就是,可以把多种调用的方式变成一种来调用,除了非静态成员函数,这个后面和绑定器结合在一起进行讲解

包装器的底层逻辑可以使得模板实例化的成本降低,在实际的开发中还是有意义的

bind

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

bind的使用

关于bind的使用,可以大致有绑定成员函数,参数调换顺序两种使用场景

所以上面的非静态成员变量实际上也是可以进行改造的,可以改造成这样

绑定成员函数

	function<double(double, double)> fun5 = bind(&func3::add2, func3(), placeholders::_1, placeholders::_2);
	cout << "非静态成员函数" << fun5(1.1, 2.2) << endl;

这个函数相当于是把fun5死绑在func3的对象中了

参数调换顺序

// bind
int sub(int x, int y)
{
	return x - y;
}
void testbind()
{
	function<int(int, int)> fun1 = sub;
	cout << fun1(10, 5) << endl;

	function<int(int, int)> fun2 = bind(sub, placeholders::_2, placeholders::_1);
	cout << fun2(10, 5) << endl;
}

你可能感兴趣的:(C++,知识总结,c++,开发语言)