一、回调函数:
1、定义:
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
2、qsort的模拟实现(回调函数的应用)
那么回调函数有什么实际性的作用呢?下面模拟库函数qsort来顺便了解回调函数的用法:(在这里使用冒泡排序来模拟)首先要了解什么是qsort:
上面的意思是:对数组的元素进行排序,使用函数确定顺序所排序的类型。依然从代码入手:
int my_cmp(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
void Swap(char* buf1, char* buf2, size_t width)
{
for (size_t i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
Bubble_Sort(void* base,size_t sz,size_t width,int (*cmp)(const void* e1,const void* e2))
{
for (size_t i = 0; i < sz - 1; i++)
{
for (size_t j = 0; j < sz - i - 1; j++)
{
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
}
}
}
void test1()
{
int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
Bobble_Sort(arr, sz, sizeof(arr[0]), my_cmp);
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
int main()
{
test1();
return 0;
}
如上所示:我们要想将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:
这是用来计算字符串长度的。
//计数
int my_strlen1(const char* src)
{
assert(&src);
int count = 0;
while (*src++)
count++;
return count;
}
//递归
int my_strlen2(const char* src)
{
assert(src);
if (*src != '\0')
return 1 + my_strlen2(src + 1);
else
return 0;
}
//指针-指针
int my_strlen3(const char* src)
{
assert(src);
const char* dest = src;
while (*dest)
{
dest++;
}
return dest - src;
}
int main()
{
char arr[] = "abcdefg";
int ret = my_strlen3(arr);
printf("%d\n", ret);
return 0;
}
如上用了三种方法分别模拟了字符串长度的计算。
2、strcpy:
这是将一个字符串拷贝到另外一个字符串中的
char* my_strcpy(char* dest, char* src)
{
char* ret = dest;
assert(dest && src);
while (*dest++ = *src++)
{
;
}
return ret;
}
int main()
{
char arr1[] = "abcdefg";
char arr2[20] = "";
my_strcpy(arr2, arr1);
printf("%s\n", arr1);
printf("%s\n", arr2);
return 0;
}
在模拟实现中将src一个个赋给dest,最后返回目标数组的地址
3、strcat:
这是将一个字符串追加在另外一个字符串后面。
char* my_strcat(char* dest, char* src)
{
assert(dest && src);
char* ret = dest;
//1.找到要追加的地方
while (*dest)
{
dest++;
}
//2.追加
while (*dest++ = *src++)
{
;
}
return ret;
}
int main()
{
char arr1[20] = "hello ";
char arr2[] = "ppr";
my_strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
首先找到我要追加的地方,然后进行赋值即可,最后返回被追加的数组首元素。
4、strcmp:
这是比较两个字符串的大小。
如上,如果第一个字符串小于后一个,那么返回一个小于0 的数;
如果第一个字符串等于后一个,那么返回0 ;
如果第一个字符串大于后一个,那么返回一个大于0 的数。
(在VS编译器中分别返回1,0,-1)。
int my_strcmp( char* str1, char* str2)
{
assert(str1 && str2);
while (*str1 == *str2)
{
if (*str1 == '\0')
return 0;
str1++;
str2++;
}
if (*str1 > *str2)
return 1;
else
return -1;
//return *str1 - *str2;
}
int main()
{
char arr1[20] = "abqf";
char arr2[20] = "abqf";
int ret = my_strcmp(arr1, arr2);
printf("%d\n", ret);
return 0;
}
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:
这个函数的作用是在一个字符串中找另外一个字符串。
char* my_strstr(const char* src1, const char* src2)
{
assert(src1 && src2);
if (*src2 == '\0')
return (char*)src1;
const char* s1 = src1;
const char* s2 = src2;
const char* p= src1;
while (*p)
{
s1 = p;
s2 = src2;
while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2)
{
s1++;
s2++;
}
if (*s2 == '\0')
return (char*)p;
p++;
}
return NULL;
}
int main()
{
char arr1[] = "abbbbcdef";
char arr2[] = "bbc";
char* arr3 = my_strstr(arr1, arr2);
if (arr3 == NULL)
{
printf("找不到\n");
}
else
{
printf("%s\n", arr3);
}
return 0;
}
思路:
在src1中找src2,第一个while循环将s1,s2分别指向src1和src2的第一位(这样可以使得每次找完可能相等的位置后,如果不相等就会返回此时记录的位置),之后来找可能相等的位置,在第二个while循环中来看看是否相等。
9、strtok:
这是属于一个字符串的分割,
第一个参数为我所需要分割的起始位置,
在后续的调用中在第一个参数位置只需传空指针即可。
第二个参数中传我所分割的标志集合。
10、strerror:
这是一个打印错误信息的函数,C语言的库函数在运行的时候,如果发生错误,就会将错误码存在一个变量中,这个变量是:errno(在errno.h这个头文件里面)
错误码是一些数字:1 2 3 4 5
我们需要将错误码翻译成错误信息
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
//perror("fopen");
return 1;
}
//读文件
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
在上面代码中,在当前路径下读一个叫做test.txt的文件,如果没有就会将错误代码打印出来
拓展:有另外一个函数perror这个用起来更加顺手些,将可以函数错误信息直接打印出来。
可以理解为:printf + strerror
11、memcpy:
这个函数也是拷贝,但是不只局限于字符串的拷贝,属于内存的拷贝,
第一个参数是目标拷贝函数,第二个参数是待拷贝的函数,第三个参数是拷贝多少个字节
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[20] = { 0 };
memcpy(arr2, arr1, 24);
return 0;
}
模拟实现:
void* my_memcpy(void* dest, void* src, size_t num)
{
void* ret = dest;
while (num--)
{
*(char*)dest = *(char*)src;
++(char*)dest;
++(char*)src;
}
return ret;
}
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[20] = { 0 };
my_memcpy(arr2, arr1, 24);
return 0;
}
我们这个自己模拟的局限性:不能够自我拷贝(但是库函数里的memcpy可以),那么接下来进行优化---------引入了memmove函数的模拟实现
12、memmove:
解决方法:如下图,若src在dest后面,就将src从前往后拷贝,若src在dest前面就从后往前拷贝。
void* my_memmove(void* dest, void* src, size_t num)
{
assert(dest && src);
void* ret = dest;
if (dest < src)
{
//前->后
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
else
{
//后->前
while (num--)
{
*((char*)dest + num) = *((char*)src + num);
}
}
}
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
my_memmove(arr1+2, arr1, 20);
return 0;
}