C++标准库中的 <functional>
库是一个强大的工具集,它提供了用于处理函数对象、函数绑定、函数包装等功能的设施,极大地增强了代码的灵活性和可复用性。
1. 函数对象(Functors)
函数对象,也被称作仿函数,是重载了 ()
运算符的类的实例。其使用方式与普通函数调用类似,但函数对象可以携带状态,这使得它比普通函数更加灵活。
#include <iostream>// 定义一个函数对象类
class Adder {
public:int operator()(int a, int b) const {return a + b;}
};int main() {Adder adder;int result = adder(3, 4);std::cout << "3 + 4 = " << result << std::endl;return 0;
}
- 原理:在
Adder
类中,重载了()
运算符,该运算符接受两个int
类型的参数并返回它们的和。Adder
类的对象adder
就可以像函数一样被调用,当调用adder(3, 4)
时,实际上是调用了Adder
类的operator()
方法。 - 优点:函数对象可以包含成员变量,这些成员变量可以在对象创建时进行初始化,并且在多次调用
operator()
时保持状态,这是普通函数无法做到的。
2. std::function
std::function
是一个通用的多态函数包装器,它可以存储、复制和调用任何可调用对象,包括普通函数、函数指针、成员函数指针、函数对象和 lambda 表达式等。
#include <iostream>
#include <functional>// 普通函数
int add(int a, int b) {return a + b;
}int main() {// 使用 std::function 包装普通函数std::function<int(int, int)> func = add;int result = func(3, 4);std::cout << "3 + 4 = " << result << std::endl;// 包装 lambda 表达式std::function<int(int, int)> lambdaFunc = [](int a, int b) { return a * b; };result = lambdaFunc(3, 4);std::cout << "3 * 4 = " << result << std::endl;return 0;
}
- 模板参数:
std::function<int(int, int)>
中的模板参数指定了可调用对象的签名,即接受两个int
类型的参数并返回一个int
类型的结果。 - 包装过程:当将普通函数
add
或 lambda 表达式赋值给std::function
对象时,std::function
会内部存储该可调用对象,并提供统一的调用接口。当调用func(3, 4)
或lambdaFunc(3, 4)
时,std::function
会根据存储的可调用对象进行相应的调用。 - 用途:
std::function
常用于实现回调机制,例如在事件处理、多线程编程等场景中,通过std::function
可以方便地传递和调用不同类型的可调用对象。
3. std::bind
std::bind
用于创建一个新的可调用对象,它将一个可调用对象和其部分或全部参数绑定在一起,允许进行部分参数绑定和参数重排。
#include <iostream>
#include <functional>// 普通函数
int add(int a, int b) {return a + b;
}int main() {// 使用 std::bind 绑定 add 函数的第一个参数为 3auto add3 = std::bind(add, 3, std::placeholders::_1);int result = add3(4);std::cout << "3 + 4 = " << result << std::endl;// 参数重排auto sub = [](int a, int b) { return a - b; };auto reversedSub = std::bind(sub, std::placeholders::_2, std::placeholders::_1);result = reversedSub(3, 4);std::cout << "4 - 3 = " << result << std::endl;return 0;
}
- 部分参数绑定:在
std::bind(add, 3, std::placeholders::_1)
中,add
是要绑定的可调用对象,3
是第一个参数,std::placeholders::_1
是一个占位符,表示新可调用对象的第一个参数。add3
是一个新的可调用对象,当调用add3(4)
时,实际上是调用add(3, 4)
。 - 参数重排:在
std::bind(sub, std::placeholders::_2, std::placeholders::_1)
中,通过占位符的顺序交换了sub
函数的参数顺序。当调用reversedSub(3, 4)
时,实际上是调用sub(4, 3)
。 - 用途:
std::bind
可以用于简化函数调用,特别是在需要固定部分参数的情况下,或者在需要对参数顺序进行调整的情况下。
4. std::mem_fn
std::mem_fn
用于创建一个可调用对象,该对象可以调用类的成员函数。它可以处理成员函数指针,使得调用成员函数更加方便。
#include <iostream>
#include <functional>class MyClass {
public:void print() const {std::cout << "Hello, World!" << std::endl;}int add(int a, int b) const {return a + b;}
};int main() {MyClass obj;// 使用 std::mem_fn 包装成员函数auto printFunc = std::mem_fn(&MyClass::print);printFunc(obj);auto addFunc = std::mem_fn(&MyClass::add);int result = addFunc(obj, 3, 4);std::cout << "3 + 4 = " << result << std::endl;return 0;
}
- 成员函数指针:
&MyClass::print
和&MyClass::add
是成员函数指针,它们指向MyClass
类的print
和add
成员函数。 - 包装过程:
std::mem_fn
接受成员函数指针作为参数,返回一个可调用对象。当调用printFunc(obj)
时,std::mem_fn
会将obj
作为调用print
成员函数的对象,从而实现成员函数的调用。同样,addFunc(obj, 3, 4)
会调用obj
的add
成员函数,并传递3
和4
作为参数。 - 用途:
std::mem_fn
使得成员函数的调用可以像普通函数调用一样方便,特别是在需要将成员函数作为参数传递给其他函数时非常有用。
5. 预定义的函数对象
<functional>
库提供了一系列预定义的函数对象,用于常见的操作,如算术运算、比较运算等。
算术运算
#include <iostream>
#include <functional>int main() {std::plus<int> add;int result = add(3, 4);std::cout << "3 + 4 = " << result << std::endl;std::minus<int> sub;result = sub(4, 3);std::cout << "4 - 3 = " << result << std::endl;return 0;
}
std::plus
:std::plus<int>
是一个预定义的函数对象,它重载了()
运算符,用于执行两个int
类型数的加法运算。当调用add(3, 4)
时,实际上是调用了std::plus<int>
的operator()
方法,返回3 + 4
的结果。std::minus
:std::minus<int>
用于执行两个int
类型数的减法运算,其原理与std::plus
类似。
比较运算
#include <iostream>
#include <functional>int main() {std::greater<int> greater;bool isGreater = greater(4, 3);std::cout << "4 > 3: " << (isGreater ? "true" : "false") << std::endl;std::less<int> less;isGreater = less(4, 3);std::cout << "4 < 3: " << (isGreater ? "true" : "false") << std::endl;return 0;
}
std::greater
:std::greater<int>
用于比较两个int
类型数的大小,返回true
表示第一个参数大于第二个参数。当调用greater(4, 3)
时,返回true
。std::less
:std::less<int>
用于比较两个int
类型数的大小,返回true
表示第一个参数小于第二个参数。当调用less(4, 3)
时,返回false
。
6. std::ref
和 std::cref
std::ref
和 std::cref
用于创建引用包装器,当需要将引用传递给 std::bind
或其他需要按值传递参数的地方时非常有用。
#include <iostream>
#include <functional>void increment(int& num) {++num;
}int main() {int num = 3;auto func = std::bind(increment, std::ref(num));func();std::cout << "num = " << num << std::endl;return 0;
}
std::ref
:std::ref(num)
创建了一个对num
的引用包装器。在std::bind(increment, std::ref(num))
中,由于increment
函数接受一个引用参数,使用std::ref
可以确保传递给increment
的是num
的引用,而不是num
的副本。当调用func()
时,increment
函数会修改num
的值。std::cref
:std::cref
与std::ref
类似,但它创建的是一个常量引用包装器,用于传递常量引用。
7. std::hash
std::hash
是一个模板类,用于生成对象的哈希值,常用于实现哈希表等数据结构。
#include <iostream>
#include <functional>int main() {std::hash<int> hasher;int num = 3;std::size_t hashValue = hasher(num);std::cout << "Hash value of " << num << " is " << hashValue << std::endl;return 0;
}
- 哈希函数:
std::hash<int>
是一个针对int
类型的哈希函数模板类,它重载了()
运算符,接受一个int
类型的参数并返回一个std::size_t
类型的哈希值。当调用hasher(num)
时,会根据num
的值生成一个对应的哈希值。 - 用途:在实现哈希表、哈希集合等数据结构时,
std::hash
可以方便地为元素生成哈希值,从而实现快速的查找和插入操作。
8. std::not_fn
(C++17 起)
std::not_fn
用于创建一个新的可调用对象,该对象返回原可调用对象结果的逻辑非。
#include <iostream>
#include <functional>
#include <algorithm>
#include <vector>bool isEven(int num) {return num % 2 == 0;
}int main() {std::vector<int> numbers = {1, 2, 3, 4, 5};auto isOdd = std::not_fn(isEven);auto oddCount = std::count_if(numbers.begin(), numbers.end(), isOdd);std::cout << "Number of odd numbers: " << oddCount << std::endl;return 0;
}
- 逻辑非操作:
std::not_fn(isEven)
创建了一个新的可调用对象isOdd
,当调用isOdd
时,它会调用isEven
并返回其结果的逻辑非。在std::count_if
中使用isOdd
可以统计numbers
向量中奇数的个数。 - 用途:
std::not_fn
可以方便地反转一个布尔函数的逻辑,避免手动编写反转逻辑的代码。
总结
<functional>
库提供了丰富的工具来处理函数对象和函数调用,通过这些工具可以提高代码的灵活性和可维护性。合理使用这些工具可以让代码更加简洁和高效,特别是在实现回调机制、函数绑定、成员函数调用等场景中发挥重要作用。