【C++】包装器-bind &function

这篇具有很好参考价值的文章主要介绍了【C++】包装器-bind &function。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

包装器

function包装器

function包装器介绍

function包装器

function是一种函数包装器,也叫做适配器它可以对可调用对象进行包装.C++中的function本质就是一个类模板

function类模板的原型如下:

template <class T> function;     // undefined 报错!缺少类模板function的参数模板
//正确写法:
template <class Ret, class... Args>
class function<Ret(Args...)>;

参数说明:

Ret:被包装的可调用对象的返回值类型. Args...:被包装的可调用对象的形参类型.


包装示例

function包装器可以对可调用对象进行包装.包括函数指针(函数名), 仿函数(函数对象),lambda表达式,类的成员函数

#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:
	static int plusi(int a, int b)
	{
		return a + b;
	}
	double plusd(double a, double b)
	{
		return a + b;
	}
};
int main()
{
	//1、包装函数指针(函数名)
    //要包装f:返回值是int,参数有两个都是int,function<int(int, int)> 包装器的类型
	function<int(int, int)> func1 = f;
	cout << func1(1, 2) << endl;

	//2、包装仿函数(函数对象)
	function<int(int, int)> func2 = Functor();
	cout << func2(1, 2) << endl;

	//3、包装lambda表达式
	function<int(int, int)> func3 = [](int a, int b){return a + b; };
	cout << func3(1, 2) << endl;

	//4、类的静态成员函数
	//function<int(int, int)> func4 = Plus::plusi;//静态的成员函数取地址,可以加& 也可以不加
	function<int(int, int)> func4 = &Plus::plusi; //&可省略
	cout << func4(1, 2) << endl;

	//5、类的非静态成员函数
    //非静态的成员函数,需要多增加一个类型,因为需要对象去调用,第一个参数是隐藏this指针
	function<double(Plus, double, double)> func5=&Plus::plusd;//普通的成员函数取地址必须要加&
	cout << func5(Plus(), 1.1, 2.2) << endl;
	return 0;
}

注意1:包装时指明返回值类型和各形参类型,然后将可调用对象赋值给function包装器即可,包装后function对象就可以像普通函数一样使用了

2.取静态成员函数的地址可以不用取地址运算符“&”,但取非静态成员函数的地址必须使用取地址运算符“&”

3.包装非静态的成员函数时需要注意:非静态成员函数的第一个参数是隐藏this指针,因此在包装时需要指明第一个形参的类型为类的类型


function包装器统一类型

包装器可以对不同类型的东西包装成统一的类型格式

template<class F, class T>
T useF(F f, T x)
{
	static int count = 0;
	cout << "count: " << ++count << endl;
	cout << "count: " << &count << endl;

	return f(x);
}

对于上述函数模板useF:

  • 传入该函数模板的第一个参数可以是任意的可调用对象,比如函数指针、仿函数、lambda表达式
  • useF函数中定义了静态变量count,并在每次调用时将count的值和地址进行了打印,可判断多次调用时调用的是否是同一个useF函数

在传入第二个参数类型相同的情况下,如果传入的可调用对象的类型是不同的,那么在编译阶段该函数模板就会被实例化多次

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;

	//lambda表达式
	cout << useF([](double d)->double{return d / 4; }, 11.11) << endl;
	return 0;
}

【C++】包装器-bind &function

由于函数指针、仿函数、lambda表达式是不同的类型,因此useF函数会被实例化出三份,三次调用useF函数所打印count的地址也是不同的, 但实际这里根本没有必要实例化出三份useF函数,因为三次调用useF函数时传入的可调用对象虽然是不同类型的.但这三个可调用对象的返回值和形参类型都是相同的,那如何解决呢?

  • 这时就可以用包装器分别对着三个可调用对象进行包装,然后再用这三个包装后的可调用对象来调用useF函数.这时就只会实例化出一份useF函数
  • 根本原因就是因为包装后,这三个可调用对象都是相同的function类型.因此最终只会实例化出一份useF函数.该函数的第一个模板参数的类型就是function类型的
int main()
{
	//函数名
	function<double(double)> func1 = f;
	cout << useF(func1, 11.11) << endl;

	//函数对象
	function<double(double)> func2 = Functor();
	cout << useF(func2, 11.11) << endl;

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

【C++】包装器-bind &function

这时三次调用useF函数所打印count的地址就是相同的,并且count在三次调用后会被累加到3,表示这一个useF函数被调用了三次.


function包装器简化代码的列子

求解逆波兰表达式的步骤如下:

  • 定义一个栈.依次遍历所给字符串.
  • 如果遍历到的字符串是数字则直接入栈.
  • 如果遍历到的字符串是加减乘除运算符.则从栈定抛出两个数字进行对应的运算.并将运算后得到的结果压入栈中.
  • 所给字符串遍历完毕后.栈顶的数字就是逆波兰表达式的计算结果
class Solution {
public:
    //得到两个操作数
    void  getNum(stack<int>&st,int& left,int& right)
    {
        right = st.top(); //先出的是右操作数
        st.pop();
        left = st.top();
        st.pop();
    }
    //tokens = ["2","1","+","3","*"]
    int evalRPN(vector<string>& tokens) {
        stack<int> st;//存放结果
        for(auto& str: tokens)//遍历字符串
        {
            int left,right;
            switch(str[0]) 
            {
                case '+':
                    getNum(st,left,right);//得到两个操作数
                    st.push(left+right);//把计算结果压到栈中
                    break;
                case '-':
                    //操作符
                    if(str.size() == 1)
                    {
                        getNum(st,left,right);//得到两个操作数
                        st.push(left-right);//把计算结果压到栈中
                        break; 
                    }
                    //操作数
                    else
                    {
                        st.push(stoi(str));//转为整数压到栈中
                        break;
                    }
                case '*':
                    getNum(st,left,right);//得到两个操作数//得到两个操作数
                    st.push(left*right);
                    break; 
                case '/':
                    getNum(st,left,right);//得到两个操作数
                    st.push(left/right);//把计算结果压到栈中
                    break; 
                default:
                    st.push(stoi(str));//转为整数压到栈中
                    break;
            }
        }
        return st.top();//返回栈顶数据就是最后的结果
    }
};

在上述代码中.我们通过switch语句来判断本次需要进行哪种运算,但是如果运算类型增加了,比如增加了求余、幂、对数等运算,那么就需要在switch语句的后面中继续增加case语句

这种情况可以用包装器来简化代码:

  • 建立各个运算符与其对应需要执行的函数之间的映射关系,当需要执行某一运算时就可以直接通过运算符找到对应的函数进行执行. 所以需要使用哈希表: unordered_map<string, function<int(int, int)>>
  • 当运算类型增加时.就只需要建立新增运算符与其对应函数之间的映射关系即可.

需要注意的是.这里建立的是运算符与function类型之间的映射关系.因此无论是函数指针、仿函数还是lambda表达式都可以在包装后与对应的运算符进行绑定

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        //建立运算符与其对应需要执行的函数之间的映射关系
        unordered_map<string,function<int(int,int)>> opFuncMap;
        //插入键值对
        opFuncMap["+"] = [](int a, int b) ->int{return a+b;};
        opFuncMap["-"] = [](int a, int b) ->int{return a-b;};
        opFuncMap["*"] = [](int a, int b) ->int{return a*b;};
        opFuncMap["/"] = [](int a, int b) ->int{return a/b;};

        stack<int> st;
        for(size_t i = 0;i<tokens.size();i++)
        {
            string& str = tokens[i];
            //如果str为操作数:直接进栈
            if(opFuncMap.find(str) == opFuncMap.end())
            {
                //字符串->整数压入栈
                st.push(stoi(str));//st.push(atoi(str.c_str()))
            }
            else //str为操作符,取栈顶的两个元素进行运算
            {
                //注意:先拿到的是右操作数
                int right = st.top();
                st.pop();
                int left = st.top();
                st.pop();
                //opFuncMap[str]得到的就是和str操作符对应的函数, 然后调用该函数进行计算
                st.push(opFuncMap[str](left,right));//把计算结果压入栈
            }
        }
        return st.top();//返回栈顶的元素,就说最终计算结果
    }
};

当然也可以这样初始化:

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

如果还有其它符号,下面的代码是不需要改动的,只需要增加键值对!


注意:a*b可能存在溢出的情况,所以需要改为:

opFuncMap["*"] = [](int a, int b) ->int{return (long)a*b;}; 强转一下类型


function包装器的意义

  • 将可调用对象的类型进行统一,便于我们对其进行统一化管理.
  • 包装后明确了可调用对象的返回值和形参类型,更加方便使用者使用

bind包装器

bind包装器介绍

bind也是一种函数包装器,也叫做适配器,它可以接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表,C++中的bind本质是一个函数模板

template <class Fn, class... Args>
/* unspecified */ bind(Fn&& fn, Args&&... args);
template <class Ret, class Fn, class... Args>
/* unspecified */ bind(Fn&& fn, Args&&... args);

参数说明

fn:可调用对象. args...:要绑定的参数列表:值或占位符.


调用bind的一般形式

调用bind的一般形式为:auto newCallable = bind(callable, arg_list);

  • callable:需要包装的可调用对象.
  • newCallable:生成的新的可调用对象.
  • arg_list:逗号分隔的参数列表.对应给定的callable的参数,当调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数.

arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是“占位符”,表示newCallable的参数,它们占据了传递给newCallable的参数的“位置”

  • 数值n表示生成的可调用对象中参数的位置.比如_1为newCallable的第一个参数,_2为第二个参数.以此类推

除了用auto接收包装后的可调用对象,也可以用function类型指明返回值和形参类型后接收包装后的可调用对象


bind包装器绑定固定参数

无意义的绑定

下面这种绑定就是无意义的绑定:

int Plus(int a, int b)
{
	return a + b;
}
int main()
{
	//无意义的绑定
	function<int(int, int)> func = bind(Plus, placeholders::_1, placeholders::_2);
	cout << func(1, 2) << endl; //3
    cout << Plus(1, 2) << endl; //3
	return 0;
}

绑定时第一个参数传入函数指针这个可调用对象,但后续传入的要绑定的参数列表依次是placeholders::_1和placeholders::_2,表示后续调用新生成的可调用对象时,传入的第一个参数传给placeholders::_1,传入的第二个参数传给placeholders::_2, 此时绑定后生成的新的可调用对象的传参方式和原来没有绑定的可调用对象是一样的,所以说这是一个无意义的绑定.


绑定固定参数

如果想把Plus函数的第二个参数固定绑定为10,可以在绑定时将参数列表的placeholders::_2设置为10

int Plus(int a, int b)
{
	return a + b;
}
int main()
{
	//绑定固定参数,第二个参数固定绑定为10
	function<int(int)> func = bind(Plus, placeholders::_1, 10);
    //使用auto更加方便:
    //auto func = bind(Plus, placeholders::_1, 10);
	cout << func(2) << endl; //12
	return 0;
}

此时调用绑定后新生成的可调用对象时就只需要传入一个参数,它会将该值与10相加后的结果进行返回


bind包装器调整传参顺序

调整传参顺序

对于下面Sub类中的sub成员函数,对于非静态成员函数:第一个参数是隐藏的this指针,如果想要在调用sub成员函数时不用对象进行调用,那么可以将sub成员函数的第一个参数固定绑定为一个Sub对象

class Sub
{
public:
	int sub(int a, int b)
	{
		return a - b;
	}
};
int main()
{
    //绑定非静态成员函数,需要多增加一个类型参数
	//绑定固定参数 要绑定的函数是&Sub::sub, 将sub成员函数的第一个参数固定绑定为一个Sub对象-Sub()
	function<int(int, int)> func = bind(&Sub::sub, Sub(), placeholders::_1, placeholders::_2);
	cout << func(1, 2) << endl; //-1
	return 0;
}

此时调用绑定后生成的可调用对象时,就只需要传入用于相减的两个参数了,因为在调用时会固定帮我们传入一个匿名对象给this指针


如果想要将sub成员函数用于相减的两个参数的顺序交换,那么在绑定时将placeholders::_1和placeholders::_2的位置交换一下就行了

class Sub
{
public:
	int sub(int a, int b)
	{
		return a - b;
	}
};
int main()
{
	//调整传参顺序
	function<int(int, int)> func = bind(&Sub::sub, Sub(), placeholders::_2, placeholders::_1);
	cout << func(1, 2) << endl; //1
	return 0;
}

根本原因就是: 后续调用新生成的可调用对象时,传入的第一个参数会传给placeholders::_1.传入的第二个参数会传给placeholders::_2, 因此可以在绑定时通过控制placeholders::_n的位置.来控制第n个参数的传递位置文章来源地址https://www.toymoban.com/news/detail-476332.html


bind包装器的意义

  • 将一个函数的某些参数绑定为固定的值,让我们在调用时可以不用传递某些参数.
  • 可以对函数参数的顺序进行灵活调整.

到了这里,关于【C++】包装器-bind &function的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • c++ | this指针 和bind、function

    个人理解: this 指针 可以简单理解为类 对象的 指针(也是隐藏指针),注意,类的成员(成员函数、成员变量)是通过类的对象进行调用的。如果把函数充当成员函数是错误的,粗鲁的解释,类的成员函数都有一个隐藏的指针(this指针),但是 c函数是没有指针的。 看看

    2024年02月13日
    浏览(36)
  • 【C++11】 包装器 | bind

    function包装器 也被叫做 适配器 C++11中function本质是 类模板, 也是一个包装器 意义在于 对可调用对象类型进行封装再适配 可调用对象:函数指针 / lambda / 仿函数 需要包含 头文件 functional 模板参数 Ret : 被调用函数的返回类型 …Args作为参数包,这个参数包中包含0到任意个模

    2024年02月12日
    浏览(40)
  • C++11 function包装器

    在C++中,有三种 可调用对象 :函数指针,仿函数,lambda表达式。 三者有相似的作用和效果,但使用形式有很大的差异。 为了进行统一,C++11引进了 function包装器 首先,要想使用function,需要包含functional这个头文件 function包装器本质是一个 类模板 以往,如果要实现一个加法

    2024年02月13日
    浏览(61)
  • C++多态与虚拟:函数重载(Function Overloading)

    重载(Overloading):所谓重载是指不同的函数实体共用一个函数名称。例如以下代码所提到的CPoint之中,有两个member functions的名称同为x():    其两个member functions实现代码如下: 函数名称相同,但参数不同(个数不同,型别也不同),实现代码也不相同。C++之所以有function

    2024年04月25日
    浏览(40)
  • 【C++】STL 算法 ⑩ ( 函数适配器 | 函数适配器概念 | 函数适配器分类 | 函数适配器辅助函数 | std::bind2nd 函数原型及示例 | std::bind 函数原型及示例 )

    在 STL 中 预定义了很多 函数对象 , 如果要 对 函数对象 的 参数 / 返回值 进行 计算 或 设置 , 可以 使用 \\\" 函数适配器 \\\" 实现上述需求 ; \\\" 函数适配器 \\\" 可以 将 已存在的 函数对象 转化为 另一种符合要求的 函数对象 ; \\\" 函数适配器 \\\" 定义在 functional 头文件 中 ; \\\" 函数适配器

    2024年02月02日
    浏览(65)
  • C++std::function和std::bind()的概念

    std::function: 一个通用的函数封装器,它允许你存储和调用 任何可以被调用的东西 ,例如函数、函数指针、函数对象、Lambda 表达式等。 std::bind: 用于创建 函数对象 。一个 可调用对象的绑定版本 ,可以 提前绑定 某些参数,稍后调用时只需提供剩余的参数。 在某些情况下

    2024年02月10日
    浏览(39)
  • C++11的std::function和bind绑定器

            在C++中,存在“可调用对象”这么一个概念。准确来说,可调用对象有如下几种定义:                 1、是一个函数指针                 2、是一个具有operator()成员函数的类对象(仿函数)                 3、是一个可转换为函数指针的类对象            

    2024年02月08日
    浏览(38)
  • 【JavaScript】手撕前端面试题:手写Object.create | 手写Function.call | 手写Function.bind

    🖥️ NodeJS专栏:Node.js从入门到精通 🖥️ 博主的前端之路(源创征文一等奖作品):前端之行,任重道远(来自大三学长的万字自述) 🖥️ TypeScript知识总结:TypeScript从入门到精通(十万字超详细知识点总结) 🧑‍💼个人简介:大三学生,一个不甘平庸的平凡人🍬 👉

    2024年02月21日
    浏览(78)
  • 【工具小技巧】Cadence Virtuoso Calculator Function Panel计算器函数功能介绍(持续更新……)

    在使用cadence virtuoso仿真过程中我们经常会关注一些电路指标,比如:运放的增益、带宽、相位裕度;bandgap的温漂系数、振荡器的振荡频率等。想要直观的知道这些指标的具体值,需要用到计算器中的函数,如下为计算器的图标和界面。    详细学习每个函数的具体含义和使

    2023年04月15日
    浏览(64)
  • C++笔记之从使用函数指针和typedef到使用std::function和using

    参考笔记:C++笔记之从数组指针到函数数组指针(使用using name和std::function) code review!

    2024年02月15日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包