C++11包装器

目录

function包装器

function包装器介绍

function包装器统一类型

function包装器简化代码

function包装器的意义

bind包装器

bind包装器介绍

bind包装器绑定固定参数

bind包装器调整传参顺序

bind包装器的意义 


function包装器

function包装器介绍

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

function类模板参数的原型如下:

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

 模板参数说明:

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

包装器示例 

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

int f(int a, int b)
{
	return a + b;
}
struct Functor
{
	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()
{
	//包装函数指针
	function<int(int, int)> func1 = f;
	cout << func1(1, 2) << endl;
	//包装防函数(函数对象)
	function<int(int, int)>func2 = Functor();
	cout << func2(1, 2) << endl;
	//包装lambda表达式
	function<int(int, int)> func3 = [](int a, int b)->int {return a + b; };
	cout << func3(1, 2) << endl;
	//类的静态成员函数包装
	function<int(int, int)>func4 = &Plus::plusi;
	cout << func4(1, 2) << endl;
	//类的非静态成员函数包装
	function<double(Plus,double, double)>func5 = &Plus::plusd;
	cout << func5(Plus(), 1.1, 2.2) << endl;
	return 0;
}

 注意事项:

  • 包装时指明返回值类型和各形参类型,然后将可调用对象赋值给function包装器,包装后的function解可以像普通函数一样使用了.
  • 去静态成员函数的地址可以不用取地址运算符"&",但取非静态成员函数的地址必须使用取地址运算符.
  • 包装非静态的成员函数时需要注意,非静态成员函数的第一个参数是隐藏的this指针,因此在包装时需要指明第一个形参的类型为类的类.

function包装器统一类型

对于一下函数模板useF:

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

代码如下:

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

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

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

由于函数指针,防函数,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;

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

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

function包装器简化代码

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

  • 定义一个栈,依次遍历所给字符串.
  • 如果遍历到的字符串时数字则直接入栈.
  • 如果遍历到的字符串是加减乘除运算符,则从栈顶抛出两个数字进行对应的运算,并将运算后得到的结果压入栈中.
  • 所给字符串遍历完毕后,栈顶的数字就是逆波兰表达式的计算结果. 

代码如下:

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> st;
        for(int i = 0;i<tokens.size();i++)
        {
            if(tokens[i] == "+"
             ||tokens[i] == "-"
             ||tokens[i] == "*"
             ||tokens[i] == "/")
             {
                 int right = st.top();
                 st.pop();
                 int left = st.top();
                 st.pop();
                 switch(tokens[i][0])
                 {
                     case '+':
                        st.push(right+left);
                        break;
                    case '-':
                        st.push(left-right);
                        break;
                    case '*':
                        st.push(left*right);
                        break;
                    case '/':
                        st.push(left/right);
                        break;
                 }
             }
             else
             {
                 st.push(stoi(tokens[i]));
             }
        }
        return st.top();
    }
};

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

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

  • 建立各个运算符与其对应需要执行的函数之间的映射关系,当需要执行某一运算时就可以直接通过运算符找到对应的函数执行.
  • 当运算符类型增加时,就只需要建立新增运算符与其对应函数之间的映射关系即可.

代码如下:

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> st;
        unordered_map<string,function<int(int,int)>>opmap = {
            {"+",[](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;}},
        };
        for(const auto& ch:tokens)
        {
            int left,right;
            if(ch == "+"||ch == "-"||ch =="*"||ch =="/")
            {
                right = st.top();
                st.pop();
                left = st.top();
                st.pop();
                st.push(opmap[ch](left,right));
            }
            else
            {
                st.push(stoi(ch));
            }
        }
        return st.top();
    }
};

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

function包装器的意义

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

bind包装器

bind包装器介绍

bind包装器 

 

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

bind函数模板的原型如下:

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

 参数模板说明:

  • 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, 1) << endl;
	return 0;
}

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

绑定固定参数 

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

int Plus(int a, int b)
{
	return a + b;
}

int main()
{
	function<int(int)> func = bind(Plus, placeholders::_1, 10);
	cout << func(1) << endl;
	return 0;
}

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

bind包装器调整传参顺序

 调整传参顺序

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

class Sub
{
public:
	int sub(int a, int b)
	{
		return a - b;
	}
};

int main()
{
	function<int(int, int)> func = bind(&Sub::sub, Sub(), placeholders::_1, placeholders::_2);
	cout<<func(1, 2)<<endl;
	return 0;
}

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

如果想要将sub成员函数用于相减的两个参数交换顺序,那么直接在绑定时将placeholder::_1和placeholder::_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;
	return 0;
}

根本原因就是因为,后续调用新生成的可调用对象时,传入的第一个参数会传给placeholder::_1,传入的第二个参数会传个placeholder::_2,因此可以在绑定时通过控制placeholder::_n的位置,来控制第n个参数传递的位置. 

bind包装器的意义 

  • 将一个函数的某些参数绑定为固定值,让我们在调用时可以不要传递某些参数.
  • 可以对函数参数的顺序进行灵活调整. 
  • 28
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值