C++函数对象详解
函数对象又叫仿函数(functor),简单理解,函数对象就是可以当做函数使用的类对象。
使用函数的标志就是在函数名称后面加上函数调用运算符“( )”,即一对括号,外加其中的函数参数。因此,如果一个对象能够当做函数使用,也必须能够在对象名后面加上括号和参数。要达到这样的目的,则必须为函数对象重载函数调用运算符。
因此,函数对象实际上是一个类或结构体,它重载了 operator(),从而允许我们像像调用函数一样使用对象实例。
函数对象的语法格式如下:
运行结果为:
为了将函数设计得更加灵活,并能够尽量重用,通常可以通过函数的参数来指定函数的计算方法、目标、条件等,而该参数就是所谓的算法策略。配置函数的算法策略有如下三种方法:
如果使用数值方法,那么在设计算法时必须根据不同的数值做出相应的处理,这样做不够灵活。例如,设计一个查找函数 find_if,至于什么样的数据符合要求,则由调用者通过参数指定。假设使用数值方法来表示算法策略,则应当先定义一个表示各种策略的枚举体,然后在定义函数时为函数传入一个该枚举体的变量,以表示应用的策略,例如:
假设使用函数指针来表示算法策略,则应当在算法函数中传递一个函数指针,用以传递实现算法策略的函数,例如:
这种方法的优势是,如果我们想要定义新的策略,只需定义新的函数,而不需要修改 find_if() 函数。这使得代码更加灵活和可扩展。
但和函数对象相比,使用函数指针表达算法策略仍然有一定的局限性:
使用函数对象表示查找某个目标数据的算法如下:
通过使用函数对象作为策略,我们可以轻松地定义和扩展算法的策略。例如,如果我们想要查找其他的数字,只需创建一个新的 Finder 对象并传递所需的数字即可。
使用函数的标志就是在函数名称后面加上函数调用运算符“( )”,即一对括号,外加其中的函数参数。因此,如果一个对象能够当做函数使用,也必须能够在对象名后面加上括号和参数。要达到这样的目的,则必须为函数对象重载函数调用运算符。
因此,函数对象实际上是一个类或结构体,它重载了 operator(),从而允许我们像像调用函数一样使用对象实例。
函数对象的语法格式如下:
template <模板参数列表> class 类名 { public: 返回值类型 operator()(函数参数列表) { // 函数体 } // 其他成员函数和数据成员... };以下是一个简单的 C++ 示例:
#include <iostream> class Adder { public: int operator()(int a, int b) { return a + b; } }; int main() { Adder add; // 创建函数对象实例 int result = add(3, 4); // 使用函数对象 std::cout << "3 + 4 = " << result << std::endl; return 0; }示例中的 Adder 是一个函数对象,它接受两个整数参数,并返回它们的和。在 main 函数中,我们创建了这个函数对象的一个实例并调用它,就像它是一个函数一样。
运行结果为:
3 + 4 = 7
函数对象VS函数指针
设计函数对象的主要目的是用来配置算法的策略。所谓算法的策略,就是计算或处理数据的方法、目标、条件等。为了将函数设计得更加灵活,并能够尽量重用,通常可以通过函数的参数来指定函数的计算方法、目标、条件等,而该参数就是所谓的算法策略。配置函数的算法策略有如下三种方法:
- 使用数值;
- 使用函数指针;
- 使用函数对象;
如果使用数值方法,那么在设计算法时必须根据不同的数值做出相应的处理,这样做不够灵活。例如,设计一个查找函数 find_if,至于什么样的数据符合要求,则由调用者通过参数指定。假设使用数值方法来表示算法策略,则应当先定义一个表示各种策略的枚举体,然后在定义函数时为函数传入一个该枚举体的变量,以表示应用的策略,例如:
#include <iostream> #include <vector> // 定义策略枚举 enum Strategy { EVEN, // 查找第一个偶数 ODD, // 查找第一个奇数 GREATER_THAN_5 // 查找第一个大于5的数 }; int find_if(const std::vector<int>& vec, Strategy strategy) { for (int num : vec) { switch (strategy) { case EVEN: if (num % 2 == 0) return num; break; case ODD: if (num % 2 != 0) return num; break; case GREATER_THAN_5: if (num > 5) return num; break; default: break; } } return -1; // 表示未找到 } int main() { std::vector<int> numbers = {3, 4, 7, 8, 9}; std::cout << "First even number: " << find_if(numbers, EVEN) << std::endl; std::cout << "First odd number: " << find_if(numbers, ODD) << std::endl; std::cout << "First number greater than 5: " << find_if(numbers, GREATER_THAN_5) << std::endl; return 0; }示例中定义了一个 find_if() 函数,该函数根据传入的策略枚举值来查找满足特定条件的第一个元素。虽然这种方法在某些情况下可能会有用,但它不够灵活。如果要增加新的策略,就需要修改枚举和 find_if() 函数的内部逻辑。
假设使用函数指针来表示算法策略,则应当在算法函数中传递一个函数指针,用以传递实现算法策略的函数,例如:
#include <iostream> #include <vector> typedef bool (*StrategyFunc)(int); bool isEven(int num) { return num % 2 == 0; } bool isOdd(int num) { return num % 2 != 0; } bool isGreaterThanFive(int num) { return num > 5; } int find_if(const std::vector<int>& vec, StrategyFunc strategy) { for (int num : vec) { if (strategy(num)) { return num; } } return -1; // 表示未找到 } int main() { std::vector<int> numbers = {3, 4, 7, 8, 9}; std::cout << "First even number: " << find_if(numbers, isEven) << std::endl; std::cout << "First odd number: " << find_if(numbers, isOdd) << std::endl; std::cout << "First number greater than 5: " << find_if(numbers, isGreaterThanFive) << std::endl; return 0; }在上面的代码中,我们定义了三种策略函数:isEven()、isOdd() 和 isGreaterThanFive()。find_if() 函数接受一个函数指针作为策略,然后根据这个函数指针来查找满足条件的第一个元素。
这种方法的优势是,如果我们想要定义新的策略,只需定义新的函数,而不需要修改 find_if() 函数。这使得代码更加灵活和可扩展。
但和函数对象相比,使用函数指针表达算法策略仍然有一定的局限性:
- 不能使用内联函数,以进一步提高调用效率。
- 普通的函数或函数指针不能保存状态,而函数对象可以保存状态,因为它们是类的实例,可以有成员变量。
使用函数对象表示查找某个目标数据的算法如下:
#include <iostream> #include <vector> class Finder { private: int target; public: // 构造函数,设置要查找的目标数据 Finder(int t) : target(t) {} // 重载()操作符,使其可以被调用如同一个函数 bool operator()(int num) const { return num == target; } }; int find_if(const std::vector<int>& vec, const Finder& strategy) { for (int num : vec) { if (strategy(num)) { return num; } } return -1; // 表示未找到 } int main() { std::vector<int> numbers = {3, 4, 7, 8, 9}; // 创建一个函数对象,用于查找数字7 Finder findSeven(7); int result = find_if(numbers, findSeven); if (result != -1) { std::cout << "Found the number: " << result << std::endl; } else { std::cout << "Number not found." << std::endl; } return 0; }在上面的代码中,我们定义了一个名为 Finder 的类。这个类的对象可以像函数一样被调用,因为它重载了 operator()。
通过使用函数对象作为策略,我们可以轻松地定义和扩展算法的策略。例如,如果我们想要查找其他的数字,只需创建一个新的 Finder 对象并传递所需的数字即可。