回調函數到底是怎么一回事呢?


  今天看到回調函數,有點迷糊,找了好多搜索引擎的資料,都不是讓我很能理解,看了《c和指針》我才明白了。

簡單描述一下什么是回調函數:

  用戶把一個函數指針作為參數傳遞給其他函數,后者將“回調”用戶的函數。如果函數可以再不同的時間執行不同類型的工作或者執行只能由函數調用者定義的工作,都可以使用回調函數。 回調函數無法知道比較的值的類型,所以參數的類型被聲明為void*。表示一個指向未知類型的指針。 可以通過函數指針來實現回調函數。一個指向回調函數的指針作為參數傳遞給另一個函數,后者使用這個指針調用回調函數。 

  可能說了太多定義也不會很是明白,來幾個事例說說。

  當我們在在鏈表中查找一個數時,我們一般會這樣寫:

 1 Node *search_list( Node *node, int const value )
 2 {
 3     while ( NULL != node ){
 4         if ( node->value == value ){
 5             break;
 6         }
 7         node = node->link;
 8     }
 9 
10     return node;
11 }

這樣就限制我們只能在查找的數必須是int類型,當變為其他類型時我們就無法用這個函數,但是重新寫一個函數,他們重復代碼又太多。那我們看看用回調函數如何辦到。  

回調函數查找:

 

1 int compare_int( void const *a, void const *b )
2 {
3     if ( *( int * )a == *( int * )b ){
4         return 0;
5     }
6 
7     return 1;
8 }

 

 1 Node *search_list(Node *node, void const *value, 
 2     int (*compare)(void const *, void const *))  //函數指針
 3 {
 4     while(node != NULL){
 5         if(compare(&node->value, value) == 0)  //相等
 6             break;
 7         node = node->link;
 8     }
 9     return node;
10 }

 

 這樣利用回調函數就可以解決如上問題。我們把一個函數指針( int (*compare)(void const *, void const *) )作為參數傳遞給查找函數,查找函數將“回調”比較函數。當我們需要執行不同類型的比較時我們合理調用該函數。例如:當我們整形查找時: search_list( root, &desired_value, compare_int ); ,使用字符查找時: search_list( root, &desired_value, compare_char ); 。這就是回調函數簡單的應用,當然回調函數不僅僅只是用於這些簡單的例子,比如庫函數qsort就是利用回調函數實現。

  函數原型如下:

void qsort(
   void *base,    //字符串首地址
   size_t num,  //排序總個數
   size_t width, //排序元素的大小
   int (__cdecl *compare )(const void *, const void *)  //函數指針
);

  庫函數實現:

 
         
void qsort(
   void *base, //字符串首地址 size_t num, //排序總個數 size_t width, //排序元素的大小 int (__cdecl *compare )(const void *, const void *) //函數指針 );
{
    char *lo, *hi;              /* ends of sub-array currently sorting */
    char *mid;                  /* points to middle of subarray */
    char *loguy, *higuy;        /* traveling pointers for partition step */
    size_t size;                /* size of the sub-array */
    char *lostk[STKSIZ], *histk[STKSIZ];
    int stkptr;                 /* stack for saving sub-array to be processed */

    /* validation section */
    _VALIDATE_RETURN_VOID(base != NULL || num == 0, EINVAL);
    _VALIDATE_RETURN_VOID(width > 0, EINVAL);
    _VALIDATE_RETURN_VOID(comp != NULL, EINVAL);

    if (num < 2)
        return;                 /* nothing to do */

    stkptr = 0;                 /* initialize stack */

    lo = (char *)base;
    hi = (char *)base + width * (num-1);        /* initialize limits */

    /* this entry point is for pseudo-recursion calling: setting
       lo and hi and jumping to here is like recursion, but stkptr is
       preserved, locals aren't, so we preserve stuff on the stack */
recurse:

    size = (hi - lo) / width + 1;        /* number of el's to sort */

    /* below a certain size, it is faster to use a O(n^2) sorting method */
    if (size <= CUTOFF) {
        __SHORTSORT(lo, hi, width, comp, context);
    }
    else {
        /* First we pick a partitioning element.  The efficiency of the
           algorithm demands that we find one that is approximately the median
           of the values, but also that we select one fast.  We choose the
           median of the first, middle, and last elements, to avoid bad
           performance in the face of already sorted data, or data that is made
           up of multiple sorted runs appended together.  Testing shows that a
           median-of-three algorithm provides better performance than simply
           picking the middle element for the latter case. */

        mid = lo + (size / 2) * width;      /* find middle element */

        /* Sort the first, middle, last elements into order */
        if (__COMPARE(context, lo, mid) > 0) {
            swap(lo, mid, width);
        }
        if (__COMPARE(context, lo, hi) > 0) {
            swap(lo, hi, width);
        }
        if (__COMPARE(context, mid, hi) > 0) {
            swap(mid, hi, width);
        }

        /* We now wish to partition the array into three pieces, one consisting
           of elements <= partition element, one of elements equal to the
           partition element, and one of elements > than it.  This is done
           below; comments indicate conditions established at every step. */

        loguy = lo;
        higuy = hi;

        /* Note that higuy decreases and loguy increases on every iteration,
           so loop must terminate. */
        for (;;) {
            /* lo <= loguy < hi, lo < higuy <= hi,
               A[i] <= A[mid] for lo <= i <= loguy,
               A[i] > A[mid] for higuy <= i < hi,
               A[hi] >= A[mid] */

            /* The doubled loop is to avoid calling comp(mid,mid), since some
               existing comparison funcs don't work when passed the same
               value for both pointers. */

            if (mid > loguy) {
                do  {
                    loguy += width;
                } while (loguy < mid && __COMPARE(context, loguy, mid) <= 0);
            }
            if (mid <= loguy) {
                do  {
                    loguy += width;
                } while (loguy <= hi && __COMPARE(context, loguy, mid) <= 0);
            }

            /* lo < loguy <= hi+1, A[i] <= A[mid] for lo <= i < loguy,
               either loguy > hi or A[loguy] > A[mid] */

            do  {
                higuy -= width;
            } while (higuy > mid && __COMPARE(context, higuy, mid) > 0);

            /* lo <= higuy < hi, A[i] > A[mid] for higuy < i < hi,
               either higuy == lo or A[higuy] <= A[mid] */

            if (higuy < loguy)
                break;

            /* if loguy > hi or higuy == lo, then we would have exited, so
               A[loguy] > A[mid], A[higuy] <= A[mid],
               loguy <= hi, higuy > lo */

            swap(loguy, higuy, width);

            /* If the partition element was moved, follow it.  Only need
               to check for mid == higuy, since before the swap,
               A[loguy] > A[mid] implies loguy != mid. */

            if (mid == higuy)
                mid = loguy;

            /* A[loguy] <= A[mid], A[higuy] > A[mid]; so condition at top
               of loop is re-established */
        }

        /*     A[i] <= A[mid] for lo <= i < loguy,
               A[i] > A[mid] for higuy < i < hi,
               A[hi] >= A[mid]
               higuy < loguy
           implying:
               higuy == loguy-1
               or higuy == hi - 1, loguy == hi + 1, A[hi] == A[mid] */

        /* Find adjacent elements equal to the partition element.  The
           doubled loop is to avoid calling comp(mid,mid), since some
           existing comparison funcs don't work when passed the same value
           for both pointers. */

        higuy += width;
        if (mid < higuy) {
            do  {
                higuy -= width;
            } while (higuy > mid && __COMPARE(context, higuy, mid) == 0);
        }
        if (mid >= higuy) {
            do  {
                higuy -= width;
            } while (higuy > lo && __COMPARE(context, higuy, mid) == 0);
        }

        /* OK, now we have the following:
              higuy < loguy
              lo <= higuy <= hi
              A[i]  <= A[mid] for lo <= i <= higuy
              A[i]  == A[mid] for higuy < i < loguy
              A[i]  >  A[mid] for loguy <= i < hi
              A[hi] >= A[mid] */

        /* We've finished the partition, now we want to sort the subarrays
           [lo, higuy] and [loguy, hi].
           We do the smaller one first to minimize stack usage.
           We only sort arrays of length 2 or more.*/

        if ( higuy - lo >= hi - loguy ) {
            if (lo < higuy) {
                lostk[stkptr] = lo;
                histk[stkptr] = higuy;
                ++stkptr;
            }                           /* save big recursion for later */

            if (loguy < hi) {
                lo = loguy;
                goto recurse;           /* do small recursion */
            }
        }
        else {
            if (loguy < hi) {
                lostk[stkptr] = loguy;
                histk[stkptr] = hi;
                ++stkptr;               /* save big recursion for later */
            }

            if (lo < higuy) {
                hi = higuy;
                goto recurse;           /* do small recursion */
            }
        }
    }

    /* We have sorted the array, except for any pending sorts on the stack.
       Check if there are any, and do them. */

    --stkptr;
    if (stkptr >= 0) {
        lo = lostk[stkptr];
        hi = histk[stkptr];
        goto recurse;           /* pop subarray from stack */
    }
    else
        return;                 /* all subarrays done */
}

  為了更好地理解回調函數,接下來我們來寫一個自己的qsort函數(利用冒泡排序)

 

int char_compare(void const * c1,void const* c2) //比較函數
{
    int a = *((int*)c1);
    int b = *((int*)c2);
    return a>b ? 1 : a<b ? -1 : 0;
}

void Swap(char *str1,char *str2,int size) 
{
    while (size--)
    {
        char tmp = *str1;
        *str1 = *str2;
        *str2 = tmp;
        str1++;str2++;
    }
}
void MyQsort(void *str,int len,int elen,int(*compare)(void const*,void const*))  //基於回調函數寫的排序算法
{
    int i = 0;
    int j = 0;
    int flag = 1;
    for (i=0; i<len-1; i++)
    {
        for (j=0; j<len-1-i; j++)
        {
            if (compare((char*)str+j*elen,(char*)str+(j+1)*elen)>0)
            {
                flag = 0;
                Swap((char*)str+j*elen,(char*)str+(j+1)*elen,elen);
            }
        }
        if (flag)
            return;
    }
}

 

看了例題在來說說原理

  簡而言之,回調函數就是一個通過函數指針調用的函數。如果你把函數的指針(地址)作為參數傳遞給另一個函數,當這個指針被用為調用它所指向的函數時,我 們就說這是回調函數。回調函數不是由該函數的實現方直接調用,而是在特定的事件或條件發生時由另外的一方調用的,用於對該事件或條件進行響應。

回調函數實現的機制是:

  (1)定義一個回調函數;

  (2)提供函數實現的一方在初始化的時候,將回調函數的函數指針注冊給調用者;

  (3)當特定的事件或條件發生的時候,調用者使用函數指針調用回調函數對事件進行處理。

 

看了兩個例子大家應該能理解回調函數了,如果還有什么問題可以私信我,建議把指針這節理解透徹,這是指針的

參考文獻:

Kenneth A.Reek 著  徐波 譯.c和指針.人民郵電出版社.2008

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM