【C++详解】C++11(四) 包装器:function、bind、STL中⼀些变化

文章目录


一、STL中⼀些变化

  • 下图圈起来的就是STL中的新容器,但是实际最有⽤的是unordered_map和unordered_set。这两个我们前⾯已经进⾏了⾮常详细的讲解,其他的仅需了解⼀下即可。
  • STL中容器的新接⼝也不少,最重要的就是右值引⽤和移动语义相关的push/insert/emplace系列接⼝和移动构造和移动赋值,还有initializer_list版本的构造等,这些前⾯都讲过了,还有⼀些⽆关痛痒的如cbegin/cend等需要时查查⽂档即可。
  • 容器的范围for遍历,这个在容器部分也讲过了。
  • 还有一点有关swap,我们知道在C++11之前有三种swap函数,一种是算法库的全局swap,一种是针对部分容器实现的全局特化swap,一种是容器的成员函数swap。其中算法库全局swap任何类型都可以交换,但是对于还未实现全局特化版本swap的容器来说调用全局swap就会发生三次深拷贝。所以C++11引入了具有右值引用移动语义特性的全局swap:

这里接受参数用的左值引用而没用万能引用的原因是交换对象通常可以长期存在并且可以修改,所以用左值引用就足够了。相比C++98直接进行一次拷贝构造和两次拷贝赋值,C++11会先对对象进行move,使其具有右值属性,调用构造和赋值时就会变成调用一次移动构造和两次移动赋值,这也符合交换时逻辑,减少深拷贝代价,大大提升效率。除此之外C++11的swap还支持了数组的交换。

二、包装器

我们之前已经介绍了三种可调用对象:函数指针、仿函数、lambda表达式,但其可调用对象不止这些,例如普通函数、函数指针、类的成员函数(包括静态成员函数)、 类的成员函数指针都属于可调用对象,可调用对象的特征就是能通过 () 执行。

而接下来我们要介绍的包装器是C++11 引入的一种模板类,它可以可统一包装上述所有可调用对象(只要签名匹配),方便存储和传递。例如当map的第二个模板参数是function:

cpp 复制代码
map<string, function<int(int, int)>>

那么map的第二个类型可以传上述可调用对象的任意一种。

function

cpp 复制代码
template <class T>
class function; // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;
  • 以上是 function 的原型,他被定义头⽂件中,语法规定Ret匹配可调用对象的返回值类型,args...匹配可调用对象的参数列表类型。
  • std::function 是⼀个类模板,也是⼀个包装器。 std::function 的实例对象可以包装存 储其他的可以调⽤对象,包括函数指针、仿函数、 lambda 、 bind 表达式等,存储的可调⽤对象被称为 std::function的⽬标。若 std::function 不含⽬标,则称它为空。调⽤空std::function 的⽬标导致抛出std::bad_function_call 异常。
  • C++ 语法规定,普通函数(如示例f)的函数名在赋值时可以隐式转换为函数指针(&可省略),因为它不属于任何类,是全局作用域的符号,但获取成员函数(包括静态成员函数)的地址时,必须使用&加上指定类域。
  • 包装非成员函数时要注意还有⼀个隐含的this指针参数,所以我们还需要"传递"this指针,但是注意这里并不是真正把this指针传递给调用它的对象,因为this指针无法显示传递,而这里"传递"this指针是因为普通成员函数需要一个 this 指针来关联调用它的对象,所以绑定时传对象或者对象的指针过去都可以,这两种传递方式都能为 this 指针提供有效的 "关联对象"。
cpp 复制代码
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;
	}

	double plusd(double a, double b)
	{
		return (a + b) * _n;
	}

private:
	int _n;
};

int main()
{
	// 包装各种可调⽤对象
	function<int(int, int)> f1 = f; //包装函数指针
	function<int(int, int)> f2 = Functor(); //包装仿函数对象
	function<int(int, int)> f3 = [](int a, int b) {return a + b; }; //包装lambda
	cout << f1(1, 1) << endl;
	cout << f2(1, 1) << endl;
	cout << f3(1, 1) << endl;

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

	// 包装普通成员函数
	// 普通成员函数还有⼀个隐含的this指针参数,所以绑定时传对象或者对象的指针过去都可以
	function<double(Plus*, double, double)> f5 = &Plus::plusd;
	Plus pd;
	cout << f5(&pd, 1.1, 1.1) << endl;
	function<double(Plus&, double, double)> f6 = &Plus::plusd;
	cout << f6(pd, 1.1, 1.1) << endl;

	function<double(Plus&&, double, double)> f7 = &Plus::plusd;
	cout << f7(move(pd), 1.1, 1.1) << endl;
	cout << f7(Plus(), 1.1, 1.1) << endl;

	return 0;
}
  • 函数指针、仿函数、 lambda 等可调⽤对象的类型各不相同, std::function 的优势就是统⼀类型,对他们都可以进⾏包装,这样在很多地⽅就⽅便声明可调⽤对象的类型,下⾯以后缀表达式题目样例展⽰了 std::function作为map的参数,实现字符串和可调⽤对象的映射表功能。
cpp 复制代码
class Solution {
public:
	int evalRPN(vector<string>& tokens) {
		stack<int> st;
		for (auto& str : tokens)
		{
			if (str == "+" || str == "-" || str == "*" || str == "/")
			{
				int right = st.top();
				st.pop();
				int left = st.top();
				st.pop();
				switch (str[0])
				{
				case '+':
					st.push(left + right);
					break;
				case '-':
					st.push(left - right);
					break;
				case '*':
					st.push(left * right);
					break;
				case '/':
					st.push(left / right);
					break;
				}
			}
			else
			{
				st.push(stoi(str));
			}
		}
		return st.top();
	}
};
// 使⽤map映射string和function的⽅式实现
// 这种⽅式的最⼤优势之⼀是⽅便扩展,假设还有其他运算,我们增加map中的映射即可
class Solution {
public:
	int evalRPN(vector<string>& tokens) {
		stack<int> st;
		// function作为map的映射可调⽤对象的类型
		map<string, function<int(int, int)>> opFuncMap = {
		{"+", [](int x, int y) {return x + y; }},
		{"-", [](int x, int y) {return x - y; }},
		{"*", [](int x, int y) {return x * y; }},
		{"/", [](int x, int y) {return x / y; }}
		};

		for (auto& str : tokens)
		{
			if (opFuncMap.count(str)) // 操作符
			{
				int right = st.top();
				st.pop();
				int left = st.top();
				st.pop();
				int ret = opFuncMap[str](left, right);
				st.push(ret);
			}
			else
			{
				st.push(stoi(str));
			}
		}
		return st.top();
	}
};

上面的示例代码涉及两个细节:

第一个是map的count接口,在 C++ 的 std::map 中,count接口的核心功能是统计容器中指定键(key)的出现次数,但由于 std::map 的键是唯一的(每个键最多对应一个值),因此它的返回值只能是 0(键不存在)或 1(键存在)。

第二个是有关这行关键代码的理解:

cpp 复制代码
int ret = opFuncMap[str](left, right);

这行代码首先运用的map的operator[ ],它的功能是通过key返回value,体现在这里就是通过查找str返回对应包装器包装的lambda表达式,然后lambda表达式通过left和right参数计算返回结果。

bind

  • 区别于类模板function,bind是⼀个函数模板,它也是⼀个可调⽤对象的包装器,可以把他看做⼀个函数适配器,对接收的fn可调⽤对象进⾏处理后返回⼀个可调⽤对象。bind 可以⽤来调整参数个数和参数顺序。bind 也在< functional >这个头⽂件中。
  • 调⽤bind的⼀般形式: auto newCallable = bind(callable,arg_list); 其中newCallable本⾝是⼀个可调⽤对象,arg_list是⼀个逗号分隔的参数列表,对应给定的callable的参数。当我们调⽤newCallable时,newCallable会调⽤callable,并传给它arg_list中的参数。
  • arg_list中的参数可能包含形如_n的名字,其中n是⼀个整数,这些参数是占位符,表⽰newCallable的参数,它们占据了传递给newCallable的参数的位置。数值n表⽰⽣成的可调⽤对象中参数的位置:_1为newCallable的第⼀个参数,_2为第⼆个参数,以此类推。_1/_2/_3...这些占位符放到placeholders的⼀个命名空间中。
cpp 复制代码
using namespace std;
#include<functional>
#include <iostream>

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;
}

class Plus
{
public:
	static int plusi(int a, int b)
	{
		return a + b;
	}
	double plusd(double a, double b)
	{ 
		return a + b;
	}
};

示例一:调整参数顺序

cpp 复制代码
	// bind 本质返回的⼀个仿函数对象
	// 调整参数顺序(不常⽤)
	// _1代表第⼀个实参
	// _2代表第⼆个实参
	// ...
	auto sub1 = bind(Sub, _1, _2);
	cout << sub1(10, 5) << endl;

	auto sub2 = bind(Sub, _2, _1);
	cout << sub2(10, 5) << endl;

示例二:调整参数个数

(要绑死callable的哪个参数,在绑定newCallable时就不用参数,而是直接写死)

cpp 复制代码
	// 调整参数个数 (常⽤)
	//绑死a
	auto sub3 = bind(Sub, 100, _1);
	cout << sub3(5) << endl;
	//绑死b
	auto sub4 = bind(Sub, _1, 100);
	cout << sub4(5) << endl;
cpp 复制代码
	// 分别绑死第123个参数
	auto sub5 = bind(SubX, 100, _1, _2);
	cout << sub5(5, 1) << endl;
	auto sub6 = bind(SubX, _1, 100, _2);
	cout << sub6(5, 1) << endl;
	auto sub7 = bind(SubX, _1, _2, 100);
	cout << sub7(5, 1) << endl;

调整参数个数的实际运用1:绑死成员函数的this指针,不用自己手动传递

cpp 复制代码
	// 成员函数对象进⾏绑死,就不需要每次都传递了
	function<double(Plus&&, double, double)> f6 = &Plus::plusd;
	Plus pd;
	cout << f6(move(pd), 1.1, 1.1) << endl;
	cout << f6(Plus(), 1.1, 1.1) << endl;

	// bind⼀般⽤于,绑死⼀些固定参数
	function<double(double, double)> f7 = bind(&Plus::plusd, Plus(), _1, _2);
	cout << f7(1.1, 1.1) << endl;

调整参数个数的实际运用2:计算复利的lambda

cpp 复制代码
	// 计算复利的lambda
	auto func1 = [](double rate, double money, int year)->double {
		double ret = money;
		for (int i = 0; i < year; i++)
		{
			ret += ret * rate;
		}
		return ret - money;
	};

	// 绑死⼀些参数,实现出⽀持不同年华利率,不同⾦额和不同年份计算出复利的结算利息
	function<double(double)> func3_1_5 = bind(func1, 0.015, _1, 3);
	function<double(double)> func5_1_5 = bind(func1, 0.015, _1, 5);
	function<double(double)> func10_2_5 = bind(func1, 0.025, _1, 10);
	function<double(double)> func20_3_5 = bind(func1, 0.035, _1, 30);

	cout << func3_1_5(1000000) << endl;
	cout << func5_1_5(1000000) << endl;
	cout << func10_2_5(1000000) << endl;
	cout << func20_3_5(1000000) << endl;

以上就是小编分享的全部内容了,如果觉得不错还请留下免费的关注和收藏
如果有建议欢迎通过评论区或私信留言,感谢您的大力支持。
一键三连好运连连哦~~

相关推荐
眠りたいです8 小时前
基于脚手架微服务的视频点播系统-界面布局部分(二):用户界面及系统管理界面布局
c++·qt·ui·微服务·云原生·架构·cmake
mit6.8248 小时前
[re_3]
c++·算法
乌萨奇也要立志学C++8 小时前
【C++详解】异常概念、抛出捕获与处理机制全解析
开发语言·c++
DS数模8 小时前
2025国赛B题保姆级教程思路分析 碳化硅外延层厚度的确定
开发语言·数学建模·2025数学建模·2025数学建模国赛·2025国赛·2025高教社杯数学建模·2025国赛b题
数维学长9868 小时前
【全网最全】《2025国赛/高教杯》C题 思路+代码python和matlab+文献 一到四问 退火算法+遗传算法 NIPT的时点选择与胎儿的异常判定
开发语言·算法·matlab
XXYBMOOO8 小时前
使用Qt Charts实现高效多系列数据可视化
开发语言·qt·ui·信息可视化
源代码•宸9 小时前
Leetcode—3516. 找到最近的人【简单】
c++·经验分享·算法·leetcode
1白天的黑夜19 小时前
哈希表-219.存在重复元素II-力扣(LeetCode)
数据结构·c++·leetcode
Pocker_Spades_A9 小时前
Python快速入门专业版(三):print 格式化输出:% 占位符、format 方法与 f-string(谁更高效?)
开发语言·python