文档章节

如何写好C语言之回调函数

frank21
 frank21
发布于 01/16 23:24
字数 1367
阅读 763
收藏 16

回调函数即常说的callback,C语言开发过程中,用好回调函数可以开发出高内聚低耦合的模块代码,事实上回调函数也是解除模块之间耦合的常用方法。本文介绍几种在开发实践中用到回到函数几种情况。

遍历回调

假设现在有一个容器模块,里面存储有数据,现在我们需要遍历容器里面的数据来做一下事情(比如统计容器里大于2的元素个数),为了不暴露容器的实现细节,容器模块可以提供一个遍历所有元素的API给用户,这个API的的原型如下:

/**
 * @brief 遍历容器里所有的元素
 * 
 * @param p_container 容器指针 
 * @param cb 回调函数
 * @param user_data 回调函数用户数据
 * @return int 返回值
 */
int container_each_element(void *p_container, void (*cb)(void *element, void *user_data), void *user_data);

一般提供回调函数参数的API都会提供一个额外的参数user_data,这个参数会在用户调用这个API的时候由用户传入,API回调时再传给用户,这个参数可以提供很多上下文的东西,待会我们会提到,下面我们看看回到函数的原型:

/**
 * @brief 用户处理数据的函数
 * 
 * @param element 容器里的元素
 * @param user_data 用户的回到函数数据。
 */
void user_callback(void *element, void *user_data);

回调函数第一个参数是当前遍历到的容器里的元素,第二个参数就是API回传给用户的数据,为了更清晰的了解这种使用方法,下面我们来举个例子,我们先实现一个简单的存储固定数量整型数的容器:

typedef struct
{
    int *p;
    int count;
}sample_container_t;


sample_container_t *sample_container_create(int size)
{
     sample_container_t *p = malloc(sizeof(sample_container_t) + size * sizeof(int));
     p->p = ((char *)p) + sizeof(sample_container_t);
     p->count = 0;
     return p;
}

void sample_container_destory(void *p)
{
    free(p);
}

//加一个数据
int sample_container_add(sample_container_t *p_container, int ele)
{
    p_container->p[p_container->count++] = ele;
}
//删除最后一个数据
int sample_container_del(sample_container_t *p_container)
{
    p_container->count--;
}

//遍历容器
int sample_container_each_element(sample_container_t *p_container, void (*cb)(int element, void *user_data), void *user_data)
{
    for(int i = 0; i < p_container->count, i++)
    {
        cb(p_container->p[i], user_data);
    }
}

针对这个容器,我们想计算一下这个容器里大于某个数的数据有多少个,那么我们可以实现下面的回调函数:

typedef struct
{
    int gt;
    int count;
}user_data_t;

void statistic_container_gt(int ele, void *user_data)
{
    user_data_t *p = (user_data_t *)user_data;
    if (ele > p->gt)
    {
        p->count++;
    }
}

int main()
{
   sample_container_t *p = sample_container_create(100);
   sample_container_add(p, 1);
   sample_container_add(p, 4);
    
   ///计算大于2的数的个数
   user_data_t user_data = {2, 0};
   sample_container_each_element(p, statistic_container_gt, &user_data);
    
   printf("The count of gt 2 is:\n", user_data.count);
}

这样做有两个好处:

  • 处理数据模块不用关心容器的实现细节,容器的任何改动都不会引起数据处理函数的修改。
  • 想对于容器直接提供API实现这样的功能,更加灵活,完全独立于业务逻辑。比如像计算容器里偶数的个数,只要在写一个回调函数就可以了,不用修改容器模块。

事件通知

在开发过程中,各个模块之间的通信是避免不了的,一般最简单的方法就是调用另外一个模块的API,告诉对方放生了什么事件。比如有一个监控一系统服务状态模块,如果发现某个服务处理不可用状态,要通知运维模块来处理这个时间。按照最直接的办法就是下面的伪代码:

int monitor_check_server_status(server_t *servers, int count)
{
    while(1)
    {
        for(int i =0; i < count; i++)
        {
            if (is_server_unavailable(servers[i]))
            {
                ///直接调用运维模块API
                notify_ops_unavailable(&servers[i]);
            }
        }
        
        sleep(1);
    }
}

这样做基本可以实现功能,但是有两个缺点:

  • 耦合度高:直接调用运维模块的API导致两个模块产生紧耦合,实际开发过程中,如果运维模块还没有开发完成,那么我们是无法进行调试或者开发的。
  • 扩展性差:如果在后期维护过程中,需要通知更多的模块,那么我们就需要修改代码,添加更多的API调用。

那么我们引进回调函数,看看有什么样子的效果。首先这种情况下,监控服务模块应该提供两个API:

  • 注册回调函数:monitor_add_unavailable_callback()
  • 撤销回调函数: monitor_del_unavailable_callback()

按照这个模式,我们简单实现以下监控服务模块的callback版本。

typedef void (*server_unavailable_callback_t)(server_t *s);
#define MAX_CALLBACKS_NUMBER 10

static server_unavaliable_t unavailable_callbacks[MAX_CALLBACK_NUMBER];
static int cb_count = 0;

int monitor_add_unavailable_callback(server_unavailable_callback_t cb)
{
    if (cb_count < MAX_CALLBACKS_NUMBER)
    {
        unavailable_callbacks[cb_count++] = cb;
    }
}    

int monitor_del_unavailable_callback(server_unavailable_callback_t cb)
{
    for(int i = 0; i < cb_count; i++)
    {
        if (unavailable_callbacks[i] == cb)
        {
            unavaiable_callbacks[i] = NULL;
        }
    }
}

int monitor_check_server_status(server_t *servers, int count)
{
    while(1)
    {
        for(int i =0; i < count; i++)
        {
            if (is_server_unavailable(&servers[i]))
            {
                for (int i = 0; i < cb_count; i++)
                {
                    if (unavailable_callbacks[i] != NULL)
                    {
						unavailable_callbacks[i](&servers[i])
                    }
                }
            }
        }
        
        sleep(1);
    }
}

这样实现就完全解决了上面提到的两个问题:

  • 不依赖于任何模块,测试开发独立进行,都是只是进行联调就可以
  • 如果需要通知更多的模块,根本不需要修改代码,只是在初始化的时候添加callback就可以了。

© 著作权归作者所有

共有 人打赏支持
frank21
粉丝 26
博文 19
码字总数 19688
作品 0
浦东
高级程序员
私信 提问
python如何调用C, 如何注册成C的回调函数(python后台程序常用方法)

其实是python后台程序常用方法: C开发完成底层的功能,python直接把C当做python模块进行调用。 需要做两个工作: python能调用C语言的函数; python通过调用C函数,并注册python的回调函数,...

simpower
01/02
0
0
java回调函数机制

一、 概述 软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:同步调用、回调、异步调用 。 同步调用:一种阻塞式调用,调用方要等待对方执行完毕才返回,它是一种单向调...

皮蛋瘦肉粥里没有粥
2015/08/28
57
0
快速上手Gobject

What is G-object? —很多人被灌输了这样一种概念:要写面向对象程序,那么就需要学习一种面向对象编程语言,例如C++、Java、C#等等,而C语言是用来编写结构化程序的。 —事实上,面向对象只...

shezjl
2016/01/17
44
0
函数指针的正向调用和回调函数

理解 函数指针:是一个指针,指向函数入口地址的指针。本质就是一个数据类型,可以来定义变量。 函数指针正向调用:在应用程序中直接给函数指针赋值,调用指针指向的函数,就是正向调用。 函...

沙米笔记
2016/05/21
165
0
PHP之高性能I/O框架:Libevent(一)

Libevent 是一个用C语言编写的、轻量级的开源高性能I/O框架,支持多种 I/O 多路复用技术: epoll、 poll、 dev/poll、 select 和 kqueue 等;支持 I/O,定时器和信号等事件;注册事件优先级。...

飞鸿影的博客
2018/07/02
0
0

没有更多内容

加载失败,请刷新页面

加载更多

PHP底层的运行机制与原理

PHP是一种适用于web开发的动态语言。具体点说,就是一个用C语言实现包含大量组件模块的软件框架。是一个强大的UI框架。 简言之;PHP动态语言执行过程:拿到一段代码后,经过词法解析、语法解...

echojson
22分钟前
3
0
0323 第六次课:磁盘管理

用户和组管理 一、磁盘使用情况 df命令 df命令是用来查看系统磁盘空间占用情况 常用示例: df -h 更人性化的显示磁盘使用信息 df -m 以m为单位显示 df -i 显示磁盘inode使用情况,有的时候磁...

wxy丶
28分钟前
0
0
为论坛做负载均衡集群

1. 克隆虚拟机 修改IP地址修改主机名 2. 准备工作 两台机器上开启nginx,保证能访问论坛第二台机器上,关闭mariadb 修改配置文件:config/config_global.php config/config_ucenter.ph...

wzb88
28分钟前
1
0
__attribute__ 总结

attribute是GNU C特色之一,在iOS用的比较广泛.系统中有许多地方使用到. attribute可以设置函数属性(Function Attribute )、变量属性(Variable Attribute )和类型属性(Type Attribute)等...

天王盖地虎626
34分钟前
0
0
Android 自定义轮播图View

一、原理 ViewPager是Android中使用频率相对较高的view组件,同时对滑动过程中的事件进行了处理,因此非常适合轮播图。关于轮播图的实现,有很多方法,使用HorizontalView或者RecylerView也可...

IamOkay
39分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部