系统程序员成长计划-拥抱变化(下)
2,279 views| 2008-11-14| 李先静| 系统程序员成长计划| | 14 条评论转载时请注明出处和作者联系方式
文章出处:http://www.limodev.cn/blog
作者联系方式:李先静 <xianjimli@gmail.com>
在专用双向链表中,dlist_printf的实现非常简单,如果里面存放的是整数,用”%d”打印,存放的字符串,用”%s”打印。现在的麻烦在于双向链表是通用的,我们无法预知其中存在的数据类型,也就是我们要面对数据类型的变化。怎么办呢?初学者常见的做法有:
1.实现多个函数,需要哪个就用哪个。比如实现的有dlist_print_int用来打印存放整数的双向链表,dlist_print_string用来打印存放字符串的双向链表,如此等等,其它类型都有自己的打印函数。
这种做法的缺点有:一是每个函数的实现方式类似,造成大量重复的代码。二是数据类型的种类不确定,每种数据类型都要写一个print函数,当要存放新的数据类型时,需要修改dlist的实现。
2.传入一个附加参数来决定如何打印。比如传入1表示按整数方式打印,传入2表示按字符串方式打印,以此类推。
这种做法比第一种好一点,至少不会造成大量重复的代码。但是同样存在增加新类型时要修改dlist_print函数的问题。
3调用dlist的接口函数获取每一个位置的数据并打印出来。
它可以避免前面两种方法的缺点,而且是一种很直观的方式。奇怪的是偏偏很少有人这样去做,原因可能有两个,其一是太拘泥于传统的实现方式而没有想到这一种。其二是担心性能问题,因为通过索引取值,每一次都从头开始定位,其性能开销为O(n*n)。
其实这种方法是可以接受的,dlist_print是用于辅助测试,我们并不在乎它的性能开销,而且很少在链表中存放成千上万的数据,它带来的性能影响也没有想的那样严重。
不过在这里我们要介绍一种新的方法:
dlist_print的大体框架为:
DListNode* iter = thiz->first;
while(iter != NULL)
{
print(iter->data);
iter = iter->next;
}
在上面代码中,我们主要是不知道如何实现print(iter->data);这行代码。可是谁知道呢?很明显,调用者知道,因为调用者知道里面存放的数据类型。OK,那让调用者来做好了,调用者调用dlist_print时提供一个函数给dlist_print调用,这种回调调用者提供的函数的方法,我们可以称它为回调函数法。
调用者如何提供函数给dlist_print呢?当然是通过函数指针了。变量指针指向的是一块数据,指针指向不同的变量,则取到的是不同的数据。函数指针指向的是一段代码(即函数),指针指向不同的函数,则具有不同的行为。函数指针是实现多态的手段,多态就是隔离变化的秘诀,这里只是一个开端,后面我们会逐步的深入学习。
回到正题上,我们看如何实现dlist_print:
定义函数指针类型:
typedef DListRet (*DListDataPrintFunc)(void* data);
声明dlist_print函数:
DListRet dlist_print(DList* thiz, DListDataPrintFunc print);
实现dlist_print函数:
DListRet dlist_print(DList* thiz, DListDataPrintFunc print)
{
DListRet ret = DLIST_RET_OK;
DListNode* iter = thiz->first;
while(iter != NULL)
{
print(iter->data);
iter = iter->next;
}
return ret;
}
调用方法
static DListRet print_int(void* data)
{
printf("%d ", (int)data);
return DLIST_RET_OK;
}
…
dlist_print(dlist, print_int);
所有问题都解决了,是不是很简单? 我以前写过一篇关于函数指针的BLOG,文中声称不懂函数指针就不要自称是C语言高手,现在我仍然坚持这个观点。函数指针的概念本身很简单,关键在于灵活应用,这里是一个最简单的应用,希望读者仔细体会一下,后面将会有大量篇幅介绍。
我写了一个简单的示例,它的实现并不完善,不过用来演示我们到目前为止学到的内容已经够了。有兴趣的读者请到这里下载。
系统程序员成长计划 Share
Comments
Tags
Recent Posts
Most Viewed
- 系统程序员成长计划写作提纲 - 19,605 views
- Android IPC机制详解 - 6,277 views
- 系统程序员成长计划-走近专业程序员(上) - 6,253 views
- 系统程序员成长计划-写得又快又好的秘诀(一) - 5,391 views
- 系统程序员成长计划-背景知识 - 5,070 views
- i++循环与i–循环的执行效率 - 4,712 views
- 系统程序员成长计划-Write once, run anywhere(WORA)(上) - 4,701 views
- 系统程序员成长计划-走近专业程序员(下) - 4,254 views
- Linux下的调试工具 - 4,017 views
- Advanced Linux Sound Architecture (ALSA) 研究笔记 - 4,017 views
- 系统程序员成长计划-序 - 3,985 views
- 系统程序员成长计划-写得又快又好的秘诀(三) - 3,930 views
- 中国人与自由软件文化研究(搞笑版) - 3,735 views
- Android中的MessageQueue,Handler,Looper和Thread - 3,686 views
- 答复:我不会OOO,仍然可以XXX - 3,659 views
Categories
- Android (28)
- Broncho-A1-Hack (6)
- DirectFB (7)
- FTK(嵌入式GUI) (24)
- GTK+ (29)
- KVM hack notes (8)
- Linux Mobile (65)
- Management (5)
- Mozilla (9)
- Open Source (5)
- Programming (34)
- Tools (9)
- Uncategorized (23)
- Win32 (3)
- X Windows (31)
- 沉思录 (29)
- 系统程序员成长计划 (67)
Blogroll
gallery
Linux guru
推荐网站
Recent Comments
- Dig on 嵌入式GUI FTK设计与实现-事件源(FtkSource)
- 用心生活每一天 » GNU gprof: linux profiling tools 使用 on gcc profiling的工作原理
- JavaScript for: i++ vs i–-传播、沟通、分享-一直“有你” on i++循环与i–循环的执行效率
- Frankly Law on 嵌入式GUI FTK介绍(11)-交叉编译
- tracing on Linux下的调试工具
- ndljsn on FTK移植指南(初稿)
- tracing on 爬塘朗山
- tracing on GTK+(基于DirectFB)的字体处理
- Kely on 系统程序员成长计划写作提纲
- tracing on 爬塘朗山



November 14th, 2008
卷心菜
November 18th, 2008
感觉一夜回到幼稚园,整点高科技,行不?
Dig
November 18th, 2008
温故而知新,学习一下
admin
November 18th, 2008
to 卷心菜:呵,这个系列是针对初学者的,会从幼儿园一步一步的走到研究生。如果你现在会走路了,去嘲笑别人跚跚走步的样子能说明什么呢?
echo
November 19th, 2008
一个东西之所以复杂是因为经历了长久的变化。这个变化的过程正是从新手到老手必须跨越的过程,博主的文章能够引导新手们以最小的代价不走弯路的尽快跨越这个门槛,实在是善莫大焉。
hedgehog
December 8th, 2008
绝对好文章…
hedgehog
December 8th, 2008
想起一个问题 请教下 如果使用指针存放数据,这里不仅print要用回调函数,而且在free的时候同样需要回调函数,因为如果data装载的是结构体的话,还会涉及到内存的分配问题。不知道这样理解对不对
admin
December 9th, 2008
是的。呵,看来你没有看后面的例子。
The linux mobile development » Blog Archive » 系统程序员成长计划写作提纲
May 25th, 2009
[...] 谁动了你的隐私(上)(下) 1.3 Write once, run anywhere(WORA)(上)(下) 1.4 拥抱变化(上)(下) 1.5 Don’t Repeat Yourself(DRY) (上)(下) 1.6 你的数据放在哪里(上)(下) 第2章 [...]
tiro
May 25th, 2009
写这个练习的时候,遇到了两个问题:一个和楼上的一样,链表的数据都需要由调用者动态分配吗,谁去释放怎么去释放这些数据的内存;第二个问题是错误处理的问题,当调用者传入的数据非法时,比如插入的位置非法什么的,有没有比较简洁通用的处理方式。
bad_guy
July 19th, 2009
好文章,但对下面的话不敢苟同:
“它可以避免前面两种方法的缺点,而且是一种很直观的方式。奇怪的是偏偏很少有人这样去做”
The Practice of Progrmming 第47页有类似的解法,但更简洁通用。
oyzp
July 22nd, 2009
博主的文章写得很好!
编程能力和表达能力都让我佩服@!
我本身对代码就很喜爱!喜欢把代码看成是艺术品,我看博主的代码就让我有这种感觉!我在这里学到了很多东西!
zhuxiaofeng
July 27th, 2009
为啥不能下载代码?谢谢
admin
July 28th, 2009
login first.
FengYi
October 16th, 2009
李老师,
print(iter->data);
需要改为
(*print)(iter->data); 么?