【C++:C++11收尾】解构C++可调用对象:从入门到精通,掌握function包装器与bind适配器包装器详解


🎬 个人主页艾莉丝努力练剑
专栏传送门 :《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录
Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享

⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平


🎬 艾莉丝的简介:


🎬 艾莉丝的C++专栏简介:


文章目录

  • C++学习阶段的三个参考文档
  • [8 ~> 包装器](#8 ~> 包装器)
    • [8.1 function](#8.1 function)
      • [8.1.1 结构](#8.1.1 结构)
      • [8.1.2 概念](#8.1.2 概念)
      • [8.1.3 function实现](#8.1.3 function实现)
      • [8.1.4 重写逆波兰表达式求值](#8.1.4 重写逆波兰表达式求值)
    • [8.2 bind](#8.2 bind)
      • [8.2.1 结构](#8.2.1 结构)
      • [8.2.2 概念](#8.2.2 概念)
      • [8.2.3 代码实现](#8.2.3 代码实现)
  • [9 ~> 智能指针预告](#9 ~> 智能指针预告)
  • C++11完整代码示例与实践演示
  • 结尾

C++学习阶段的三个参考文档

看库文件(非官方文档): Cplusplus.com

这个文档在C++98、C++11时候还行,之后就完全没法用了......

准官方文档(同步更新) ------还 可以看语法C++准官方参考文档

这个行,包括C++26都同步了,我们以后主要会看这个。

官方文档(类似论坛): Standard C++

这个网站上面会有很多大佬,类似于论坛。



8 ~> 包装器

8.1 function

8.1.1 结构

cpp 复制代码
template <class T>
class function; // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;

8.1.2 概念

std::function 是一个类模板,也是一个包装器 std::function 的实例对象可以包装存储其他的可以调用对象,包括函数指针、仿函数、lambdabind表达式 等,存储的可调用对象被称为 std::function 的目标。若std::function不含目标,则称它为空。调用空 std::function 的目标导致抛出std::bad_function_call异常。

以上是function的原型,他被定义头文件中。std::function是function的官方文件链接。

函数指针、仿函数、 lambda 等可调用对象的类型各不相同, std::function 的优势就是统一类型,对他们都可以进行包装,这样在很多地方就方便声明可调用对象的类型,下面的第二个代码样例展示了 std::function 作为map的参数,实现字符串和可调用对象的映射表功能。

8.1.3 function实现


8.1.4 重写逆波兰表达式求值

力扣题目链接: 150. 逆波兰表达式求值

力扣题解链接: 后缀法 && 操作符紧跟操作数 && switch...case...语句解决

这道题我们已经写过一次了,这次我们会采用另一种方式实现。

题目描述:


对应博客链接: 【C++STL :stack && queue (一) 】STL:stack与queue全解析|深入使用(附高频算法题详解)

传统方式实现(之前的实现)------

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]) // 大坑:switch...case语句只能是int类型
                {
                    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)); // 字符串转整型,to_string
            }
        }
        return st.top();
    }
};

我们可以现学现用,使用map映射string和function的方式实现一下------

这种方式的最大优势之一是方便扩展,假设还有其他运算,我们增加map中的映射即可,算法实现如下所示------

cpp 复制代码
class Solution{
public:
    int evalRPN(vector<string>& tokens) {
        map<string,function<int(int,int)>> opFuncMap =
        {
            {"+",[](int a,int b) {return a + b;}},
            {"-",[](int a,int b) {return a - b;}},
            {"*",[](int a,int b) {return a * b;}},
            {"/",[](int a,int b) {return a / b;}}
        };

        stack<int> st;
        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();
    }
};

8.2 bind

8.2.1 结构

cpp 复制代码
simple(1)
template <class Fn, class... Args>
 /* unspecified */ bind (Fn&& fn, Args&&... args);
 
with return type (2)
template <class Ret, class Fn, class... Args>
 /* unspecified */ bind (Fn&& fn, Args&&... args);

8.2.2 概念

bind 是一个函数模板,它也是一个可调用对象的包装器,可以把他看做一个函数适配器,对接收的fn可调用对象进行处理后返回一个可调用对象。 bind 可以用来调整参数个数和参数顺序。 bind 也在这个头文件中。

调用bind的一般形式:

cpp 复制代码
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的一个命名空间中。

8.2.3 代码实现


9 ~> 智能指针预告

C++专栏的主线内容马上就要结束啦,艾莉丝将把智能指针作为C++干货专栏的主线(C++98、C++11部分)的终章,之后本专栏还会更新,内容以C++11、C++14、C++17、C++20、以及其它的加餐内容为主了。感谢大家对艾莉丝的支持!希望uu们能够继续支持艾莉丝哦!感谢大佬们的支持!


C++11完整代码示例与实践演示

Test.cpp:

cpp 复制代码
#define  _CRT_SECURE_NO_WARNINGS  1
#include<iostream>
#include<vector>
#include<functional>
using namespace std;

// ===================C++11:包装器=================
//--------------------function---------------------
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; };
//	cout << f1(1, 1) << endl;
//	cout << f2(1, 1) << endl;
//	cout << f3(1, 1) << endl;
//
//	vector<function<int(int, int)>> v;
//	v.push_back(f);
//	v.push_back(Functor());
//	v.push_back([](int a, int b) {return a + b; });
//
//	for (auto& f : v)
//	{
//		cout << f(1, 1) << endl;
//	}
//	cout << endl;
//
//	//function<int(int, int)> f4 = Plus::plusi;
//	function<int(int, int)> f4 = &Plus::plusi;
//	cout << f4(1, 1) << endl;
//
//	function<double(Plus*, double, double)> f5 = &Plus::plusd;
//	Plus ps;
//	cout << f5(&ps, 1.1, 1.1) << endl;
//
//	function<double(Plus, double, double)> f6 = &Plus::plusd;
//	cout << f6(ps, 1.1, 1.1) << endl;
//
//	function<double(Plus, double, double)> f7 = &Plus::plusd;
//	cout << f7(Plus(), 1.1, 1.1) << endl;		// Plus():匿名对象
//
//	function<double(Plus&&, double, double)> f8 = &Plus::plusd;
//	cout << f8(Plus(), 1.1, 1.1) << endl;
//
//	auto pf1 = &Plus::plusd;
//	Plus* ptr = &ps;
//	cout << (ps.*pf1)(1.1, 1.1) << endl;
//	cout << (ptr->*pf1)(1.1, 1.1) << endl;	// 无法显式传this指针
//
//	return 0;
//}

// 运行结果:
// 2
// 2
// 2
// 2
// 2
// 2
// 
// 2
// 22
// 22
// 22
// 22
// 22
// 22

// ------------------bind-------------------

// _n占位
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()
{
	// bind的本质是返回一个仿函数对象
	// 调整参数顺序(这个功能不常用)
	// _1代表第一个实参
	// _2代表第二个实参
	// ...

	// 交换
	auto f1 = bind(Sub, _1, _2);
	auto f2 = bind(Sub, _2, _1);

	// _1代表第一个实参
	// _2代表第二个实参
	cout << f1(10, 5) << endl;
	cout << f2(10, 5) << endl;

	// 调整参数个数
	auto f3 = bind(SubX, 10, _1, _2);
	cout << f3(15, 5) << endl;
	// _1代表第一个实参
	// _2代表第二个实参
	// 底层operator(),调用SubX,第一个参数10, 15, 5

	auto f4 = bind(SubX, _1, 10, _2);
	cout << f4(15, 5) << endl;
	// 底层operator(),调用SubX,第一个参数15, 10, 5

	auto f5 = bind(SubX, _1, _2, 10);
	cout << f5(15, 5) << endl;
	// 底层operator(),调用SubX,第一个参数15, 5, 10

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

	function<double(Plus&&, double, double)> f8 = &Plus::plusd;
	cout << f8(Plus(), 1.1, 1.1) << endl;
	cout << f8(Plus(), 2.2, 1.1) << endl;
	cout << f8(Plus(), 3.3, 1.1) << endl << endl;

	// 计算复利(利息)的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;
	};

	// 年利率1.5%
	function<double(double)> func_r1_5_3y = bind(func1, 0.015, _1, 3);
	function<double(double)> func_r1_5_5y = bind(func1, 0.015, _1, 5);
	function<double(double)> func_r1_5_20y = bind(func1, 0.015, _1, 20);

	// 本金100000元
	cout << func_r1_5_3y(100000) << endl;
	cout << func_r1_5_5y(100000) << endl;
	cout << func_r1_5_20y(100000) << endl;

	// 这只股票如果利率有10%呢
	function<double(double)> func_r10_3y = bind(func1, 0.1, _1, 3);
	function<double(double)> func_r10_5y = bind(func1, 0.1, _1, 5);
	function<double(double)> func_r10_20y = bind(func1, 0.1, _1, 20);

	cout << func_r10_3y(100000) << endl;
	cout << func_r10_5y(100000) << endl;
	cout << func_r10_20y(100000) << endl;

	// 如果你的10万元到了"股神"巴菲特手里------19%,同样的时间会怎么样?
	function<double(double)> func_r19_3y = bind(func1, 0.19, _1, 3);
	function<double(double)> func_r19_5y = bind(func1, 0.19, _1, 5);
	function<double(double)> func_r19_20y = bind(func1, 0.19, _1, 20);

	cout << func_r19_3y(100000) << endl;
	cout << func_r19_5y(100000) << endl;
	cout << func_r19_20y(100000) << endl;

	return 0;
}

// 运行结果:
// 50
// -50
// -100
// 0
// 0
// 22
// 33
// 44
// 22
// 33
// 44
// 
// 4567.84
// 7728.4
// 34685.5
// 33100
// 61051
// 572750
// 68515.9
// 138635
// 3.14294e+06

结尾

uu们,本文的内容到这里就全部结束了,艾莉丝再次感谢您的阅读!

结语:希望对学习C++相关内容的uu有所帮助,不要忘记给博主"一键四连"哦!

往期回顾

【C++:C++11】C++11新特性深度解析:从可变参数模板到Lambda表达式

🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡 ૮₍ ˶ ˊ ᴥ ˋ˶₎ა

相关推荐
benna1 小时前
多模态知识图谱的构建及在热点新闻事件中的应用
人工智能·知识图谱
小年糕是糕手1 小时前
【C++】类和对象(三) -- 拷贝构造函数、赋值运算符重载
开发语言·c++·程序人生·考研·github·个人开发·改行学it
f***24111 小时前
Spring Boot接收参数的19种方式
java·spring boot·后端
xunyan62341 小时前
面向对象(下)-设计模式与单例设计模式
java·单例模式·设计模式
AI弟1 小时前
第13章 迁移学习:让AI学会“举一反三“的艺术
人工智能·机器学习·迁移学习
ccLianLian1 小时前
MaskCLIP+
人工智能·计算机视觉
杰克尼1 小时前
蓝桥云课-小蓝做题
java·数据结构·算法
卿雪1 小时前
MySQL【索引】篇:索引的分类、B+树、创建索引的原则、索引失效的情况...
java·开发语言·数据结构·数据库·b树·mysql·golang
CNRio1 小时前
第七章-DockerSwarm:容器集群的‘指挥官‘
java·开发语言·容器