首页 > 编程笔记

C++函数对象详解

函数对象又叫仿函数(functor),简单理解,函数对象就是可以当做函数使用的类对象。

使用函数的标志就是在函数名称后面加上函数调用运算符“( )”,即一对括号,外加其中的函数参数。因此,如果一个对象能够当做函数使用,也必须能够在对象名后面加上括号和参数。要达到这样的目的,则必须为函数对象重载函数调用运算符。

因此,函数对象实际上是一个类或结构体,它重载了 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 对象并传递所需的数字即可。

推荐阅读