很高兴再次与大家分享关于 C++11 的一些知识。在上一篇文章中,我们讲解了 condition_variable 的使用方法。今天,我们将继续探讨 C++11 中的两个重要概念:function 包装器和 bind() 函数。这两个概念在 C++11 中具有非常重要的作用,它们可以帮助我们更好地管理函数指针和函数对象,并且可以大大提高代码的可读性和可维护性。在接下来的这篇文章中,我们将深入探讨这两个概念的使用方法和注意事项。
当我们在C++中使用函数指针或函数对象时,经常会遇到一些灵活性和可复用性的问题。C++11引入了一个强大的工具——function包装器,它可以解决这些问题。
function包装器是一个通用的函数封装器,可以容纳各种可调用对象(如函数指针、函数对象、成员函数指针等),并提供统一的接口来调用这些可调用对象。通过使用function包装器,我们可以将不同类型的可调用对象存储在一个容器中,方便统一管理和调用。
function包装器的使用非常简单。首先,我们需要包含头文件<functional>
。然后,我们可以使用std::function
模板类来定义一个function对象。例如:
#include <functional>
std::function<int(int)> func;
上述代码定义了一个名为func的function对象,它可以接受一个int类型的参数,并返回一个int类型的值。我们可以将各种可调用对象赋值给func,例如函数指针、函数对象、lambda表达式等。例如:
int myFunction(int x) {
return x * 2;
}
struct MyFunctor {
int operator()(int x) {
return x + 3;
}
};
func = myFunction; // 函数指针
int result1 = func(2); // 调用myFunction,结果为4
MyFunctor functor;
func = functor; // 函数对象
int result2 = func(2); // 调用functor,结果为5
func = [](int x) { return x * x; }; // lambda表达式
int result3 = func(2); // 调用lambda表达式,结果为4
通过使用function包装器,我们可以将不同类型的可调用对象统一存储并调用,极大地提高了代码的灵活性和可复用性。此外,function还提供了其他功能,如判空、交换等。
需要注意的是,function包装器的使用可能会引入一些性能开销,因为它需要进行动态分派。在性能要求较高的场景中,可以考虑使用函数指针或模板来代替function包装器。
接下来我们来讲一道题,相信通过这个OJ题目可以让大家更加了解function包装器。首先我先简单介绍一下这个题目:逆波兰表示法(Reverse Polish Notation, RPN)是一种数学表达式的书写方式,它通过将操作符放在两个操作数之后来表示一个算术表达式,从而避免了使用括号。
🔴题目链接
如果当前元素是操作符(‘+’、‘-’、‘*’ 或 ‘/’):
如果当前元素是操作数(整数):
遍历结束后,栈中只会剩下一个元素,即为最终的计算结果。
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<long long> st; // 创建一个存储操作数的栈,使用 long long 类型以避免溢出问题
for(auto& str : tokens) // 遍历表达式中的每个元素
{
if(str == "+" || str == "-" || str == "*" || str == "/") // 当前元素为操作符
{
long long right = st.top(); // 取出栈顶的操作数作为右操作数
st.pop(); // 弹出栈顶元素
long long 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(stoll(str)); // 将字符串转换为 long long 类型并压入栈中
}
}
return st.top(); // 返回栈顶元素,即最终的计算结果
}
};
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> st; // 创建一个存储操作数的栈
map<string, function<int(int, int)>> opFuncMap = // 创建一个从操作符到对应计算函数的映射表
{
{ "+", [](int i, int j){return i + j; } }, // lambda 函数实现加法计算
{ "-", [](int i, int j){return i - j; } }, // lambda 函数实现减法计算
{ "*", [](int i, int j){return i * j; } }, // lambda 函数实现乘法计算
{ "/", [](int i, int j){return i / j; } } // lambda 函数实现除法计算
};
for(auto& str : tokens) // 遍历表达式中的每个元素
{
if(opFuncMap.find(str) != opFuncMap.end()) // 当前元素为操作符
{
int right = st.top(); // 取出栈顶的操作数作为右操作数
st.pop(); // 弹出栈顶元素
int left = st.top(); // 取出新的栈顶元素作为左操作数
st.pop(); // 弹出栈顶元素
// 根据操作符进行计算,并将结果压入栈中
st.push(opFuncMap[str](left, right));
}
else // 当前元素为操作数
{
st.push(stoi(str)); // 将字符串转换为整数并压入栈中
}
}
return st.top(); // 返回栈顶元素,即最终的计算结果
}
};
std::bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。一般而言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M可以大于N,但这么做没什么意义)参数的新函数。同时,使用std::bind函数还可以实现参数顺序调整等操作。
头文件
bind()
函数位于 <functional>
头文件中,因此在使用该函数之前需要包含该头文件。
函数原型
bind()
函数的原型如下所示:
template <class Fn, class... Args>
bind(Fn&& fn, Args&&... args);
参数解释
Fn
:表示函数类型或函数对象类型。Args
:表示参数类型。fn
:要进行绑定的函数或函数对象。args
:要绑定的参数。返回值
bind()
函数返回一个新的可调用对象,该对象可以像原始函数一样被调用,但会自动传递已绑定的参数给 fn
。
使用示例
下面是一个使用 bind()
函数的示例代码:
#include <functional>
#include <iostream>
using namespace std;
// 定义一个普通函数 Plus,接收两个 int 类型参数,返回它们的和
int Plus(int a, int b)
{
return a + b;
}
// 定义一个类 Sub,包含一个成员函数 sub,接收两个 int 类型参数,返回它们的差
class Sub
{
public:
int sub(int a, int b)
{
return a - b;
}
};
int main()
{
// 绑定全局函数 Plus 到一个 std::function 对象 func1 上,并使用 placeholders 占位符表示待绑定的参数。
// 将第一个和第二个参数分别绑定到调用 func1 时传递的第一个和第二个参数上。
std::function<int(int, int)> func1 = std::bind(Plus, placeholders::_1, placeholders::_2);
// 使用 auto 定义变量 func2,将全局函数 Plus 绑定到它上面,并将第一个参数和第二个参数分别指定为 1 和 2。
// 因为 Plus 函数已经被绑定,这里不需要再使用 placeholders 占位符了。
auto func2 = std::bind(Plus, 1, 2);
// 创建一个 Sub 对象 s,将它的成员函数 sub 绑定到 std::function 对象 func3 上。
// 使用 placeholders 占位符表示待绑定的参数。将第一个和第二个参数分别绑定到调用 func3 时传递的第一个和第二个参数上。
Sub s;
std::function<int(int, int)> func3 = std::bind(&Sub::sub, s, placeholders::_1, placeholders::_2);
// 创建另一个 std::function 对象 func4,将它绑定到 s 的成员函数 sub 上。
// 使用 placeholders 占位符表示待绑定的参数。这里将第一个参数和第二个参数分别绑定到调用 func4 时传递的第二个和第一个参数上。
std::function<int(int, int)> func4 = std::bind(&Sub::sub, s, placeholders::_2, placeholders::_1);
// 分别输出各个函数对象的返回值
cout << func1(1, 2) << endl; // 输出 3,即 Plus(1, 2) 的结果
cout << func2() << endl; // 输出 3,即 Plus(1, 2) 的结果
cout << func3(1, 2) << endl; // 输出 -1,即 s.sub(1, 2) 的结果
cout << func4(1, 2) << endl; // 输出 1,即 s.sub(2, 1) 的结果
return 0;
}
这段代码演示了如何使用 std::bind
函数将函数对象和成员函数绑定到 std::function
对象上,并使用占位符 std::placeholders::_1
和 std::placeholders::_2
来表示待绑定的参数。
Plus
,它接收两个 int
类型的参数并返回它们的和。Sub
,其中包含一个成员函数 sub
,它接收两个 int
类型的参数并返回它们的差。
main
函数中,我们首先用 std::bind
将全局函数 Plus
绑定到 std::function
对象 func1
上,使用占位符 std::placeholders::_1
和 std::placeholders::_2
分别表示第一个和第二个参数。这样,我们可以使用 func1
来调用 Plus
函数,并传递实际的参数。auto
关键字定义了一个变量 func2
,将全局函数 Plus
绑定到它上面,并指定第一个参数为 1
,第二个参数为 2
。因为在这种情况下不需要使用占位符,所以我们直接指定了参数的值。Sub
类的对象 s
,并将其成员函数 sub
绑定到 std::function
对象 func3
上,使用占位符 std::placeholders::_1
和 std::placeholders::_2
分别表示第一个和第二个参数。这样,我们可以通过 func3
调用 s.sub
函数,并传递实际的参数。std::function
对象 func4
,将其绑定到 s
的成员函数 sub
上,并使用占位符 std::placeholders::_2
和 std::placeholders::_1
分别表示第一个和第二个参数。这样,我们可以通过 func4
调用 s.sub
函数,并以不同的顺序传递实际的参数。感谢您对博主文章的关注与支持!另外,我计划在未来的更新中持续探讨与本文相关的内容,会为您带来更多关于C++以及编程技术问题的深入解析、应用案例和趣味玩法等。请继续关注博主的更新,不要错过任何精彩内容!
再次感谢您的支持和关注。期待与您建立更紧密的互动,共同探索C++、算法和编程的奥秘。祝您生活愉快,排便顺畅!