系统程序员成长计划-拥抱变化(下)
转载时请注明出处和作者联系方式
文章出处:http://www.limodev.cn/blog
作者联系方式:李先静 <xianjimli at hotmail dot 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语言高手,现在我仍然坚持这个观点。函数指针的概念本身很简单,关键在于灵活应用,这里是一个最简单的应用,希望读者仔细体会一下,后面将会有大量篇幅介绍。
我写了一个简单的示例,它的实现并不完善,不过用来演示我们到目前为止学到的内容已经够了。有兴趣的读者请到这里下载。
欢迎到Linux mobile development上交流