首页 > 其他分享 >一文搞懂回调函数

一文搞懂回调函数

时间:2024-09-04 17:21:46浏览次数:14  
标签:一文 int void printf 搞懂 回调 event 函数

回调函数

概念

回调函数(Callback Function) 是一种通过函数指针调用的函数。回调函数的一个典型用途是允许代码的一个模块或组件通知另一个模块或组件,事件已经发生或者某种条件已经达成。回调函数通常作为参数传递给另一个函数,后者在合适的时候调用它。

简而言之,回调函数就是把一个函数作为参数传递给另一个函数,目的是在某个时间点调用这个传入的函数。

定义和使用

在C语言中,可以使用函数指针来定义回调函数。以下是回调函数的一些关键点:

  1. 函数指针的定义:一个指向函数的指针,它可以保存一个函数的地址,并可以用来调用这个函数。
  2. 函数类型匹配:回调函数的函数指针类型必须匹配要传递的函数的返回类型和参数类型。

示例:如何定义和使用回调函数

假设我们有一个函数 process(),它接收一个回调函数作为参数,回调函数类型为 void callback(int),表示它是一个接受一个 int 参数并返回 void 的函数。我们可以定义和使用回调函数如下:

#include <stdio.h>

// 定义回调函数类型:返回类型为 void,接受一个 int 参数
typedef void (*callback_t)(int);

// 定义一个回调函数
void myCallback(int num) {
    printf("Callback called with value: %d\n", num);
}

// 使用回调函数的函数
void process(callback_t cb) {
    printf("Processing...\n");
    // 调用回调函数
    cb(10);
}

int main() {
    // 调用 process() 并传入回调函数 myCallback
    process(myCallback);
    return 0;
}

1. 注册回调函数的概念

注册回调函数:注册回调函数是指将一个回调函数指针存储在某个地方(通常是一个数据结构或全局变量),以便在特定事件发生时调用该回调函数。这种机制允许程序在运行时动态地改变行为。

2. 注册回调函数的高级用法

2.1 事件驱动编程中的回调函数注册

在事件驱动编程中,回调函数通常用于处理各种事件,例如按钮点击、网络数据到达等。下面是一个简单的例子,展示如何注册和调用回调函数来处理事件。

#include <stdio.h>
#include <stdlib.h>

// 定义一个事件处理函数类型
typedef void (*EventHandler)(void);

// 定义一个结构体来存储事件处理函数
typedef struct {
    EventHandler handler;
} Event;

// 定义一个全局变量来存储事件
Event event;

// 注册事件处理函数
void registerEventHandler(EventHandler handler) {
    event.handler = handler;
}

// 模拟事件触发
void triggerEvent() {
    if (event.handler != NULL) {
        event.handler();
    }
}

// 定义一个具体的事件处理函数
void onEvent() {
    printf("Event occurred!\n");
}

int main() {
    // 注册事件处理函数
    registerEventHandler(onEvent);

    // 模拟事件触发
    triggerEvent();

    return 0;
}
2.2 异步编程中的回调函数注册

在异步编程中,回调函数常用于处理异步操作的结果,例如文件读取、网络请求等。下面是一个简单的例子,展示如何注册和调用回调函数来处理异步操作的结果。

#include <stdio.h>
#include <stdlib.h>

// 定义一个异步操作完成的回调函数类型
typedef void (*AsyncCallback)(int result);

// 定义一个结构体来存储异步操作的回调函数
typedef struct {
    AsyncCallback callback;
} AsyncOperation;

// 注册异步操作的回调函数
void registerAsyncCallback(AsyncOperation *operation, AsyncCallback callback) {
    operation->callback = callback;
}

// 模拟异步操作完成
void completeAsyncOperation(AsyncOperation *operation, int result) {
    if (operation->callback != NULL) {
        operation->callback(result);
    }
}

// 定义一个具体的异步操作完成的回调函数
void onAsyncOperationComplete(int result) {
    printf("Async operation completed with result: %d\n", result);
}

int main() {
    AsyncOperation operation;

    // 注册异步操作的回调函数
    registerAsyncCallback(&operation, onAsyncOperationComplete);

    // 模拟异步操作完成
    completeAsyncOperation(&operation, 42);

    return 0;
}
2.3 库设计中的回调函数注册

在库设计中,回调函数常用于提供扩展点,使得用户可以自定义库的行为。下面是一个简单的例子,展示如何设计一个支持回调函数注册的库。

#include <stdio.h>
#include <stdlib.h>

// 定义一个日志回调函数类型
typedef void (*LogCallback)(const char *message);

// 定义一个结构体来存储日志回调函数
typedef struct {
    LogCallback callback;
} Logger;

// 定义一个全局变量来存储日志回调函数
Logger logger;

// 注册日志回调函数
void registerLogCallback(LogCallback callback) {
    logger.callback = callback;
}

// 记录日志
void logMessage(const char *message) {
    if (logger.callback != NULL) {
        logger.callback(message);
    } else {
        printf("Default log: %s\n", message);
    }
}

// 定义一个具体的日志回调函数
void customLogCallback(const char *message) {
    printf("Custom log: %s\n", message);
}

int main() {
    // 注册自定义的日志回调函数
    registerLogCallback(customLogCallback);

    // 记录日志
    logMessage("Hello, world!");

    return 0;
}

3. 回调函数注册的优缺点

优点
  1. 灵活性:允许在运行时动态地改变程序的行为。
  2. 模块化:使得代码更加模块化,降低耦合度。
  3. 可扩展性:通过回调函数注册机制,可以很容易地扩展功能,而不需要修改现有代码。
  4. 事件驱动:特别适用于事件驱动编程模型,使得代码更加响应式和高效。
缺点
  1. 复杂性:回调函数的使用增加了代码的复杂性,尤其是在嵌套回调或多层回调的情况下。
  2. 调试困难:由于回调函数的调用是动态的,调试和跟踪代码执行路径可能会变得更加困难。
  3. 性能开销:频繁的回调函数调用可能会带来一定的性能开销,尤其是在实时系统中。
  4. 内存管理:在某些情况下,回调函数的注册和注销需要小心处理内存管理,以避免内存泄漏或悬挂指针。

4. 更高级的用法

4.1 多个回调函数的注册和管理

在某些情况下,你可能需要注册多个回调函数,并在特定事件发生时调用所有注册的回调函数。下面是一个简单的例子,展示如何实现多个回调函数的注册和管理。

#include <stdio.h>
#include <stdlib.h>

#define MAX_CALLBACKS 10

// 定义一个事件处理函数类型
typedef void (*EventHandler)(void);

// 定义一个结构体来存储多个事件处理函数
typedef struct {
    EventHandler handlers[MAX_CALLBACKS];
    int count;
} Event;

// 初始化事件
void initEvent(Event *event) {
    event->count = 0;
}

// 注册事件处理函数
void registerEventHandler(Event *event, EventHandler handler) {
    if (event->count < MAX_CALLBACKS) {
        event->handlers[event->count++] = handler;
    } else {
        printf("Max callbacks reached!\n");
    }
}

// 触发事件,调用所有注册的回调函数
void triggerEvent(Event *event) {
    for (int i = 0; i < event->count; i++) {
        event->handlers[i]();
    }
}

// 定义具体的事件处理函数
void onEvent1() {
    printf("Event handler 1 called!\n");
}

void onEvent2() {
    printf("Event handler 2 called!\n");
}

int main() {
    Event event;
    initEvent(&event);

    // 注册多个事件处理函数
    registerEventHandler(&event, onEvent1);
    registerEventHandler(&event, onEvent2);

    // 触发事件
    triggerEvent(&event);

    return 0;
}
4.2 带参数的回调函数

有时你可能需要传递参数给回调函数。可以通过定义带参数的回调函数类型来实现这一点。

#include <stdio.h>
#include <stdlib.h>

// 定义一个带参数的回调函数类型
typedef void (*EventHandler)(int);

// 定义一个结构体来存储事件处理函数
typedef struct {
    EventHandler handler;
} Event;

// 注册事件处理函数
void registerEventHandler(Event *event, EventHandler handler) {
    event->handler = handler;
}

// 触发事件,传递参数给回调函数
void triggerEvent(Event *event, int value) {
    if (event->handler != NULL) {
        event->handler(value);
    }
}

// 定义具体的事件处理函数
void onEvent(int value) {
    printf("Event occurred with value: %d\n", value);
}

int main() {
    Event event;

    // 注册事件处理函数
    registerEventHandler(&event, onEvent);

    // 触发事件,传递参数
    triggerEvent(&event, 42);

    return 0;
}
4.3 使用上下文数据的回调函数

在某些情况下,你可能需要在回调函数中使用上下文数据。可以通过传递一个包含上下文数据的结构体来实现这一点。

假设我们有一个排序函数,它可以接受一个比较函数作为回调。我们希望使用这个回调函数来比较两个整数,但是我们希望在比较时能够根据上下文数据来决定排序顺序(升序或降序)。

#include <stdio.h>
#include <stdlib.h>

// 比较函数的类型
typedef int (*CompareFunc)(int a, int b, void* context);

// 上下文结构,用于存储排序顺序的信息
typedef struct {
    int order;  // 1表示升序,-1表示降序
} SortContext;

// 升序比较函数
int ascending(int a, int b, void* context) {
    SortContext* ctx = (SortContext*)context;
    return (a - b) * ctx->order;
}

// 降序比较函数
int descending(int a, int b, void* context) {
    SortContext* ctx = (SortContext*)context;
    return (b - a) * ctx->order;
}

void sort(int* array, size_t size, CompareFunc cmp, void* context) {
    for (size_t i = 0; i < size - 1; ++i) {
        for (size_t j = 0; j < size - i - 1; ++j) {
            if (cmp(array[j], array[j + 1], context) > 0) {
                int temp = array[j];
                array[j] = array[j + 1];
                array[j + 1] = temp;
            }
        }
    }
}

int main() {
    int array[] = {5, 2, 9, 1, 5, 6};
    size_t size = sizeof(array) / sizeof(array[0]);

    SortContext ascendingContext = {1};  // 升序
    SortContext descendingContext = {-1};  // 降序

    printf("Original array: ");
    for (size_t i = 0; i < size; ++i) {
        printf("%d ", array[i]);
    }
    printf("\n");

    // 升序排序
    sort(array, size, ascending, &ascendingContext);
    printf("Sorted array in ascending order: ");
    for (size_t i = 0; i < size; ++i) {
        printf("%d ", array[i]);
    }
    printf("\n");

    // 降序排序
    sort(array, size, descending, &descendingContext);
    printf("Sorted array in descending order: ");
    for (size_t i = 0; i < size; ++i) {
        printf("%d ", array[i]);
    }
    printf("\n");

    return 0;
}

运行结果

编译并运行上述代码,将得到以下结果:

Original array: 5 2 9 1 5 6 
Sorted array in ascending order: 1 2 5 5 6 9 
Sorted array in descending order: 9 6 5 5 2 1 

通过这个例子,我们可以看到如何使用回调函数和上下文数据在C语言中实现灵活的排序算法。回调函数 ascendingdescending 根据上下文数据 SortContext 来决定排序顺序。

4.4 通过使用回调函数来处理异步任务,并传递上下文数据进行复杂的操作

以下是一个更高级的示例,通过使用回调函数来处理异步任务,并传递上下文数据进行复杂的操作。

例子:异步任务处理器

我们将创建一个异步任务处理器,它可以接受不同的任务并在任务完成时调用回调函数。每个任务都有自己的上下文数据,用于存储任务的状态和相关信息。

首先,我们定义一个任务结构和回调函数类型,以及一个通用的上下文结构。

//步骤 1:定义任务和回调函数
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>  // 为了使用sleep函数

// 回调函数的类型
typedef void (*TaskCallback)(void* context);

// 上下文结构,用于存储任务状态和相关信息
typedef struct {
    int taskId;
    const char* taskName;
    int result;
} TaskContext;

// 任务结构
typedef struct {
    TaskContext* context;
    TaskCallback callback;
} Task;

//步骤 2:异步任务处理器
void asyncTaskProcessor(Task* task) {
    printf("Processing task %d: %s\n", task->context->taskId, task->context->taskName);

    // 模拟异步操作,例如网络请求或文件I/O
    sleep(2);  // 休眠2秒,模拟操作时间

    // 设置任务结果
    task->context->result = task->context->taskId * 2;

    // 调用回调函数
    task->callback(task->context);
}


//步骤 3:定义回调函数
void onTaskComplete(void* context) {
    TaskContext* ctx = (TaskContext*)context;
    printf("Task %d (%s) completed with result: %d\n", ctx->taskId, ctx->taskName, ctx->result);
}


//步骤 4:测试异步任务处理器
int main() {
    // 创建任务上下文
    TaskContext context1 = {1, "Task One", 0};
    TaskContext context2 = {2, "Task Two", 0};

    // 创建任务
    Task task1 = {&context1, onTaskComplete};
    Task task2 = {&context2, onTaskComplete};

    // 处理任务
    asyncTaskProcessor(&task1);
    asyncTaskProcessor(&task2);

    return 0;
}

运行结果

编译并运行上述代码,将得到以下结果:

Processing task 1: Task One
Processing task 2: Task Two
Task 1 (Task One) completed with result: 2
Task 2 (Task Two) completed with result: 4

解释

  1. 任务和上下文:每个任务都有一个上下文结构 TaskContext,用于存储任务ID、任务名称和结果。
  2. 异步任务处理器asyncTaskProcessor 模拟了一个异步操作,使用 sleep 函数来模拟操作时间,并在操作完成后调用回调函数 onTaskComplete
  3. 回调函数onTaskComplete 回调函数在任务完成后被调用,并根据上下文数据进行处理。

标签:一文,int,void,printf,搞懂,回调,event,函数
From: https://blog.csdn.net/qq_43119867/article/details/141898837

相关文章

  • MySQL(二)函数
    聚合函数1、AVG()函数返回数值列的平均值SELECTAVG(column_name)FROMtable_name2、COUNT()函数返回匹配指定条件的行数(1)返回指定列的值的数目(NULL不计入)SELECTCOUNT(column_name)FROMtable_name;(2)返回表中的记录数SELECTCOUNT(*)FROMtable_name;(3)返回指......
  • 【C++从练气到飞升】19---哈希:哈希冲突 | 哈希函数 | 闭散列 | 开散列
     ......
  • 数据分析之Excel常用的函数
    函数注意事项1.写函数前要加'='号:        2.函数可以跨工作表引用3.单元格下方小十字可以自动填充,仿照选中单元格函数,如果自动填充时想锁定某个条件,可以在前面加$符,如图,锁定了Q18这个条件(选中想要锁定的条件后可以直接按F4快捷键锁定,Mac是fn+F4)        ......
  • S-Clustr(影子集群) Simple SCC伪代码编译器,工业控制DSL结构语言,递归函数调用
    项目地址:https://github.com/MartinxMax/S-Clustr/releases200S-ClustrSimpleDSL语法内置函数示例RUN(启动设备)RUN:<ID>STOP(停止设备)STOP:<ID>TIME(MS延时)TIME:<Delay/Ms>函数示例DEF(定义函数名,空形参)DEFFunction:DEF(函数名,带形参)DEFFunction:var,......
  • 【ORACLE】listagg() 函数
    Oracle数据库中的LISTAGG函数是一个聚合函数,它用于将多个行的字符串值合并成一个单一的字符串。这对于生成报告或创建列表非常有用,例如,将同一类别的所有项合并成一个逗号分隔的字符串。语法LISTAGG(expression,delimiter)WITHINGROUP(ORDERBYcolumn)expressio......
  • 《C++编程规范》四、函数与操作符
    目录第25条正确地选择通过值、(智能)指针或者引用传递参数第25条正确地选择通过值、(智能)指针或者引用传递参数正确选择参数:分清输入参数、输出参数和输入/输出参数,分清值参数和引用参数。正确地传递参数。选择如何传递参数时,应该遵循以下准则。对于只输入(input-only)参数:始......
  • 深度学习基础实践:理解Sigmoid激活函数原理和实现
    Sigmoid激活函数是一种广泛应用于机器学习和深度学习中的非线性函数,特别是在二分类问题中。它的作用是将一个实数值映射到(0,1)区间,使得输出可以被解释为概率值,这在处理二分类问题时非常有用。Sigmoid函数的定义Sigmoid函数的数学表达式为:......
  • 动态内存分配之realloc()函数详解
    目录一、函数简介二、函数原型参数返回值三、函数实现(伪代码)3.1.简化的realloc实现逻辑3.2.伪代码示例四、使用场景4.1.动态数组大小调整4.2.动态字符串大小调整4.3.内存优化4.4.复杂数据结构的内存管理4.5.跨函数内存管理4.6.灵活的内存分配策略五、......
  • Python程序:递归实现阶乘函数的优化与代码解读
    一、引言阶乘(Factorial)在数学和计算机科学中是一个常见的概念,它表示一个正整数的所有正整数的乘积。阶乘的定义如下:n!=n×(n−1)×(n−2)×…×1其中,0!定义为1。本文将以递归方式实现阶乘函数,并对代码进行优化与解释。二、原始代码首先来看一个简单的递归实现阶乘的P......
  • 一文打尽,商业银行有啥业务逻辑?
    资产业务发放短期、中期和长期贷款场景:企业A需要资金扩大生产,向商业银行申请贷款。银行评估企业A的信用状况和还款能力后,决定发放一笔中期贷款。重要性:贷款是银行最主要的资产业务,通过贷款,银行可以获得利息收入,同时支持企业和个人的发展。业务逻辑:银行需要评估借款人的信......