C++包装器(functional&&bind)

为什么要有包装器

我们现在学过的可调用对象太多了:

  1. 函数指针
  2. 仿函数对象
  3. lambda表达式对象

ret = func(x);

这里的func可能是面的任意一种

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);
}
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;
	// lamber表达式
	cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;
	return 0;
}

上面的T useF(F f, T x)的f用来接收一个可调用对象,x用来接收一个值,函数内定义了一个静态变量,初始化为0,每次调用这个这个函数就会把静态变量++;

main函数分别传入三种可调用对象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uDDo6LRB-1670438758976)(https://massachusetts-pubic.oss-cn-guangzhou.aliyuncs.com/%E5%9B%BE%E5%BA%8A/202210102133749.png)]

可以发现:同一个静态变量三次++,一直都是1,三次的地址也不同,所以这个静态变量是有三份的,也就是说,函数重载了三份。

所以说,编译器在推演的时候,因为F可能是三种类型的调用,所以就要实例化三份useF函数

那有没有办法让这里useF只实例化成一份呢?

C++11为了将可调用类型统一,就提出了包装器概念

function包装器

function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。

std::function在头文件

类模板原型:

template <class T> function; // undefined

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

模板参数说明:

  • Ret: 被调用函数的返回类型
  • Args…:被调用函数的形参

使用方法:

function<int(int, int)> func1 = f;

使用的时候我们可以把它看成是一个类模板,模板参数用这样一个特殊的方式

#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()
{
	// 函数名(函数指针)
	std::function<int(int, int)> func1 = f;
	cout << func1(1, 2) << endl;
	// 函数对象
	std::function<int(int, int)> func2 = Functor();
	cout << func2(1, 2) << endl;
	// lamber表达式
	std::function<int(int, int)> func3 = [](const int a, const int b){return a + b; };
	cout << func3(1, 2) << endl;
	// 类的成员函数
    //静态成员函数——没有this指针
	std::function<int(int, int)> func4 = &Plus::plusi;//可以不写&
	cout << func4(1, 2) << endl;
    //非静态——有this指针
	std::function<double(Plus, double, double)> func5 = &Plus::plusd;//包装的时候必须有一个this的位置(必须写&)
	cout << func5(Plus(), 1.1, 2.2) << endl;//通过匿名对象调用成员函数
	return 0;
}

赋值nullptr&bool判断

一个function对象可以不进行显式初始化,

也可以用nullptr进行初始化,

甚至可以用nullptr对一个已经定义的对象进行赋值,

但显然初始化为空的function对象是不允许调用的(强制调用,内部会抛出异常)

所以,在调用之前需要进行检测,好在function内提供了operator bool

直接对一个function对象进行if(),即可判断此包装器是否可以调用

int main()
{
    std::function<int(int, int)> foo, bar;

    foo = [](int, int) -> int { return 0; }; // 用lamabda表达式进行赋值
    bar = nullptr; // 赋值为空

    foo.swap(bar); // 同类function支持swap

    std::cout << "foo is " << (foo ? "callable" : "not callable") << ".\n";
    std::cout << "bar is " << (bar ? "callable" : "not callable") << ".\n";

    return 0;
}

运行结果:

foo is not callable.
bar is callable.

包装器的使用场景:字符串对方法的索引

我们可以在如下这道题中体会一下包装器的使用

150. 逆波兰表达式求值 - 力扣(LeetCode)

这道题会给一个vector每个元素都是一个字符串,每个字符串存一个数字或一个运算符

这些数字与符号排列组合成的表达式并非我们平时使用的如:(1+2)*3(中缀表达式)

而是1 2 + 3 *(后缀表达式,也称逆波兰表达式)

要计算下面的逆波兰表达式,

  • 只需要从前向后将值依次取出值,放入一个

  • 当遇到运算符,就把栈顶的两个元素取出,进行当前运算符的运算

如:当前栈中存入的元素有1 2,字符串读到了+,则取出栈顶的1 2,进行1+2

的运算

  • 把运算得数放到栈中

  • 读完整个字符串,栈顶的元素就是运算结果

思路很简单,但是会遇到一个问题:如何把一个“+”的字符串变为一个加法算呢?

当然,我们可以像这样

if(strcmp(str,+)==0)
{
    st.push(a+b);
}
else if...

把每个运算符都设置一个分支

但是写起来多少是有一点恶心的

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> stk;
        int n = tokens.size();
        for (int i = 0; i < n; i++) {
            string& token = tokens[i];
            if (isNumber(token)) {
                stk.push(atoi(token.c_str()));
            } else {
                int num2 = stk.top();
                stk.pop();
                int num1 = stk.top();
                stk.pop();
                switch (token[0]) {
                    case '+':
                        stk.push(num1 + num2);
                        break;
                    case '-':
                        stk.push(num1 - num2);
                        break;
                    case '*':
                        stk.push(num1 * num2);
                        break;
                    case '/':
                        stk.push(num1 / num2);
                        break;
                }
            }
        }
        return stk.top();
    }

    bool isNumber(string& token) {
        return !(token == "+" || token == "-" || token == "*" || token == "/");
    }
};

//作者:LeetCode-Solution
//链接:https://leetcode.cn/problems/evaluate-reverse-polish-notation/solution/ni-bo-lan-biao-da-shi-qiu-zhi-by-leetcod-wue9/
//来源:力扣(LeetCode)

我们是否可以用一个map,把字符串对应的方法联系起来?

map<string,方法类型> opFuncMap = {{”+“,加法运算},{”-“,减法运算}…}

使用的时候就可以:

opFuncMap[str](a,b);

但是问题来了,如果方法用一个lambda表达式来写,那模板参数的类型接收什么?

有四种运算,也就是个lambda表达式,对应四个类型,有没有一个类型能接收这四种lambda表达式呢?

那就是包装器

包装方法:

map<string, function<int(int, int)>> opFuncMap =
{
{“+”,[](long long x,long long y) {return x + y; }},
{“-”,[](long long x,long long y) {return x - y; }},
{“*”,[](long long x,long long y) {return x * y; }},
{“/”,[](long long x,long long y) {return x / y; }}
};

使用方法:

st.push(stoi(str));

多少又有点多态的味道了,虽然说这个语法长相越来越不像C++了,但是用着爽啊

题解:

class Solution {
public:
    long long evalRPN(vector<string>& tokens) {
        stack<long long> st;
        map<string, function<int(int, int)>> opFuncMap =
        {
            {"+",[](long long x,long long y) {return x + y; }},
            {"-",[](long long x,long long y) {return x - y; }},
            {"*",[](long long x,long long y) {return x * y; }},
            {"/",[](long long x,long long y) {return x / y; }}
        };
        for (auto& str : tokens)
        {
            if (opFuncMap.count(str))//是操作符
            {
                long long right = st.top();
                st.pop();
                long long left = st.top();
                st.pop();
                st.push(opFuncMap[str](left, right));
            }
            else//是操作数
            {
                st.push(stoi(str));
            }
        }
        return st.top();
    }
};

虽然说代码没变多短,但是如果学会了map、包装器、lambda表达式,下面这段代码的封装性更强,可读性是更高;

相比C语言的面向过程,C++11新增的的这些语法把C++这门语言的面向对象更深化了几分。

bind

情景

我们看如下这种情况

#include <functional>
#include <map>
int f(int a, int b)
{
	return a + b;
}
struct Functor
{
public:
	int operator() (int a, int b)
	{
		return a + b;
	}
};
class Plus
{
public:
	double plusd(double a, double b)
	{
		return (a + b);
	}
private:
};
int main()
{
	map<string, std::function<int(int, int)>> opFuncMap = {
		{"普通函数指针",f},
		{"函数对象",Functor()},
		//{"成员函数指针", &Plus::plusd}//无法匹配
	};
	return 0;
}

我们想用一个map存储各种可调用类型,

当存到成员函数的时候出现了问题

因为成员函数的参数默认是存在一个this指针的,所以包装器接收的时候就得为这个this指针留位置:

std::function<double(Plus, double, double)> func = &Plus::plusd;

于是就出现了,明明是同一类调用方式,却不能用一个function接收

所以,有没有办法吧那个this指针隐藏掉呢?

概念

image-20221208015709243

std::bind函数是一个函数模板

它就像一个函数包装器(适配器),接受一个可调用对象(callable object),通过将某些参数写死,或者调换参数的顺序生成一个新的可调用对象

函数原型:

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

使用方法:

int f_src(int a, int b, int c)
{
    return a * (b - c);
}
int main()
{
    cout << f_src(10, 1, 2) << endl; // 10 * (1 - 2) = -30
    function<int(int, int)> f_dst = bind(f_src, 10, placeholders::_2, placeholders::_1);
    cout << f_dst(2, 1) << endl;     // 10 * (1 - 2) = -30
    return 0;
}

运行结果:

-30

-30

如上,bind的参数:

  • 第一个参数:需要被绑定修改的调用对象(传入函数名)
  • 后面再输入n个参数,n是被绑定函数的参数个数

上面调用f_dst(2, 1);时,参数按照如下方式传递:

  • 2 --> placeholders::_1 --> c
  • 1 --> placeholders::_2 --> b
  • ​ 10 --> a

相当于调用f_src(10, 1, 2);

其中 placeholders::_1, placeholders::_2(_1和_2属于placeholders命名空间域)相当于两个占位参数

如果需要写死的参数是this指针,可以传入一个匿名对象、非匿名对象的指针或者非匿名对象

此时我们就可以通过写死this指针,把绑定后的对象赋给map的function了:

#include <functional>
#include <map>
int f(int a, int b)
{
	return a + b;
}
struct Functor
{
public:
	int operator() (int a, int b)
	{
		return a + b;
	}
};
class Plus
{
public:
	double plusd(double a, double b)
	{
		return (a + b);
	}
private:
};
int main()
{
	map<string, std::function<int(int, int)>> opFuncMap = {
		{"普通函数指针",f},
		{"函数对象",Functor()},
		{"成员函数指针", bind(&Plus::plusd/*函数指针*/, Plus()/*匿名对象*/, placeholders::_1, placeholders::_2)}//绑定返回一个两个参数的调用对象
	};
	return 0;
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++中的std::bind是一个函数模板,用于创建一个可调用对象(函数对象或成员函数指针),并绑定其参数。它可以将一个函数或成员函数与一组参数绑定在一起,形成一个新的可调用对象。 std::bind的基本语法如下: ```cpp std::bind(Function, args...) ``` 其中,Function是要绑定的函数或成员函数指针,args是要绑定的参数。 使用std::bind可以实现以下功能: 1. 绑定普通函数的部分参数,生成一个新的可调用对象。 2. 绑定成员函数的对象指针和部分参数,生成一个新的可调用对象。 3. 绑定成员函数的对象引用和部分参数,生成一个新的可调用对象。 下面是一些示例: 1. 绑定普通函数的部分参数: ```cpp #include <iostream> #include <functional> void printSum(int a, int b) { std::cout << "Sum: " << a + b << std::endl; } int main() { auto printSumWith5 = std::bind(printSum, 5, std::placeholders::_1); printSumWith5(10); // 输出:Sum: 15 return 0; } ``` 2. 绑定成员函数的对象指针和部分参数: ```cpp #include <iostream> #include <functional> class MyClass { public: void printProduct(int a, int b) { std::cout << "Product: " << a * b << std::endl; } }; int main() { MyClass obj; auto printProductWith2 = std::bind(&MyClass::printProduct, &obj, std::placeholders::_1); printProductWith2(5); // 输出:Product: 10 return 0; } ``` 3. 绑定成员函数的对象引用和部分参数: ```cpp #include <iostream> #include <functional> class MyClass { public: void printDifference(int a, int b) { std::cout << "Difference: " << a - b << std::endl; } }; int main() { MyClass obj; auto printDifferenceWith3 = std::bind(&MyClass::printDifference, std::ref(obj), std::placeholders::_1); printDifferenceWith3(7); // 输出:Difference: 4 return 0; } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值