std::function
std::function
是一个类型安全的通用函数包装器。它可以存储、复制和调用任何可以调用的目标(如普通函数、Lambda表达式、函数指针、成员函数指针等),std::function
对象可以通过 operator()
来调用所存储的可调用对象。这使得std::function
在需要使用回调函数或将函数作为参数传递时非常有用。
语法规则
声明
要使用 std::function
,首先需要包含 <functional>
头文件。然后,可以声明一个 std::function
对象,指定其可调用对象的签名(即返回类型和参数列表)。
std::function<返回类型(参数列表)> functionName = 可调用对象;
例如,一个接受两个 int
参数并返回一个 int
值的可调用对象可以这样声明:
std::function<int(int, int)> func;
然后可以使用 std::function
的构造函数来初始化。
赋值
你可以将函数、lambda 表达式或其他可调用对象赋值给 std::function
对象。例如:
// 赋值一个lambda表达式
func = [](int x, int y) { return x + y; };
// 调用
int result = func(2, 3); // result 为 5
std::function
可以赋值给相同签名的另一个 std::function
对象。
void print(int val)
{
std::cout << val << std::endl;
}
// 声明一个 std::function 对象
std::function<void(int)> f = print;
std::function<void(int)> h;
h = f; // 合法赋值
std::function<void(int, double)> j;
j = h; // 编译错误,类型不匹配
调用
std::function
对象通过 operator()
来调用所存储的可调用对象。与普通函数调用的使用一样。
示例
#include <iostream>
#include <functional>
// 普通函数
void print_hello()
{
std::cout << "Hello, World!" << std::endl;
}
// Lambda 表达式
auto print_world = []()
{
std::cout << "Hello, World from Lambda!" << std::endl;
};
// 函数对象
struct PrintMessage
{
void operator()() const
{
std::cout << "Hello from function object!" << std::endl;
}
};
int main()
{
// 存储普通函数
std::function<void()> func1 = print_hello;
func1();
// 存储 Lambda 表达式
std::function<void()> func2 = print_world;
func2();
// 存储函数对象
std::function<void()> func3 = PrintMessage();
func3();
return 0;
}
使用场景
- 回调函数: 在图形用户界面程序或网络编程中,经常需要定义会调用函数。
- 事件处理: 在观察者模式中,可以用
std::function
存储和调用事件处理函数。 - 作为函数参数和返回值: 方便传递函数或存储函数以在其他地方调用。
应用一:回调函数
在图形用户界面(GUI)程序或网络编程中,回调函数是非常常见的。它们允许程序在特定事件发生时(如用户点击按钮或数据接收完毕)执行特定的代码。
GUI 回调函数示例(假设我们有一个简化的 GUI 框架):
#include <iostream>
#include <functional>
// 假设的按钮类
class Button
{
public:
void setOnClickListener(std::function<void()> listener)
{
onClick = listener;
}
void click()
{
if (onClick)
{
onClick();
}
}
private:
std::function<void()> onClick;
};
void onButtonClick()
{
std::cout << "Button was clicked!" << std::endl;
}
int main()
{
Button button;
button.setOnClickListener(onButtonClick);
button.click(); // 输出: Button was clicked!
return 0;
}
在这个例子中,Button
类有一个 setOnClickListener
方法,它接受一个 std::function<void()>
类型的回调函数。当用户点击按钮时(在这里模拟为调用 click
方法),回调函数会被调用。
应用二:事件处理
在观察者模式中,std::function
可以用来存储和调用事件处理函数。当某个事件发生时,所有注册的事件处理函数都会被调用。
#include <iostream>
#include <vector>
#include <functional>
class EventSource
{
public:
void registerObserver(std::function<void()> observer)
{
observers.push_back(observer);
}
void notifyObservers()
{
for (auto& observer : observers)
{
observer();
}
}
private:
std::vector<std::function<void()>> observers;
};
void handleEvent1()
{
std::cout << "Event 1 handled." << std::endl;
}
void handleEvent2()
{
std::cout << "Event 2 handled." << std::endl;
}
int main()
{
EventSource eventSource;
eventSource.registerObserver(handleEvent1);
eventSource.registerObserver(handleEvent2);
eventSource.notifyObservers(); // 输出: Event 1 handled. Event 2 handled.
return 0;
}
在这个例子中,EventSource
类维护了一个 std::vector<std::function<void()>>
类型的观察者列表。当调用 notifyObservers
方法时,所有注册的观察者都会被调用。
应用三:作为函数参数和返回值
将函数作为参数传递或作为返回值返回可以增加代码的灵活性和可重用性。std::function
使得这种操作变得简单且类型安全。
#include <iostream>
#include <functional>
// 一个接受函数作为参数并调用它的函数
void processFunction(std::function<void()> func)
{
func();
}
// 一个返回函数的函数
std::function<void()> createGreetingFunction(const std::string& name)
{
return [name]() {
std::cout << "Hello, " << name << "!" << std::endl;
};
}
int main()
{
// 将函数作为参数传递
processFunction([]() { std::cout << "Function passed as argument." << std::endl; });
// 获取并调用返回的函数
auto greetAlice = createGreetingFunction("Alice");
greetAlice(); // 输出: Hello, Alice!
return 0;
}
在这个例子中,processFunction
接受一个 std::function<void()>
类型的参数并调用它。createGreetingFunction
返回一个 std::function<void()>
类型的函数对象,该函数对象捕获了一个字符串参数并在调用时打印出来。
应用四:包装类成员函数
std::function
能包装类成员函数:静态成员函数和非静态成员函数
#include <iostream>
#include <functional>
class Puls
{
public:
static int plus_i(int a, int b)
{
return a + b;
}
double plus_d(double a, double b)
{
return a + b;
}
};
int main()
{
// 包装类静态成员函数
std::function<int(int, int)>func2 = Puls::plus_i;
std::cout << "plus_i = " << func2(1, 2) << std::endl;
// 包装类非静态成员函数
// 第一种写法:对象地址
Plus p;
std::function<double(Plus*, double, double)>func3 = &Plus::plus_d;
std::cout << "plus_d = " << func3(&p, 1.5, 2.3) << std::endl;
// 第二种写法:匿名对象
std::function<double(Plus, double, double)>func3 = &Plus::plus_d;
std::cout << "plus_d = " << func3(Plus(), 1.5, 2.3) << std::endl;
return 0;
}
- 如果要包装类非静态成员函数,需要对象指针或者对象去调用。因为要取成员函数的地址,需要在前面加上一个
&
。 - 还要注意的是,非静态成员函数还有一个隐含的参数
this
,在调用传递参数时也需要传入对象。
不过这两种写法,还是不够优雅~ 因为我们在调用某一个函数时,只想传递在参数列表中暴露出来的参数,而不传递隐含的参数 this
。
为了达到这种效果,就需要使用 std::bind
改变函数调用参数的个数(具体用法下面讲解):
int main()
{
// 包装类非静态成员函数
std::function<double(double, double)>func4 = std::bind(&Plus::plus_d, Plus(), \
std::placeholders::_1, std::placeholders::_2) ;
std::cout << "plus_d = " << func4(1.5, 2.3) << std::endl;
return 0;
}
实例:逆波兰表达式求值
这里有一个OJ题目,可以使用function
更简单的解决:150. 逆波兰表达式求值
这里原先思路是:判断波兰表达式每一个字符,如果遇到操作数入栈,如果遇到操作符,出栈两个操作数,使用switch case
语句匹配执行对应的运算,在将结果入栈:
if(e == "+" || e == "-" || e == "*" || e == "/")
{
int rightNum = st.top();
st.pop();
int leftNum = st.top();
st.pop();
switch(e[0])
{
case '+' : st.push(leftNum+rightNum);break;
case '-' : st.push(leftNum-rightNum);break;
case '*' : st.push(leftNum*rightNum);break;
case '/' : st.push(leftNum/rightNum);break;
}
}
else
{
// 操作数转化成整型后,入栈
st.push(stoi(e));
}
这里可以使用std::map
容器,将操作符和对应操作函数的映射关系保存下来,这样遇到一个操作符时,就执行对应的操作,不需要在进行switch case
判断了:
并且这里使用封装器对执行函数进行封装,而不是直接使用函数对象中的任一一个,是因为每一个对象都有各自的缺点:
- 函数指针:类型写起来比较复杂。
- 仿函数:不同仿函数的类型不同,一个仿函数一个类型,不能存放在同一个容器进行管理使用(即使参数和返回值相同)
- lambda表达式:语法层面没有类型
如果不使用
std::function
要使用map
容器管理加法、减法、乘法和除法执行的函数时,需要传递类型,而这里只有函数指针符合条件:
- 如果使用仿函数,4个运算符对应4个函数,就要写4个仿函数,每一个仿函数的类型不一致,无法传递给
map
的模板参数。 - lambda表达式没有类型,更无法进行传递。
- 如果使用函数指针,就需要写出4个函数,并且其函数指针类型写起来也不方便(可以看下面代码感受一下)。
// 定义基本运算函数
int add(int x, int y) { return x + y; }
int subtract(int x, int y) { return x - y; }
int multiply(int x, int y) { return x * y; }
int divide(int x, int y) { return x / y; }
std::map<std::string, int (*)(int, int)> opFuncMap = {
{"+", add},
{"-", subtract},
{"*", multiply},
{"/", divide}
};
使用
std::function
之后,可以统一类型
也就是说,在map
模板使用function
之后,在初始化时,不仅可以使用函数指针进行初始化,还可以使用仿函数和lambda表达式进行初始化。这时,这三种方式哪个写起来比较方便,就可以选择写哪个:
int subtract(int x, int y) { return x - y; }
struct Divide {
int operator()(int x, int y) const { return x / y; }
};
class Solution {
public:
int evalRPN(vector<string>& tokens) {
// 使用map容器和function,将操作符和其操作映射起来
std::map<std::string, std::function<int (int, int)>> opFuncMap = {
{"+", [](int x, int y) { return x + y ;}},
{"-", subtract}, // 这里使用函数指针
{"*", [](int x, int y) { return x * y ;}},
{"/", Divide() } // 这里使用仿函数
};
stack<int> st;
for(auto& e: tokens)
{
// 如果是操作符
if(opFuncMap.count(e))
{
int rightNum = st.top();
st.pop();
int leftNum = st.top();
st.pop();
st.push(opFuncMap[e](leftNum, rightNum));
}
else // 如果是操作数
{
// 操作数转化成整型后,入栈
st.push(stoi(e));
}
}
return st.top();
}
};
std::bind
std::bind
是一个函数适配器,用于将一个可调用对象(如函数、函数指针、Lambda表达式等)与其参数绑定在一起。它返回一个新的可调用对象,可以在稍后的时间调用。通过std::bind
,可以简化函数调用的过程,特别是当某些参数已经确定时。
语法规则
std::bind
头文件也在<functional>
中。
基本定义
#include <functional>
auto 新函数名 = std::bind(可调用对象, 参数1, 参数2, ..., std::placeholders::_1, ...);
std::placeholders
使用
std::placeholders
是 std::bind
提供的一个命名空间,它包含了一组占位符 _1, _2, _3, ...
,用于表示函数参数的位置。在绑定函数时,可以使用这些占位符来指定哪些参数将在稍后调用时被提供。
std::placeholders::_1
表示第一个参数,_2
表示第二个参数,以此类推。- 可以使用
std::placeholders
允许在新的可调用对象中保留某些参数,动态地在调用时传递。 std::placeholders
还可以更改传入参数的顺序。
auto pfunc = std::bind(func1, std::placeholders::_2, std::placeholders::_1); // 交换参数位置
pfunc(1, 2); // 输出: 2 1
示例:
#include <iostream>
#include <functional> // 包含 std::bind
void add(int a, int b)
{
std::cout << " a = " << a << ", b = " << b << std::endl;
std::cout << "Sum: " << a + b << std::endl;
}
int main() {
// 将函数 add 的第一个参数绑定为 10
auto boundAdd = std::bind(add, 10, std::placeholders::_1);
// 调用 boundAdd,只需要提供第二个参数
boundAdd(5); // 输出: Sum: 15
boundAdd(20); // 输出: Sum: 30
return 0;
}
运行结果:
a = 10, b = 5
Sum: 15
a = 10, b = 20
Sum: 30
使用场景
应用一:绑定普通函数
int add(int a, int b) {
return a + b;
}
auto boundAdd = std::bind(add, 5, std::placeholders::_1); // 绑定第一个参数为5
int result = boundAdd(3); // 等价于 add(5, 3),输出: 8
应用二:绑定Lambda 表达式
auto lambdaAdd = [](int a, int b) { return a + b; };
auto boundLambdaAdd = std::bind(lambdaAdd, 10, std::placeholders::_1); // 绑定第一个参数为10
int result = boundLambdaAdd(5); // 等价于 lambdaAdd(10, 5),输出: 15
应用三:绑定成员函数
当我们使用std::bind
来绑定类的成员函数时,首先要理解成员函数的调用方式。成员函数需要一个指向对象的指针(或引用)来调用,因此在绑定时必须提供该对象。
绑定成员函数的语法
std::bind
的基本语法如下:
std::bind(&ClassName::MemberFunction, &objectInstance, arg1, arg2, ...);
&ClassName::MemberFunction
:指定要绑定的成员函数。&objectInstance
:指定调用成员函数的对象实例,也可以使用匿名对象(ClassName()
)。arg1, arg2, ...
:绑定的参数,如果有的话。
示例代码
class MyClass
{
public:
void display(int x, int y)
{
std::cout << "X: " << x << ", Y: " << y << std::endl;
}
};
int main()
{
MyClass obj;
// 绑定 display 成员函数,将 y 固定为 20
auto boundDisplay = std::bind(&MyClass::display, &obj, std::placeholders::_1, 20);
// 调用绑定函数,只需提供 x 参数
boundDisplay(10); // 输出: X: 10, Y: 20
boundDisplay(30); // 输出: X: 30, Y: 20
return 0;
}
逐步解析示例
-
定义类和成员函数:定义了一个类
MyClass
,它包含一个名为display
的成员函数,该函数接受一个整数参数并输出该值。 -
创建实例:在
main
函数中,创建了MyClass
的一个实例obj
。 -
绑定成员函数:
- 使用
std::bind
绑定了display
成员函数,并将obj
作为调用对象。 std::placeholders::_1
用于占位,表示调用时会传入的第一个参数。
- 使用
-
调用绑定函数: 调用
boundDisplay
并传递参数boundDisplay(value)
,实际调用obj.display(value, 20)
。
注意事项
- 绑定常量成员函数: 如果要绑定一个
const
成员函数,可以用相同的方法,但对象实例需要是const
的。 - 指针和引用: 确保对象实例在调用期间有效,避免悬空指针或引用。
- 占位符: 确保使用
std::placeholders
来指明参数位置。
补充: 使用Lambda替代 std::bind
在现代C++中,使用Lambda表达式通常会更加直观和简洁。例如,上面的boundDisplay
可以用Lambda替代:
auto boundDisplay = [&](int x) { obj.display(x, 20); };
今天的分享就到这里了,如果,你感觉这篇博客对你有帮助的话,就点个赞吧!感谢感谢……