飞道的博客

还在自己写排序的函数吗?快来通过回调函数学习并模拟库函数 qsort 的实现把

288人阅读  评论(0)

目录

一.回调函数:

        1.回调函数的定义:

        2.回调函数的使用:

        3.qsort函数的使用:

        4.利用回调函数模拟实现qsort函数:

二.总结: 


博客主页:张栩睿的博客主页

欢迎关注:点赞+收藏+留言

系列专栏:c语言学习

        家人们写博客真的很花时间的,你们的点赞和关注对我真的很重要,希望各位路过的朋友们能多多点赞并关注我,我会随时互关的,欢迎你们的私信提问,也期待你们的转发!

        希望大家关注我,你们将会看到更多精彩的内容!!!

一.回调函数:

        1.回调函数的定义:

        我们都知道,函数的调用除了我们最基础的调用方式之外,通过函数指针我们也可以实现对函数的调用。函数指针调用函数的函数,就称为回调函数。

        如果你把函数的地址作为参数(即使用指针)传递给另一个函数 ,且当这个指针调用了它指向的函数时,我们就把这样的使用方式称作回调函数。

        2.回调函数的使用:

实例1:


  
  1. //函数1:
  2. //函数1使用了这样的调用方式,就被称为回调函数
  3. void test( )
  4. {
  5. printf( "test\n");
  6. }
  7. //函数2:
  8. //使用函数指针接收,并对函数test进行调用:
  9. void TEST( void(*p)())
  10. {
  11. //使用函数指指针调用test函数:
  12. p();
  13. printf( "TEST\n");
  14. }
  15. int main( )
  16. {
  17. //将函数地址传递给另一个函数:
  18. TEST(test);
  19. return 0;
  20. }

        在这个过程中函数 test 就被作为函数参数传递了出去,并且在函数 TEST 中被函数指针接收,且该指针调用了它指向的 test 函数,于是我们就可以说 test 函数是回调函数。

实例2:

        我们之前通过转移表简化了计算器代码的书写计算器的书写,我们是否可以用回调函数简化呢?


  
  1. void menu()
  2. {
  3. printf( "*******************************\n");
  4. printf( "****** 1. add 2. sub *****\n");
  5. printf( "****** 3. mul 4. div *****\n");
  6. printf( "****** 0. exit *****\n");
  7. printf( "*******************************\n");
  8. }
  9. int Add(int x, int y)
  10. {
  11. return x + y;
  12. }
  13. int Sub(int x, int y)
  14. {
  15. return x - y;
  16. }
  17. int Mul(int x, int y)
  18. {
  19. return x * y;
  20. }
  21. int Div(int x, int y)
  22. {
  23. return x / y;
  24. }
  25. void calc(int (*pf)(int, int))
  26. {
  27. int x = 0;
  28. int y = 0;
  29. int ret = 0;
  30. printf( "请输入两个操作数:>");
  31. scanf( "%d %d", &x, &y);
  32. ret = pf(x, y);
  33. printf( "%d\n", ret);
  34. }
  35. int main()
  36. {
  37. int input = 0;
  38. do
  39. {
  40. menu();
  41. printf( "请选择:>");
  42. scanf( "%d", &input);
  43. switch (input)
  44. {
  45. case 1:
  46. calc(Add);
  47. break;
  48. case 2:
  49. calc(Sub);
  50. break;
  51. case 3:
  52. calc(Mul);
  53. break;
  54. case 4:
  55. calc(Div);
  56. break;
  57. case 0:
  58. printf( "退出计算器\n");
  59. break;
  60. default:
  61. printf( "选择错误\n");
  62. break;
  63. }
  64. } while (input);
  65. return 0;
  66. }

  3.qsort函数的使用:

        我们之前学过使用冒泡排序,用冒泡排序写一个函数来对整形数组进行升序降序,但是局限就是只能对整形,对其他类型无法排序。而qsort函数对于任意类型都可以排序,整形,字符型,甚至结构体型都可以,那么我们该如何·使用·qsotr函数呢?

        库函数 qsort 是基于快速排序法实现的一个排序函数。

我们在cplusplus上看看用法:

 函数括号里到底传入了什么参数呢?

 这里的compare函数如何书写呢?

不难发现,在升序排序中,compare是将两个要排序的元素的地址作为参数,将他们比较大小的差作为返回值。

如果结果>0,返回正数

如果结果=0.返回0

如果结果<0,返回负数 

那么就会有同学问了,那么降序怎么办?

如果降序的话就让两个值交换位置相减即可。

那么就会有同学问了,那个void*是什么类型,不同类型如何转化

 下面我们就来看看结构体的排序吧!


  
  1. struct Stu
  2. {
  3. char name[ 20];
  4. int age;
  5. };
  6. //按照学生的年龄来排序
  7. int cmp_stu_by_age(const void* e1, const void* e2)
  8. {
  9. return (( struct Stu*)e1)->age - (( struct Stu*)e2)->age;
  10. }
  11. //按学生的姓名排序
  12. int cmp_stu_by_name(const void* e1, const void* e2)
  13. {
  14. return strcmp((( struct Stu*)e1)->name, (( struct Stu*)e2)->name);
  15. }
  16. void test2()
  17. {
  18. struct Stu s[ 3] = { { "zhangsan", 20}, { "lisi", 50}, { "wangwu", 33} };
  19. int sz = sizeof(s) / sizeof(s[ 0]);
  20. //qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
  21. qsort(s, sz, sizeof(s[ 0]), cmp_stu_by_name);
  22. }

   这里需要注意的是strcmp的使用,他对于两个字符串的每个字符一一对比,如果大于返回正数,等于就往后继续比,小于就返回负数。这和我们要返回的数值正好对应

下面,我们就通过冒泡排序来实现qsort函数吧!

4.利用回调函数模拟实现qsort函数:

首先我们回忆一下冒泡排序:


  
  1. #include <stdio.h>
  2. void bubble_sort(int arr[],int sz)
  3. {
  4. int i = 0;
  5. for (i = 0; i < sz - 1; i++)
  6. {
  7. int j = 0;
  8. for (j = 0; j < sz - i - 1; j++)
  9. {
  10. if (arr[j] > arr[j + 1])
  11. {
  12. int tmp = arr[j];
  13. arr[j] = arr[j + 1];
  14. arr[j + 1] = tmp;
  15. }
  16. }
  17. }
  18. }
  19. int main()
  20. {
  21. int arr[] = { 1, 2, 3, 4, 0, 8, 9, 7, 6, 5 };
  22. int sz = sizeof(arr) / sizeof(arr[ 0]);
  23. bubble_sort(arr, sz);
  24. int i = 0;
  25. for (i = 0; i < sz; i++)
  26. {
  27. printf( "%d ", arr[i]);
  28. }
  29. return 0;
  30. }

这样的冒泡排序已经写死了整形,不能比较其他类型。

我们设计一个兼容可以排序整型,又可以排字符型和结构体的呢

模拟qsort的思想,利用冒泡的形式


  
  1. void swap(char* buf1, char* buf2, int width)
  2. {
  3. int i = 0;
  4. for (i = 0; i < width; i++)
  5. {
  6. char tmp = *buf1;
  7. *buf1 = *buf2;
  8. *buf2 = tmp;
  9. buf1++;
  10. buf2++;
  11. }
  12. }
  13. int cmp_int(void* e1, void* e2)
  14. {
  15. return *( int*)e1 - *( int*)e2;
  16. }
  17. void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void* e1, const void* e2))
  18. {
  19. int i = 0, j = 0;
  20. for (i = 0; i < sz - 1; i++)
  21. {
  22. for (j = 0; j < sz - 1 - i; j++)
  23. {
  24. if (cmp(( char*) base + j * width, ( char*) base + (j + 1) * width) > 0)
  25. {
  26. swap(( char*) base + j * width, ( char*) base + (j + 1) * width,width);
  27. }
  28. }
  29. }
  30. }
  31. int main()
  32. {
  33. //整数:
  34. int arr[ 10] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
  35. int sz = sizeof(arr) / sizeof(arr[ 0]);
  36. bubble_sort(arr, sz, sizeof(arr[ 0]), cmp_int);
  37. for ( int i = 0; i < sz; i++)
  38. printf( "%d ",arr[i]);
  39. }

这里有两处非常重要的点:

首先是这个冒泡排序:


   
  1. for (i = 0; i < sz - 1; i++)
  2. {
  3. for (j = 0; j < sz - 1 - i; j++)
  4. {
  5. if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
  6. {
  7. swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
  8. }
  9. }
  10. }

        因为我们不知道我们会对什么类型的变量排序,所以我们用char*强转指针,使这个指针的步长为一,后面就可以显现出width(类型大小)的作用了:

        因为我们不知道类型不能用[]下标访问传过来的数组,就只能用指针的形式,通过指针+-整数来访问这个数组的每个元素。我们根据+width的倍数,正好就是这个类型的步长。

其次是这个swap交换函数:


   
  1. void swap(char* buf1, char* buf2, int width)
  2. {
  3. int i = 0;
  4. for (i = 0; i < width; i++)
  5. {
  6. char tmp = *buf1;
  7. *buf1 = *buf2;
  8. *buf2 = tmp;
  9. buf1++;
  10. buf2++;
  11. }
  12. }

        由于我们不知道要交换元素类型,所以我们通过变量中拥有最小单位:一个字节的char类型来依次交换每个字节来实现元素的交换,这样是不是很简便呢?

 

 

我们在来看一看结构体类型的交换:


  
  1. void swap(char* buf1, char* buf2, int width)
  2. {
  3. for ( int i = 0; i < width; i++)
  4. {
  5. char tmp = *buf1;
  6. *buf1 = *buf2;
  7. *buf2 = tmp;
  8. buf1++;
  9. buf2++;
  10. }
  11. }
  12. struct stu
  13. {
  14. char name[ 20];
  15. int age;
  16. };
  17. int cmp_stu_by_name(void* e1, void* e2)
  18. {
  19. return (strcmp((( struct stu*)e1)->name, (( struct stu*)e2)->name));
  20. }
  21. void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(void* e1, void* e2))//这里等下看一下
  22. {
  23. int i, j;
  24. for (i = 0; i < sz; i++)
  25. {
  26. for (j = 0; j < sz - 1 - i; j++)
  27. {
  28. if (cmp(( char*) base + j * width, ( char*) base + (j + 1) * width) > 0)
  29. {
  30. swap(( char*) base + j * width, ( char*) base + (j + 1) * width,width);
  31. }
  32. }
  33. }
  34. }
  35. int main()
  36. {
  37. //结构体排序
  38. struct stu s[ 3] = { { "zhangsan", 20}, { "lisi", 50}, { "wangwu", 33} };
  39. int sz = sizeof(s) / sizeof(s[ 0]);
  40. //bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_age);
  41. bubble_sort(s, sz, sizeof(s[ 0]), cmp_stu_by_name);
  42. }

 结果:

        不难发现,bubble_sort函数,swap函数都一模一样,都是写好了的,所以我们只要自己写出那个回调函数cmp函数(比较大小) 即可,这就是qsort的方便之处。

二.总结: 

        通过qsort函数的学习,我们对回调函数有了更深的理解。这里我们在复习一下qsort里的参数。

 

   今天我们算是给我们的指针升级之路画上了一个完美的句号,至此我们关于指针的进阶的学习就告一段落了。 辛苦各位小伙伴们动动小手,三连走一波 最后,本文仍有许多不足之处,欢迎各位认真读完文章的小伙伴们随时私信交流、批评指正!


转载:https://blog.csdn.net/qq_74310471/article/details/128597957
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场