C++11:lambda、包装器

1、lambda表达式

1.1 lambda表达式语法

lambda 表达式本质是一个匿名函数对象,跟普通函数不同的是他可以定义在函数内部。我们⼀般是用auto或者模板参数定义的对象去接收lambda 对象。

#include <iostream>
using namespace std;

int main()
{
    // [] 捕获列表,() 参数,-> 返回值(可省略),{} 函数体
    auto func = []() {
        cout << "这是lambda匿名函数" << endl;
    };

    // 调用lambda
    // 输出这是lambda匿名函数
    func();
    return 0;
}

lambda表达式的格式:[capture-list] (parameters)-> return type { function boby }

[capture-list]:捕捉列表,捕捉外面的变量供lambda函数使用,捕捉列表为空也不能省略

(parameters):lambda的形参列表,用来传参,与普通函数的参数列表功能类似,如果不需要参数传递,则可以连同()一起省略

->return type:返回值类型,指定返回值类型,一般返回值类型明确情况下可以省略,由编译器对返回类型进行推导

{function boby}:函数体,和普通函数大括号完全一样,写逻辑、循环、判断、return、cout 输出全都放这里。

eg:

int main()
{
	auto add1 = [](int x, int y)->int{return x + y;};
	cout << add1(1, 2) << endl;  // 输出3

	// 1、捕捉为空也不能省略
	// 2、参数为空可以连同()一起省略
	// 3、返回值可以省略,可以通过返回对象自动推导
	// 4、函数体不能省略
	auto func1 = []
	{
		cout << "hello" << endl;
		return 0;
	};

	func1();  // 输出hello

	int a = 0, b = 1;
	auto swap1 = [](int& x, int& y)
	{
		int tmp = x;
		x = y;
		y = tmp;
	};
	swap1(a, b);
	cout << a << ":" << b << endl;  // 输出1:0

	return 0;
}

1.2 lambda和sort排序

使用sort排序:

struct Goods
{
	string _name; // 名字
	double _price; // 价格
	int _evaluate; // 评价
	// ...

	Goods(const char* str, double price, int evaluate)
		:_name(str)
		, _price(price)
		, _evaluate(evaluate)
	{
	}
};

// 按价格升序
struct Compare1
{
	bool operator()(const Goods& gl, const Goods& gr)
	{
		return gl._price < gr._price;
	}
};

// 按价格降序
struct Compare2
{
	bool operator()(const Goods& gl, const Goods& gr)
	{
		return gl._price > gr._price;
	}
};

// 按评价分数降序
struct Compare3
{
	bool operator()(const Goods& gl, const Goods& gr)
	{
		return gl._evaluate > gr._evaluate;
	}
};

int main()
{
	vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3}, { "菠萝", 1.5, 4 } };
	sort(v.begin(), v.end(), Compare1());
	sort(v.begin(), v.end(), Compare2());
	sort(v.begin(), v.end(), Compare3());
}

使用sort后可读性较差,逻辑复杂,使用lambda表达式修改

#include<vector>
#include<algorithm>

struct Goods
{
	string _name; // 名字
	double _price; // 价格
	int _evaluate; // 评价
	// ...

	Goods(const char* str, double price, int evaluate)
		:_name(str)
		, _price(price)
		, _evaluate(evaluate)
	{
	}
};

int main()
{
	// 价格降序
	auto priceLess = [](const Goods& gl, const Goods& gr)
	{
		return gl._price < gr._price;
	};
	
	sort(v.begin(), v.end(), priceLess);
	
	sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr){
			return gl._price > gr._price;
		});
	
	sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr) {
		return gl._evaluate > gr._evaluate;
		});
	
	sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr) {
		return gl._evaluate < gr._evaluate;
		});
}

用lambda表达式后,不用单独写一个struct仿函数,不需要额外定义类

1.3 捕捉列表

lambda表达式中默认只能用lambda 函数体和参数中的变量,如果想用外层作用域中的变量就需要进行捕捉

第一种:在捕捉列表中显示的传值捕捉([var])和传引用捕捉([&var]),捕捉的多个变量用逗号分割

int x = 0;

int main()
{
	// 只能用当前lambda局部域和捕捉的对象和全局对象
	int a = 0, b = 1, c = 2, d = 3;

	auto func1 = [a, &b]() mutable
	{
		// 值捕捉的变量不能修改,引用捕捉的变量可以修改
		//a++;
		b++;
		x++;
		int ret = a + b;
		return ret;
	};

	cout << func1() << endl;  // 输出:3
}

第二种:在捕捉列表中隐式捕捉,[=]表示隐式值捕捉,[&]表示引用方式传递捕捉所有父作用域中的变量,这样我们lambda表达式中⽤了那些变量,编译器就会自动捕捉那些变量

int main()
{
	int a = 0, b = 1, c = 2, d = 3;
	// 隐式值捕捉
	// 用了哪些变量就捕捉哪些变量
	auto func2 = [=]
	{
		int ret = a + b + c;
		return ret;
	};
	cout << func2() << endl;  // 输出3

	// 隐式引用捕捉
	// 用了哪些变量就捕捉哪些变量
	auto func3 = [&]
	{
		a++;
		c++;
		d++;
	};
	func3();
	// 输出1 1 3 4
	cout << a << " " << b << " " << c << " " << d << endl;
}

第三种:在捕捉列表中混合使用隐式捕捉和显示捕捉,[=, &x]表示其他变量隐式值捕捉,x引用捕捉;[&, x, y]表示其他变量引用捕捉,x和y值捕捉

int main()
{
	// 只能用当前lambda局部域和捕捉的对象和全局对象
	int a = 0, b = 1, c = 2, d = 3;
	// 混合捕捉1
	auto func4 = [&, a, b]  //其他都是引用捕捉,a和b是值捕捉,&不能放到中间
	{
		//a++;
		//b++;
		c++;
		d++;
		return a + b + c + d;
	};
	func4();
	// 输出0 1 3 4
	cout << a << " " << b << " " << c << " " << d << endl;

	// 混合捕捉2
	auto func5 = [=, &a, &b]  //其他都是值捕捉,a和b是引用捕捉
	{
		a++;
		b++;
		/*c++;
		d++;*/
		return a + b + c + d;
	};
	func5();
	// 输出1 2 3 4
	cout << a << " " << b << " " << c << " " << d << endl;

	return 0;
}

注意

值传递方式捕捉的变量默认情况下是被const修饰的,也就是说传值捕捉的变量不能修改,如果想修改可以在参数列表的后面加mutable取消其常量性,例如:

auto func7 = [=]()mutable
{
	a++; // a可修改
	b++;
	c++;
	d++;
	return a + b + c + d;
};

cout << func7() << endl;
cout << a << " " << b << " " << c << " " << d << endl;

语法上捕捉列表可以有多个项组成,例如[&, a, b],其他都是引用捕捉,a和b是值捕捉,&只能放在列表的开头,[a, &, b]是错误的

捕捉列表不允许变量重复传递,否则会导致编译出错,例如[=, a],出现了两个值捕捉,导致重复

2、function

std::function 是一个类模板,是C++提供的函数包装器类模板,不管是普通函数、函数指针、仿函数、lambda、bind绑定出来的可调用对象,std::function 相当于一个统一的盒子,能把它们全部装进去,统一形成一种类型

template < class T>
class function; // undefined

template < class Ret, class... Args>
class function<Ret(Args...)>;

//eg:接收两个int参数,返回int的可调用对象盒子
function<int(int, int)>

函数指针、仿函数、lambda等可调用对象的类型各不相同,std::function 的优势就是统一类型,对他们都可以进行包装,这样在很多地方就方便声明可调用对象的类型

#include<functional>

// 计算两数相加
int f(int a, int b)
{
	return a + b;
}

// 仿函数结构体
struct Functor
{
public:
	int operator() (int a, int b)
	{
		return a + b;
	}
};

// 包含多种函数
class Plus
{
public:
	// 构造函数
	Plus(int n = 10)
		:_n(n)
	{}

	// 静态成员函数
	static int plusi(int a, int b)
	{
		return a + b;
	}

	// const普通成员函数
	double plusd(double a, double b) const
	{
		return (a + b) * _n;
	}
private:
	int _n;
};

int main()
{
	//函数指针
	// void(*pf)(int) = nullptr;
	// 包装各种可调用对象
	function<int(int, int)> f1 = f;  // 全局函数
	function<int(int, int)> f2 = Functor(); // 仿函数
	function<int(int, int)> f3 = [](int a, int b) {return a + b; };  // lamdba

	cout << f1(1, 1) << endl; // 2
	cout << f2(1, 1) << endl; // 2
	cout << f3(1, 1) << endl; // 2

  // 把function放进vector容器中
	vector<function<int(int, int)>> vf = { f , Functor(),[](int a, int b) {return a + b; } };
	for (auto& f : vf)
	{
		cout << f(1, 1) << endl;  // 输出2
	}

	// 包装静态成员函数
	// 成员函数要指定类域并且前面加&才能获取地址
	function<int(int, int)> f4 = &Plus::plusi;
	cout << f4(1, 1) << endl;

	// 普通成员函数隐藏第一个参数,即this参数,所以要加上Plus*
	function<double(Plus*, double, double)> f5 = &Plus::plusd;
	Plus ps;
	cout << f5(&ps, 1.1, 1.1) << endl; // 输出22

	// 第二种包装plusd,接收const对象引用
	// const Plus& 可以接收普通对象、临时对象,不用传指针
	//function<double(Plus, double, double)> f6 = &Plus::plusd;
	function<double(const Plus&, double, double)> f6 = &Plus::plusd;
	cout << f6(Plus(), 1.1, 1.1) << endl; // Plus()是临时对象,匹配const引用,输出22
	cout << f6(ps, 1.1, 1.1) << endl;   // 传入局部对象ps,输出22

	// 右值引用版本Plus&&:仅能接收临时对象、std::move转换后的右值对象
	function<double(Plus&&, double, double)> f7 = &Plus::plusd;
	cout << f7(Plus(), 1.1, 1.1) << endl;  // Plus()临时右值,匹配Plus&&,输出22
	cout << f7(move(ps), 1.1, 1.1) << endl;  // move把ps转为右值,匹配Plus&&,输出22

	return 0;
}

总结:三种传对象的方式区别:

Plus*:传对象地址

const Plus&:万能,普通对象、临时对象都能接

Plus&&:只接收临时右值,或者 move 转换后的对象

3、bind

bind是一个函数模板,也是一个可调用对象的包装器,接收任意可调用对象,返回一个新的可调用对象,它可以用来调整参数个数和参数顺序

// 版本1:自动推导返回值
template <class Fn, class... Args>
	/* unspecified */ bind (Fn&& fn, Args&&... args);
	
// 版本2:手动指定返回值
template <class Ret, class Fn, class... Args>
	/* unspecified */ bind (Fn&& fn, Args&&... args);

bind的一般形式auto newCallable = bind(callable,arg_list);

newCallable:可调用对象

callable:代表原始可调用对象,就是你想要改装的函数

arg_list:是逗号分隔的参数,分两种类型:1、固定常量(数字 / 变量 / 对象)2、占位符 _1 _2 _3

_1为newCallable的第⼀个参数,_2为第⼆个参数,以此类推

#include<functional>

using placeholders::_1;
using placeholders::_2;
using placeholders::_3;

int Sub(int a, int b)
{
	return (a - b) * 10;
}

int SubX(int a, int b, int c)
{
	return (a - b - c) * 10;
}

int main()
{
	cout << Sub(10, 5) << endl;

	// bind 本质返回的一个仿函数对象
	// 调整参数顺序(不常用)
	// _1代表第一个实参
	// _2代表第二个实参
	// ...
	// _2填给Sub的第一个形参a,_1 填给 Sub 的第 2 个形参 b
	auto newSub1 = bind(Sub, _2, _1);
	cout << newSub1(10, 5) << endl;

	// 调整参数个数
	// _1填给Sub的a,常量10固定填给Sub的b
	auto newSub2 = bind(Sub, _1, 10);
	cout << newSub2(20) << endl;

	// 常量5固定填给Sub的a,_1填给Sub的b
	auto newSub3 = bind(Sub, 5, _1);
	cout << newSub3(20) << endl;

	// 常量5填给SubX的a,_1填给SubX的b,_2填给SubX的c
	auto newSub4 = bind(SubX, 5, _1, _2);
	cout << newSub4(10, 20) << endl;

	// _1填给SubX的a,常量5填给SubX的b,_2填给SubX的c
	auto newSub5 = bind(SubX, _1, 5, _2);
	cout << newSub5(10, 20) << endl;

	// _1填给SubX的a,_2填给SubX的b,常量5填给SubX的c
	auto newSub6 = bind(SubX, _1, _2, 5);
	cout << newSub6(10, 20) << endl;

写_n:调用新函数时传入的第 n 个实参会填充到这个原函数形参位置

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐