当前位置:   article > 正文

【C语言】回调函数 和 部分库函数的用法以及模拟实现

【C语言】回调函数 和 部分库函数的用法以及模拟实现

一、回调函数

1、定义:

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。

2、qsort的模拟实现(回调函数的应用)

那么回调函数有什么实际性的作用呢?下面模拟库函数qsort来顺便了解回调函数的用法:(在这里使用冒泡排序来模拟)
首先要了解什么是qsort:
上面的意思是:对数组的元素进行排序,使用函数确定顺序所排序的类型。
依然从代码入手:
  1. int my_cmp(const void* e1, const void* e2)
  2. {
  3. return *(int*)e1 - *(int*)e2;
  4. }
  5. void Swap(char* buf1, char* buf2, size_t width)
  6. {
  7. for (size_t i = 0; i < width; i++)
  8. {
  9. char tmp = *buf1;
  10. *buf1 = *buf2;
  11. *buf2 = tmp;
  12. buf1++;
  13. buf2++;
  14. }
  15. }
  16. Bubble_Sort(void* base,size_t sz,size_t width,int (*cmp)(const void* e1,const void* e2))
  17. {
  18. for (size_t i = 0; i < sz - 1; i++)
  19. {
  20. for (size_t j = 0; j < sz - i - 1; j++)
  21. {
  22. if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
  23. {
  24. Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
  25. }
  26. }
  27. }
  28. }
  29. void test1()
  30. {
  31. int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
  32. int sz = sizeof(arr) / sizeof(arr[0]);
  33. Bobble_Sort(arr, sz, sizeof(arr[0]), my_cmp);
  34. for (int i = 0; i < sz; i++)
  35. {
  36. printf("%d ", arr[i]);
  37. }
  38. }
  39. int main()
  40. {
  41. test1();
  42. return 0;
  43. }

如上所示:我们要想将arr数组排为升序,在模拟实现时,要给四个参数:

1、这个数组名

2、这个数组的元素个数

3、这个数组每个元素的大小(单位字节)

4、指向比较两个元素的函数的指针。(这个来控制是比较整型或者浮点型或者结构体等等)

接收的时候用void*接受是因为不知道我传过来的是什么类型的,在之后强制类型转即可。

上述代码中:
my_cmp为我要排序整型所需要的函数。
Swap为交换两个元素
Bubble_Sort为冒泡排序模拟库函数qsort
在其中:
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
    Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
这行代码是最核心的,
width为偏移量,这样可以通过 回调函数来调用my_cmp来知道我是什么类型的和大小。
将传过来的数组名用void* base接收,后来转换为(char*)这样可以+偏移量来找到每个元素,这也是要传数组每个元素的大小的意义。
在Swap函数中与传统的交换不同的是需要每一个元素的大小,这样可以将一个元素以字节为单位一个一个交换,毕竟如果直接交换的话,就会将代码写死(比如写了个int类型,就不能交换浮点数类型)

在上述代码中就在函数中调用了函数,所以就用了回调函数cmp

二、库函数的用法及部分模拟实现:

首先来了解一下有那些处理字符和字符串的库函数:

1、strlen:

这是用来计算字符串长度的。

  1. //计数
  2. int my_strlen1(const char* src)
  3. {
  4. assert(&src);
  5. int count = 0;
  6. while (*src++)
  7. count++;
  8. return count;
  9. }
  10. //递归
  11. int my_strlen2(const char* src)
  12. {
  13. assert(src);
  14. if (*src != '\0')
  15. return 1 + my_strlen2(src + 1);
  16. else
  17. return 0;
  18. }
  19. //指针-指针
  20. int my_strlen3(const char* src)
  21. {
  22. assert(src);
  23. const char* dest = src;
  24. while (*dest)
  25. {
  26. dest++;
  27. }
  28. return dest - src;
  29. }
  30. int main()
  31. {
  32. char arr[] = "abcdefg";
  33. int ret = my_strlen3(arr);
  34. printf("%d\n", ret);
  35. return 0;
  36. }

如上用了三种方法分别模拟了字符串长度的计算。

2、strcpy:

这是将一个字符串拷贝到另外一个字符串中的

  1. char* my_strcpy(char* dest, char* src)
  2. {
  3. char* ret = dest;
  4. assert(dest && src);
  5. while (*dest++ = *src++)
  6. {
  7. ;
  8. }
  9. return ret;
  10. }
  11. int main()
  12. {
  13. char arr1[] = "abcdefg";
  14. char arr2[20] = "";
  15. my_strcpy(arr2, arr1);
  16. printf("%s\n", arr1);
  17. printf("%s\n", arr2);
  18. return 0;
  19. }

在模拟实现中将src一个个赋给dest,最后返回目标数组的地址

3、strcat:

这是将一个字符串追加在另外一个字符串后面。

  1. char* my_strcat(char* dest, char* src)
  2. {
  3. assert(dest && src);
  4. char* ret = dest;
  5. //1.找到要追加的地方
  6. while (*dest)
  7. {
  8. dest++;
  9. }
  10. //2.追加
  11. while (*dest++ = *src++)
  12. {
  13. ;
  14. }
  15. return ret;
  16. }
  17. int main()
  18. {
  19. char arr1[20] = "hello ";
  20. char arr2[] = "ppr";
  21. my_strcat(arr1, arr2);
  22. printf("%s\n", arr1);
  23. return 0;
  24. }

首先找到我要追加的地方,然后进行赋值即可,最后返回被追加的数组首元素。

4、strcmp:

这是比较两个字符串的大小。

如上,如果第一个字符串小于后一个,那么返回一个小于0 的数;

           如果第一个字符串等于后一个,那么返回0 ;

           如果第一个字符串大于后一个,那么返回一个大于0 的数。

(在VS编译器中分别返回1,0,-1)。

  1. int my_strcmp( char* str1, char* str2)
  2. {
  3. assert(str1 && str2);
  4. while (*str1 == *str2)
  5. {
  6. if (*str1 == '\0')
  7. return 0;
  8. str1++;
  9. str2++;
  10. }
  11. if (*str1 > *str2)
  12. return 1;
  13. else
  14. return -1;
  15. //return *str1 - *str2;
  16. }
  17. int main()
  18. {
  19. char arr1[20] = "abqf";
  20. char arr2[20] = "abqf";
  21. int ret = my_strcmp(arr1, arr2);
  22. printf("%d\n", ret);
  23. return 0;
  24. }

5、strncpy:

这些长度受限制的函数引入是因为strcpy函数不安全,

比如在strcpy函数中,如果拷贝过去字符串,此时接收这个字符串的数组不够这么大,就会失败,同样,如果c语言中最开始不声明“#define _CRT_SECURE_NO_WARNINGS 1”就会不可以使用,所以就衍生出了strncpy,这个作用和strcpy几乎是一模一样的,就是多了1个无符号整型参数,作用是我要拷贝过来几个字符。

但是strncpy只是相对于strcpy安全罢了,如果你输入的数字等于这个数组的最大元素,那么就会使‘\0’拷贝不过来,也会出现错误:

所以strncpy只是相对于strcpy安全。

6、strncat:

这个多出来的无符号整型的参数就是我需要追加几个字符数。

7、strncmp:

这个多出来的无符号整型的参数就是我需要比较几个字符数。

8、strstr:

这个函数的作用是在一个字符串中找另外一个字符串。

  1. char* my_strstr(const char* src1, const char* src2)
  2. {
  3. assert(src1 && src2);
  4. if (*src2 == '\0')
  5. return (char*)src1;
  6. const char* s1 = src1;
  7. const char* s2 = src2;
  8. const char* p= src1;
  9. while (*p)
  10. {
  11. s1 = p;
  12. s2 = src2;
  13. while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2)
  14. {
  15. s1++;
  16. s2++;
  17. }
  18. if (*s2 == '\0')
  19. return (char*)p;
  20. p++;
  21. }
  22. return NULL;
  23. }
  24. int main()
  25. {
  26. char arr1[] = "abbbbcdef";
  27. char arr2[] = "bbc";
  28. char* arr3 = my_strstr(arr1, arr2);
  29. if (arr3 == NULL)
  30. {
  31. printf("找不到\n");
  32. }
  33. else
  34. {
  35. printf("%s\n", arr3);
  36. }
  37. return 0;
  38. }

思路:

在src1中找src2,第一个while循环将s1,s2分别指向src1和src2的第一位(这样可以使得每次找完可能相等的位置后,如果不相等就会返回此时记录的位置),之后来找可能相等的位置,在第二个while循环中来看看是否相等。

9、strtok:

这是属于一个字符串的分割,

第一个参数为我所需要分割的起始位置,

在后续的调用中在第一个参数位置只需传空指针即可。

第二个参数中传我所分割的标志集合。

10、strerror:

这是一个打印错误信息的函数,C语言的库函数在运行的时候,如果发生错误,就会将错误码存在一个变量中,这个变量是:errno(在errno.h这个头文件里面)

错误码是一些数字:1 2 3 4 5

我们需要将错误码翻译成错误信息

  1. int main()
  2. {
  3. FILE* pf = fopen("test.txt", "r");
  4. if (pf == NULL)
  5. {
  6. printf("%s\n", strerror(errno));
  7. //perror("fopen");
  8. return 1;
  9. }
  10. //读文件
  11. //关闭文件
  12. fclose(pf);
  13. pf = NULL;
  14. return 0;
  15. }

在上面代码中,在当前路径下读一个叫做test.txt的文件,如果没有就会将错误代码打印出来

拓展:有另外一个函数perror这个用起来更加顺手些,将可以函数错误信息直接打印出来。

可以理解为:printf + strerror

11、memcpy:

这个函数也是拷贝,但是不只局限于字符串的拷贝,属于内存的拷贝,

第一个参数是目标拷贝函数,第二个参数是待拷贝的函数,第三个参数是拷贝多少个字节

  1. int main()
  2. {
  3. int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
  4. int arr2[20] = { 0 };
  5. memcpy(arr2, arr1, 24);
  6. return 0;
  7. }

模拟实现:

  1. void* my_memcpy(void* dest, void* src, size_t num)
  2. {
  3. void* ret = dest;
  4. while (num--)
  5. {
  6. *(char*)dest = *(char*)src;
  7. ++(char*)dest;
  8. ++(char*)src;
  9. }
  10. return ret;
  11. }
  12. int main()
  13. {
  14. int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
  15. int arr2[20] = { 0 };
  16. my_memcpy(arr2, arr1, 24);
  17. return 0;
  18. }

我们这个自己模拟的局限性:不能够自我拷贝(但是库函数里的memcpy可以),那么接下来进行优化---------引入了memmove函数的模拟实现

12、memmove:

解决方法:如下图,若src在dest后面,就将src从前往后拷贝,若src在dest前面就从后往前拷贝。

  1. void* my_memmove(void* dest, void* src, size_t num)
  2. {
  3. assert(dest && src);
  4. void* ret = dest;
  5. if (dest < src)
  6. {
  7. //前->后
  8. while (num--)
  9. {
  10. *(char*)dest = *(char*)src;
  11. dest = (char*)dest + 1;
  12. src = (char*)src + 1;
  13. }
  14. }
  15. else
  16. {
  17. //后->前
  18. while (num--)
  19. {
  20. *((char*)dest + num) = *((char*)src + num);
  21. }
  22. }
  23. }
  24. int main()
  25. {
  26. int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
  27. my_memmove(arr1+2, arr1, 20);
  28. return 0;
  29. }
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/寸_铁/article/detail/756221
推荐阅读
相关标签
  

闽ICP备14008679号