一、什么是可调用对象?
在 C++ 中,“可调用对象”指的是可以像函数一样被调用的对象。主要有以下几种:
| 类型 | 示例 |
|---|---|
| 函数指针 | void (*p)() |
| 仿函数 | 重载了operator()的类对象 |
| Lambda 表达式 | [](int x){ return x; } |
| 类成员函数指针 | &MyClass::func |
| std::function 包装的对象 | std::function<void()> f = func; |
二、函数指针
函数指针是最传统的可调用对象,本质是指向函数的指针。
#include <iostream> using namespace std; int add(int a, int b) { return a + b; } int sub(int a, int b) { return a - b; } int main() { // 定义函数指针:返回值 (*指针名)(参数类型) int (*pf)(int, int) = nullptr; // 指向 add 函数 pf = add; // 函数名就是地址,等价于 pf = &add; cout << pf(10, 20) << endl; // 30 // 指向 sub 函数 pf = sub; cout << pf(10, 20) << endl; // -10 return 0; }说明:函数名就是函数的地址,所以pf = add和pf = &add都可以。函数指针可以指向不同类型的函数,只要返回值类型和参数列表匹配。
三、仿函数
仿函数是一个重载了operator()的类或结构体,它的对象可以像函数一样调用。
class Add { private: int value; public: Add() : value(0) {} int operator()(int a, int b) { value = a + b; return value; } int operator()(int a) { value = a * a; return value; } void Print() const { cout << "value: " << value << endl; } }; int main() { Add add; int c = add(10, 20); // 调用 operator()(int, int) // 等价于 add.operator()(10, 20) add.Print(); // value: 30 c = add(5); // 调用 operator()(int) // 等价于 add.operator()(5) add.Print(); // value: 25 return 0; }为什么add(10, 20)能像函数一样调用?
因为Add类重载了operator(),add(10, 20)是语法糖,编译器会转换成add.operator()(10, 20)。这让对象可以像函数一样被调用,所以称为“仿函数”。
特点:
| 特点 | 说明 |
|---|---|
| 可以有多个重载版本 | 参数不同,调用时自动匹配 |
| 可以保存状态 | 有成员变量,多次调用可以记住之前的值 |
| 可以作为算法的参数 | 如sort的自定义比较函数 |
四、std::function 可调用对象包装器
问题:为什么需要 std::function?
不同类型的可调用对象,类型不一样:
| 可调用对象 | 类型 |
|---|---|
| 普通函数 | void(*)() |
| 仿函数 | Foo(自定义类型) |
| Lambda | 编译器生成的唯一类型(无法写出) |
void func() {} struct Foo { void operator()() {} }; auto lam = []() {};三个变量类型不同,无法放在同一个数组里
std::function 的作用:把不同类型的可调用对象,包装成统一的类型。
std::function<void()> f; // 统一类型 f = func; // 可以存普通函数 f = Foo(); // 可以存仿函数 f = [](){}; // 可以存 Lambda4.1 基本用法
#include <functional> #include <iostream> using namespace std; void func() { cout << "全局函数" << endl; } struct Foo { void operator()() { cout << "仿函数" << endl; } }; struct Bar { static void static_func() { cout << "静态成员函数" << endl; } }; int main() { // std::function<void(void)> 表示:包装一个返回 void、无参数的函数 std::function<void(void)> f; f = func; // 包装全局函数 f(); // 输出:全局函数 f = Bar::static_func; // 包装静态成员函数 f(); // 输出:静态成员函数 Foo foo; f = foo; // 包装仿函数 f(); // 输出:仿函数 return 0; }语法解释:std::function<void(void)>中的void(void)表示:
第一个
void:返回值类型括号内的
void:参数列表(无参数)
std::function<void(void)>表示:包装一个返回 void、无参数的函数
4.2 为什么需要 std::function?
场景:想把多个不同类型的可调用对象放进一个数组,统一调用。
std::function<void(void)> funcList[3]; funcList[0] = func; // 普通函数 funcList[1] = foo; // 仿函数 funcList[2] = []() { cout << "Lambda" << endl; }; // Lambda for (auto& f : funcList) { f(); // 统一调用 }没有 std::function 的话:三种类型不同,无法放在同一个数组里。
一句话总结
std::function是一个“万能容器”,可以把函数指针、仿函数、Lambda 等统一装进去,然后统一调用。
五、std::bind 绑定器
std::bind用于将函数的参数绑定到固定值,生成一个新的可调用对象。
5.1 基本用法
#include <functional> #include <iostream> using namespace std; void output(int a, int b) { cout << "a = " << a << ", b = " << b << endl; } int main() { int x = 10, y = 20; // 两个参数都固定 auto f1 = std::bind(output, x, y); f1(); // 输出:a = 10, b = 20 return 0; }说明:std::bind(output, x, y)把output的两个参数都固定为x和y,生成的新函数f1不需要再传参。
5.2 占位符(_1、_2)
占位符表示参数在调用时才传入。
#include <functional> using namespace std::placeholders; // _1, _2 在这个命名空间 int main() { int x = 10; // 第一个参数固定,第二个参数调用时传入 auto f2 = std::bind(output, x, _1); f2(12); // a = 10, b = 12 // 第一个参数固定,第二个参数用占位符 _2(只绑定第二个实参) auto f3 = std::bind(output, x, _2); f3(1, 2); // a = 10, b = 2(_2 取第二个参数 2) // 参数顺序交换 auto f4 = std::bind(output, _2, _1); f4(1, 2); // a = 2, b = 1(_2 取第二个参数,_1 取第一个参数) return 0; }说明:_1表示调用时的第一个参数,_2表示第二个参数。通过占位符可以固定部分参数,或重新排列参数顺序。
5.3 占位符说明
| 占位符 | 含义 |
|---|---|
_1 | 调用时的第一个参数 |
_2 | 调用时的第二个参数 |
_3 | 调用时的第三个参数 |
// bind 时定义参数顺序,调用时传入实参 auto f = std::bind(output, _2, _1, _3); f(10, 20, 30); // 实际调用 output(20, 10, 30)说明:占位符可以任意重排参数顺序,_1永远是调用时的第一个参数,_2是第二个,以此类推。
5.4 绑定成员函数
class Int { private: int value; public: Int(int x = 0) : value(x) {} void print() const { cout << "value = " << value << endl; } }; int main() { Int a(10); // 绑定成员函数需要传递对象指针 std::function<void(void)> f = std::bind(&Int::print, &a); f(); // 输出:value = 10 return 0; }说明:成员函数有一个隐式的this参数,绑定时需要传递对象指针(&a)。
5.5 bind 的作用:统一成无参函数
void output(int a, int b, int c) { cout << a << ", " << b << ", " << c << endl; } int main() { // 把三个参数都固定,变成无参函数 auto f = std::bind(output, 1, 2, 3); f(); // 输出:1, 2, 3 return 0; }核心优势:可以把任何带参数的函数转换成无参函数,方便存储到容器或作为回调函数。
六、完整示例
下面的代码综合演示了std::function和std::bind的用法。
#include <iostream> #include <functional> using namespace std; using namespace std::placeholders; // 1. 普通函数 int add(int a, int b) { return a + b; } // 2. 仿函数 struct Multiply { int operator()(int a, int b) const { return a * b; } }; // 3. 成员函数 class Calculator { public: int divide(int a, int b) const { return a / b; } }; int main() { // std::function 统一存储不同类型的可调用对象 std::function<int(int, int)> func; // 存储函数指针 func = add; cout << "add: " << func(10, 20) << endl; // 30 // 存储仿函数 Multiply mul; func = mul; cout << "multiply: " << func(10, 20) << endl; // 200 // 存储 Lambda func = [](int a, int b) { return a - b; }; cout << "lambda: " << func(10, 20) << endl; // -10 // std::bind 绑定成员函数 Calculator calc; func = std::bind(&Calculator::divide, &calc, _1, _2); cout << "divide: " << func(20, 10) << endl; // 2 // bind 固定部分参数 auto fixed = std::bind(add, 100, _1); cout << "fixed: " << fixed(50) << endl; // 150 return 0; }示例说明:
std::function<int(int, int)>定义了一个统一类型:接收两个int,返回int函数指针、仿函数、Lambda 都可以赋值给
funcstd::bind可以将成员函数或普通函数绑定成新的可调用对象
七、总结
| 知识点 | 核心要点 |
|---|---|
| 函数指针 | 指向函数的指针,int (*pf)(int, int) |
| 仿函数 | 重载operator()的类对象,add(10,20)实际是add.operator()(10,20) |
| 仿函数特点 | 可保存状态、可重载、可作算法参数 |
| std::function | 通用可调用对象包装器,统一存储函数指针、仿函数、Lambda |
| std::bind | 绑定函数参数,生成新可调用对象 |
| 占位符 | _1、_2、_3,表示调用时传入的参数位置 |
| 绑定成员函数 | 需要传递对象指针(&obj),因为有隐式this |
| bind 作用 | 把带参函数转成无参函数,方便存储到容器或作为回调 |
一句话总结:
std::function统一存储各类可调用对象,std::bind绑定参数生成新函数,配合占位符可重排参数顺序。仿函数通过operator()让对象像函数一样调用。