• 系统程序员成长计划拥抱变化(下)


    转载时请注明出处和作者联系方式
    文章出处: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上交流

  • 相关阅读:
    MySQL-存储过程
    MySQL-触发器
    MySQL自学笔记
    arrayList和LinkedList区别
    RecyclerView和ListView比较
    【二叉树遍历】必知方式
    进程与线程的区别
    【单例模式】java实现
    【斐波那契数列】java探究
    replugin插件化,插件转场动画失效的问题解决
  • 原文地址:https://www.cnblogs.com/zhangyunlin/p/6167594.html
Copyright © 2020-2023  润新知