回调函数编写双向链表求和、找最大值学习笔记(摘抄自李先静博客)

    技术2022-07-11  121

    1、自己之前在开发功能时候也常常封装很多函数。这种做法会造成重复的代码,让dlist的实现随着应用环境的变化而变化。

    2、采样上一篇的回调函数法。这两个函数的实现和dlist_print的实现类似,无非是print那行代码要换成别的功能。在真正动手时,发现每个回调函数都要保存一些中间数据。大部分人选择了用全局变量来保存,这可以实现要求的功能(这也是我常常犯的毛病),但违背了禁用全局变量的原则。

    (1).不要编写重复的代码。

    写重复的代码很简单,甚至凭本能都可以写出来。但要想成为优秀的程序员,一定要克服自己的惰性,因为重复的代码造成很多问题:

    重复的代码更容易出错。在写类似代码的时候,几乎所有人都会选择 Copy&Paste 的方法,这种方法很容易犯一些细节上的错误,如果某个地方修改不完整,那就留下了”不定时”的炸弹,说不定什么时候会暴露出来。

    重复的代码经不起变化。无论是修改BUG,还是增加新特性,往往你要修改很多地方,如果忘掉其中之一,你同样得为此付出代价。请记住古惑仔的话,出来混迟早是要还的。大师们说过,在软件中欠下的BUG,你会为此还得更多。

    去除重复代码往往不是件简单的事情,需要更多思考和更多精力,不过事实证明这是最值得的投资。在这里,我们要怎么抽取这些重复的代码呢? 这三个函数无非是要遍历双向链表并做一些事情,遍历双向链表我们可以提供一个dlist_foreach 函数,至于要做什么,这是千变万化的行为,可以通过回调函数让调用者去做。

    (2)、任何回调函数都要有上下文

    虽然混了这么多年嵌入式开发软件的饭,但是自己还是常常喜欢用全局变量;使用全局变量有很多坏处,按作者的说法列举如下:

    (禁止全局变量 除了为使用单件模式(只允许一个实例存在)的情况外,任何时候都要禁止使用全局变量。这一点我反复的强调,但发现初学者还是屡禁不止,为了贪图方便而使用全局变量。请读者从现在开始就记住这一准则。 全局变量始终都会占用内存空间,共享库的全局变量是按页分配的,那怕只有一个字节的全局变量也占用一个page,所以这会造成不必要空间浪费。全局变量也会给程序并发造成困难,想把程序从单线程改为多线程将会遇到麻烦。重要的是,如果调用者直接访问这些全局变量,会造成调用者和实现者之间的耦合。)

    在使用回调函数情况下可以这样避免使用全局变量:很简单,给回调函数传递额外的参数就行了。这个参数我们称为回调函数的上下文,变量名用ctx(context的缩写)。要在这个上下文中存放什么东西呢?那得根据具体的回调函数而定,为了能保存任何数据类型,我们选择void*表示这个上下文。

    DListRet dlist_foreach(DList* list,DListDataVisitFunc visit,void* ctx)

    {

       DListNode* node = list->first;

       while(node != NULL)

       {

          visit(ctx,node->data);

          node =  node->next;

      }

       return DLIST_RET_OK;

    }

    visit 是回调函数,ctx就是我们说的上下文。要特别强调的一点是,ctx应该作为回调函数的第一个参数。为什么呢?在前面我们讲过的面向对象的函数命名规则中,我们以thiz作为函数的第一个参数,而thiz通常也就是函数的上下文。如果在这里恰ctx==thiz,就不需要因为参数顺序不同而做转换了。

    实现求和的回调函数:

    DListRet sum_cx(void* ctx,void* data)

    {

       long* sum = ctx;

       *sum += (int)data;

       return DLIST_RET_OK;

    }

    调用foreach:

    long sumx = 0;

    dlist_foreach(dlist,sum_cx,&sumx);

    同理,查找最大值的回调函数:

    typedef struct _Max_Val

    {

       unsigned char first;

       int max_val;

    }Max_Val;

    DListRet max_cx(void* ctx,void* data)

    {

       Max_Val* max = ctx;

       if(ctx->first == 0)

       {

          ctx->first = 1;

          max->max_val = (int)data;

       }

       else if(max->max_val < (int)data)

        {

          max->max_val = (int)data;

        }

        return DLIST_RET_OK;

    }

    调用foreach:

    Max_Val maxval = {.first = 0,0};

    dlistforeach(dlist,max_cx,&maxval);

    Processed: 0.064, SQL: 12