函数指针与函数对象

一、函数指针是什么?

函数指针的定义:在 C++ 中就是指向函数的指针变量,类型为 返回值类型(*)(参数类型列表),它保存的是函数的地址。通过函数指针,你可以动态调用指针指向的函数,实现更灵活的代码,比如回调函数、事件处理等。

简单来说,函数指针的作用是让你可以把函数当作变量来传递和调用。

举个简单例子:

// 定义一个普通函数
int add(int a, int b) {
    return a + b;
}

int main() {
    // 定义一个函数指针,指向返回 int,参数是两个 int 的函数
    int (*funcPtr)(int, int) = add;

    // 通过函数指针调用函数
    int result = funcPtr(3, 4);
    std::cout << "Result: " << result << std::endl; // 输出 Result: 7

    return 0;
}

解释一下:

  • int (*funcPtr)(int, int):这是声明一个函数指针,指向返回 int 并且接收两个 int 参数的函数。
  • funcPtr = add;:把函数 add 的地址赋给函数指针。
  • funcPtr(3, 4);:通过函数指针调用函数。

二、函数对象是什么?

函数对象的定义:函数对象(也称为仿函数)是 C++ 中一种特殊的类,通过重载 operator()运算符,使得其实例可以像函数一样被调用(使用 对象名(参数)的语法)。

函数对象的特点:

  1. 可以携带状态​​:函数对象是类的实例,因此可以拥有成员变量,保存上下文信息(这是普通函数和 Lambda 无法直接具备的优势,尽管 Lambda 可以通过捕获列表模拟状态)。
  2. 行为可定制​​:通过不同的构造函数或成员变量,函数对象可以实现不同的行为。
  3. ​​类型安全​​:函数对象是具体的类型(由其类定义决定),而函数指针的类型由返回值和参数决定。

示例:自定义函数对象
假设我们需要一个回调函数,除了打印结果外,还能记录处理次数。可以用函数对象实现:

#include <iostream>

// 函数对象:记录处理次数的回调
class CountingCallback {
public:
    CountingCallback() : count(0) {}

    // 重载 operator(),使实例可像函数一样调用
    void operator()(int value) {
        count++;  // 更新状态(处理次数)
        std::cout << "[计数] 第 " << count << " 次处理,结果: " << value << " ";
    }

private:
    int count;  // 状态:已处理次数
};

int main() {
    CountingCallback counter;  // 创建函数对象实例
    std::function<void(int)> callback = counter;  // 存储到 std::function

    // 模拟多次调用回调
    callback(10);  // 输出:[计数] 第 1 次处理,结果: 10
    callback(20);  // 输出:[计数] 第 2 次处理,结果: 20
    callback(30);  // 输出:[计数] 第 3 次处理,结果: 30

    return 0;
}

关键输出:

[计数] 第 1 次处理,结果: 10
[计数] 第 2 次处理,结果: 20
[计数] 第 3 次处理,结果: 30

问题:函数对象是什么?

结论:函数对象(Functor)是重载了 operator() 的类的实例,可像函数一样调用

函数对象(也称为仿函数)是 C++ 中一种特殊的类,通过重载 operator() 运算符,使得其实例可以像函数一样被调用(使用 对象名(参数) 的语法)。

函数对象的特点:
  1. 可以携带状态:函数对象是类的实例,因此可以拥有成员变量,保存上下文信息(这是普通函数和 Lambda 无法直接具备的优势,尽管 Lambda 可以通过捕获列表模拟状态)。
  2. 行为可定制:通过不同的构造函数或成员变量,函数对象可以实现不同的行为。
  3. 类型安全:函数对象是具体的类型(由其类定义决定),而函数指针的类型由返回值和参数决定。
示例:自定义函数对象

假设我们需要一个回调函数,除了打印结果外,还能记录处理次数。可以用函数对象实现:

#include <iostream>

// 函数对象:记录处理次数的回调
class CountingCallback {
public:
    CountingCallback() : count(0) {}

    // 重载 operator(),使实例可像函数一样调用
    void operator()(int value) {
        count++;  // 更新状态(处理次数)
        std::cout << "[计数] 第 " << count << " 次处理,结果: " << value << " ";
    }

private:
    int count;  // 状态:已处理次数
};

int main() {
    CountingCallback counter;  // 创建函数对象实例
    std::function<void(int)> callback = counter;  // 存储到 std::function

    // 模拟多次调用回调
    callback(10);  // 输出:[计数] 第 1 次处理,结果: 10
    callback(20);  // 输出:[计数] 第 2 次处理,结果: 20
    callback(30);  // 输出:[计数] 第 3 次处理,结果: 30

    return 0;
}
关键输出:
[计数] 第 1 次处理,结果: 10
[计数] 第 2 次处理,结果: 20
[计数] 第 3 次处理,结果: 30

可以看到,函数对象 counter 内部维护了 count 状态,每次调用都会更新并打印处理次数。这种能力是普通函数(无状态)或 Lambda(依赖捕获的外部变量)难以直接实现的(Lambda 需要通过外部变量共享状态,而函数对象的状态是封装在自身对象中的)。

对比:函数指针 vs 函数对象 vs Lambda

类型 核心特点 是否可携带状态 典型用途
函数指针 指向普通函数的指针,无状态 简单回调(如日志、通知)
函数对象 重载 operator() 的类实例,可封装状态 是(通过成员变量) 需要上下文的回调(如计数、统计)
Lambda 表达式 匿名函数对象(本质是编译器生成的无名类实例),可捕获外部变量 是(通过捕获列表) 临时、简短的回调逻辑

总结

  • 函数对象是重载了 operator() 的类实例,可携带状态,适合需要上下文的回调场景。
  • std::function 作为通用回调容器,能统一包装函数指针、函数对象、Lambda 等可调用对象,提供灵活的回调机制。

posted on 2025-12-03 22:25  四季萌芽V  阅读(0)  评论(0)    收藏  举报

导航