什么是回调函数?

﹏ヽ暗。殇╰゛Y 2022-02-23 12:22 501阅读 0赞

原文地址:https://www.cnblogs.com/zhonglongbo/p/8410464.html

今天看到回调函数,有点迷糊,找了好多搜索引擎的资料,都不是让我很能理解,看了《c和指针》我才明白了。

简单描述一下什么是回调函数:

  用户把一个函数指针作为参数传递给其他函数,后者将“回调”用户的函数。如果函数可以再不同的时间执行不同类型的工作或者执行只能由函数调用者定义的工作,都可以使用回调函数。 回调函数无法知道比较的值的类型,所以参数的类型被声明为void*。表示一个指向未知类型的指针。 可以通过函数指针来实现回调函数。一个指向回调函数的指针作为参数传递给另一个函数,后者使用这个指针调用回调函数。

  可能说了太多定义也不会很是明白,来几个事例说说。

  当我们在在链表中查找一个数时,我们一般会这样写:

复制代码

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

复制代码

这样就限制我们只能在查找的数必须是int类型,当变为其他类型时我们就无法用这个函数,但是重新写一个函数,他们重复代码又太多。那我们看看用回调函数如何办到。  

回调函数查找:

复制代码

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

复制代码

复制代码

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

复制代码

 这样利用回调函数就可以解决如上问题。我们把一个函数指针( int (*compare)(void const *, void const*) )作为参数传递给查找函数,查找函数将“回调”比较函数。当我们需要执行不同类型的比较时我们合理调用该函数。例如:当我们整形查找时: search_list( root, &desired_value, compare_int ); ,使用字符查找时: search_list( root, &desired_value, compare_char ); 。这就是回调函数简单的应用,当然回调函数不仅仅只是用于这些简单的例子,比如库函数qsort就是利用回调函数实现。

  函数原型如下:

复制代码

  1. void qsort(
  2. void *base, //字符串首地址
  3. size_t num, //排序总个数
  4. size_t width, //排序元素的大小
  5. int (__cdecl *compare )(const void *, const void *) //函数指针
  6. );

复制代码

  库函数实现:

复制代码

  1. void qsort(
  2. void *base, //字符串首地址
  3. size_t num, //排序总个数
  4. size_t width, //排序元素的大小
  5. int (__cdecl *compare )(const void *, const void *) //函数指针
  6. );
  7. {
  8. char *lo, *hi; /* ends of sub-array currently sorting */
  9. char *mid; /* points to middle of subarray */
  10. char *loguy, *higuy; /* traveling pointers for partition step */
  11. size_t size; /* size of the sub-array */
  12. char *lostk[STKSIZ], *histk[STKSIZ];
  13. int stkptr; /* stack for saving sub-array to be processed */
  14. /* validation section */
  15. _VALIDATE_RETURN_VOID(base != NULL || num == 0, EINVAL);
  16. _VALIDATE_RETURN_VOID(width > 0, EINVAL);
  17. _VALIDATE_RETURN_VOID(comp != NULL, EINVAL);
  18. if (num < 2)
  19. return; /* nothing to do */
  20. stkptr = 0; /* initialize stack */
  21. lo = (char *)base;
  22. hi = (char *)base + width * (num-1); /* initialize limits */
  23. /* this entry point is for pseudo-recursion calling: setting
  24. lo and hi and jumping to here is like recursion, but stkptr is
  25. preserved, locals aren't, so we preserve stuff on the stack */
  26. recurse:
  27. size = (hi - lo) / width + 1; /* number of el's to sort */
  28. /* below a certain size, it is faster to use a O(n^2) sorting method */
  29. if (size <= CUTOFF) {
  30. __SHORTSORT(lo, hi, width, comp, context);
  31. }
  32. else {
  33. /* First we pick a partitioning element. The efficiency of the
  34. algorithm demands that we find one that is approximately the median
  35. of the values, but also that we select one fast. We choose the
  36. median of the first, middle, and last elements, to avoid bad
  37. performance in the face of already sorted data, or data that is made
  38. up of multiple sorted runs appended together. Testing shows that a
  39. median-of-three algorithm provides better performance than simply
  40. picking the middle element for the latter case. */
  41. mid = lo + (size / 2) * width; /* find middle element */
  42. /* Sort the first, middle, last elements into order */
  43. if (__COMPARE(context, lo, mid) > 0) {
  44. swap(lo, mid, width);
  45. }
  46. if (__COMPARE(context, lo, hi) > 0) {
  47. swap(lo, hi, width);
  48. }
  49. if (__COMPARE(context, mid, hi) > 0) {
  50. swap(mid, hi, width);
  51. }
  52. /* We now wish to partition the array into three pieces, one consisting
  53. of elements <= partition element, one of elements equal to the
  54. partition element, and one of elements > than it. This is done
  55. below; comments indicate conditions established at every step. */
  56. loguy = lo;
  57. higuy = hi;
  58. /* Note that higuy decreases and loguy increases on every iteration,
  59. so loop must terminate. */
  60. for (;;) {
  61. /* lo <= loguy < hi, lo < higuy <= hi,
  62. A[i] <= A[mid] for lo <= i <= loguy,
  63. A[i] > A[mid] for higuy <= i < hi,
  64. A[hi] >= A[mid] */
  65. /* The doubled loop is to avoid calling comp(mid,mid), since some
  66. existing comparison funcs don't work when passed the same
  67. value for both pointers. */
  68. if (mid > loguy) {
  69. do {
  70. loguy += width;
  71. } while (loguy < mid && __COMPARE(context, loguy, mid) <= 0);
  72. }
  73. if (mid <= loguy) {
  74. do {
  75. loguy += width;
  76. } while (loguy <= hi && __COMPARE(context, loguy, mid) <= 0);
  77. }
  78. /* lo < loguy <= hi+1, A[i] <= A[mid] for lo <= i < loguy,
  79. either loguy > hi or A[loguy] > A[mid] */
  80. do {
  81. higuy -= width;
  82. } while (higuy > mid && __COMPARE(context, higuy, mid) > 0);
  83. /* lo <= higuy < hi, A[i] > A[mid] for higuy < i < hi,
  84. either higuy == lo or A[higuy] <= A[mid] */
  85. if (higuy < loguy)
  86. break;
  87. /* if loguy > hi or higuy == lo, then we would have exited, so
  88. A[loguy] > A[mid], A[higuy] <= A[mid],
  89. loguy <= hi, higuy > lo */
  90. swap(loguy, higuy, width);
  91. /* If the partition element was moved, follow it. Only need
  92. to check for mid == higuy, since before the swap,
  93. A[loguy] > A[mid] implies loguy != mid. */
  94. if (mid == higuy)
  95. mid = loguy;
  96. /* A[loguy] <= A[mid], A[higuy] > A[mid]; so condition at top
  97. of loop is re-established */
  98. }
  99. /* A[i] <= A[mid] for lo <= i < loguy,
  100. A[i] > A[mid] for higuy < i < hi,
  101. A[hi] >= A[mid]
  102. higuy < loguy
  103. implying:
  104. higuy == loguy-1
  105. or higuy == hi - 1, loguy == hi + 1, A[hi] == A[mid] */
  106. /* Find adjacent elements equal to the partition element. The
  107. doubled loop is to avoid calling comp(mid,mid), since some
  108. existing comparison funcs don't work when passed the same value
  109. for both pointers. */
  110. higuy += width;
  111. if (mid < higuy) {
  112. do {
  113. higuy -= width;
  114. } while (higuy > mid && __COMPARE(context, higuy, mid) == 0);
  115. }
  116. if (mid >= higuy) {
  117. do {
  118. higuy -= width;
  119. } while (higuy > lo && __COMPARE(context, higuy, mid) == 0);
  120. }
  121. /* OK, now we have the following:
  122. higuy < loguy
  123. lo <= higuy <= hi
  124. A[i] <= A[mid] for lo <= i <= higuy
  125. A[i] == A[mid] for higuy < i < loguy
  126. A[i] > A[mid] for loguy <= i < hi
  127. A[hi] >= A[mid] */
  128. /* We've finished the partition, now we want to sort the subarrays
  129. [lo, higuy] and [loguy, hi].
  130. We do the smaller one first to minimize stack usage.
  131. We only sort arrays of length 2 or more.*/
  132. if ( higuy - lo >= hi - loguy ) {
  133. if (lo < higuy) {
  134. lostk[stkptr] = lo;
  135. histk[stkptr] = higuy;
  136. ++stkptr;
  137. } /* save big recursion for later */
  138. if (loguy < hi) {
  139. lo = loguy;
  140. goto recurse; /* do small recursion */
  141. }
  142. }
  143. else {
  144. if (loguy < hi) {
  145. lostk[stkptr] = loguy;
  146. histk[stkptr] = hi;
  147. ++stkptr; /* save big recursion for later */
  148. }
  149. if (lo < higuy) {
  150. hi = higuy;
  151. goto recurse; /* do small recursion */
  152. }
  153. }
  154. }
  155. /* We have sorted the array, except for any pending sorts on the stack.
  156. Check if there are any, and do them. */
  157. --stkptr;
  158. if (stkptr >= 0) {
  159. lo = lostk[stkptr];
  160. hi = histk[stkptr];
  161. goto recurse; /* pop subarray from stack */
  162. }
  163. else
  164. return; /* all subarrays done */
  165. }

复制代码

  为了更好地理解回调函数,接下来我们来写一个自己的qsort函数(利用冒泡排序)

复制代码

  1. int char_compare(void const * c1,void const* c2) //比较函数
  2. {
  3. int a = *((int*)c1);
  4. int b = *((int*)c2);
  5. return a>b ? 1 : a<b ? -1 : 0;
  6. }
  7. void Swap(char *str1,char *str2,int size)
  8. {
  9. while (size--)
  10. {
  11. char tmp = *str1;
  12. *str1 = *str2;
  13. *str2 = tmp;
  14. str1++;str2++;
  15. }
  16. }
  17. void MyQsort(void *str,int len,int elen,int(*compare)(void const*,void const*)) //基于回调函数写的排序算法
  18. {
  19. int i = 0;
  20. int j = 0;
  21. int flag = 1;
  22. for (i=0; i<len-1; i++)
  23. {
  24. for (j=0; j<len-1-i; j++)
  25. {
  26. if (compare((char*)str+j*elen,(char*)str+(j+1)*elen)>0)
  27. {
  28. flag = 0;
  29. Swap((char*)str+j*elen,(char*)str+(j+1)*elen,elen);
  30. }
  31. }
  32. if (flag)
  33. return;
  34. }
  35. }

复制代码

看了例题在来说说原理

  简而言之,回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我 们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

回调函数实现的机制是:

  (1)定义一个回调函数;

  (2)提供函数实现的一方在初始化的时候,将回调函数的函数指针注册给调用者;

  (3)当特定的事件或条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理。

看了两个例子大家应该能理解回调函数了,如果还有什么问题可以私信我,建议把指针这节理解透彻,这是指针的

参考文献:

Kenneth A.Reek 著 徐波 译.c和指针.人民邮电出版社.2008

发表评论

表情:
评论列表 (有 0 条评论,501人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Java中的函数什么?

    在Java中,回调函数是一种常见的编程模式,也称为回调机制。它允许将一段代码作为参数传递给另一个方法,并在需要时执行。回调函数通常用于异步编程或事件处理,可以将程序的控制权转移

    相关 什么函数

    1 定义和使用场合 回调函数是指 使用者自己定义一个函数,实现这个函数的程序内容,然后把这个函数(入口地址)作为参数传入别人(或系统)的函数中,由别人(或系统)的函数在运行时

    相关 什么事件

    第一次听见事件回调是在看netty那本书时书中所提到的,这一次又看到了相关概念,决定进行一个挖掘和学习: 回调的分类:   同步回调   异步回调 同步回调是在事件发生