如何写好C语言之回调函数
回调函数即常说的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就可以了。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
使用 ale.js 制作一个小而美的表格编辑器(2)
今天来教大家如何使用 ale.js 制作一个小而美的表格编辑器,首先先上 gif: 是不是还是有一点非常 cool 的感觉的?那么我们现在开始吧! 这是我们这篇文章结束后完成的效果(如果想继续完成请访问第三篇文章): ok,那继续开始吧(本篇文章是表格编辑器系列的第二篇文章,如果您还没有看过第一篇,请访问 第一篇文章(开源中国)): 首先我们写一个名叫 staticData 的 object,里面添加2个属性,分别是 sortBy 和 sortType:(关于 staticData 这里不做阐述,如果有需要请访问 cn.alejs.org) staticData: { sortBy: -1, //排序列索引,默认没有,所以为-1 sortType: 'down' //排序类型,默认为降序 } 之后我们在th 标签里面增加一个 onclick 属性,指向 methods 里面的 handleTheadOnclick 函数,并传递一个 event 作为参数: (之前的代码) this.data.bookHeader.forEach(function(val, i, arr) { r...
-
下一篇
利用神器BTrace 追踪线上 Spring Boot应用运行时信息
概述 生产环境中的服务可能会出现各种问题,但总不能让服务下线来专门排查错误,这时候最好有一些手段来获取程序运行时信息,比如 接口方法参数/返回值、外部调用情况 以及 函数执行时间等信息以便定位问题。传统的日志记录方式的确可以,但有时非常麻烦,甚至可能需要重启服务,因此代价太大,这时可以借助一个牛批的工具:BTrace ! BTrace 可用于动态跟踪正在运行的 Java程序,其原理是通过动态地检测目标应用程序的类并注入跟踪代码 ( “字节码跟踪” ),因此可以直接用于监控和追踪线上问题而无需修改业务代码并重启应用程序。 BTrace 的使用方式是用户自己编写符合 BTrace使用语法的脚本,并结合btrace命令,来获取应用的一切调用信息,就像下面这样: <btrace>/bin/btrace <PID> <trace_script> 其中 <PID>为被监控 Java应用的 进程ID <trace_script> 为 根据需要监控的信息 而自行编写的 Java脚本 本文就来实操一波 BTrace工具的使用,实验环境如下: O...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- MySQL数据库在高并发下的优化方案
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- CentOS7,8上快速安装Gitea,搭建Git服务器
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS8编译安装MySQL8.0.19
- Dcoker安装(在线仓库),最新的服务器搭配容器使用