不愤不启,不悱不发。举一隅不以三隅反,则不复也。
目录
为什么要模拟库函数
我们都知道C语言给我们提供了很多库函数给我们使用,功能强大,使用方便。如此一来我们直接使用不就可以了吗,为啥还要模拟实现呢?正所谓知其然知其所以然,模拟库函数可以让我们进一步加深对某个函数的理解,它的参数、它的返回值为什么这样设计,等以后用起来便更加得心应手。
memmove模拟实现
它的功能是把一块内存空间的数据移动到另一块内存空间,并且允许这两个内存空间有重叠的部分。
包含它的头文件为 string.h,它的格式是这样的:
void * memmove ( void * destination, const void * source, size_t num );
可以看到,memmove有3个参数。
第一个参数是一个空指针,表示这块内存空间的数据要被移动到哪,destination即为目的地地址。
第二个参数也是一个空指针,表示这块内存空间的起始位置,source即为起始地址。
第三个参数的类型是一个无符号整型,表示这块内存空间的大小,单位是字节,num即为空间大小。
返回值是一个空指针,内容为目的地地址。
#include <stdio.h>
#include <assert.h>
void* my_memmove(void* dest, const void* src, size_t num)
{
assert(dest && src);//断言,判断空指针
char* x = (char*)dest;
char* y = (char*)src;
if (x > y)//目标地指针地址高于起始指针地址,起始指针内容从后往前覆盖目标指针内容
{
x = x + num - 1;
y = y + num - 1;
while (num--)
{
*x-- = *y--;
}
}
else
{
while (num--)
{
*x++ = *y++;
}
}
return dest;
}
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9 };
my_memmove(arr + 2, arr, 20);//20个字节,5个int
for (int i = 0; i < 9; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
memmove模拟实现的难点在于当两块内存空间有相互重叠的部分的时候,把起始指针的内容从前往后覆盖目标指针的内容时,会丢失重叠部分的数据,导致错误。
strcpy模拟实现
它的功能是把一个字符串复制给另一个字符串。
包含它的头文件为 string.h,它的格式是这样的:
char * strcpy ( char * destination, const char * source );
strcpy有两个参数,他们都是字符指针,destination为复制内容的接收方,source为复制内容的提供方。接收方的空间大小要大于等于提供方的空间大小,不然会造成越界写入,导致报错。
返回值也是字符指针,内容为接收方的起始地址。
#include <stdio.h>
#include <assert.h>
char* my_strcpy(char* dest, const char* src)
{
assert(dest && src);//断言,判断空指针
char* ret = dest;
while (*dest++ = *src++)
{
;
}
return ret;
}
int main()
{
char str1[] = "violet";
char str2[50] = { 0 };
printf("%s\n", my_strcpy(str2, str1));
return 0;
}
这里有一个小细节,复制什么时候停止呢?是当复制内容的提供方读到 '\0' 时停止,并把 ’\0' 也复制过去。代码中while的停止条件是赋值语句的返回值,当把 '\0' 赋值过去后,赋值语句的返回值为0,循环停止,这样就完成了整个字符串的复制。
strlen模拟实现
strlen函数是用来求字符串长度的,包含它的头文件为 string.h,它的格式为:
size_t strlen ( const char * str );
它的参数很简单,只有一个字符指针,返回值为一个无符号整型,表示这个字符串的长度。
#include <stdio.h>
#include <assert.h>
size_t my_strlen(const char* str)
{
assert(str);
size_t i = 0;
while (*str != '\0')
{
i++;
str++;
}
return i;
}
int main()
{
char str[] = "violet\0xyz";
printf("%zd\n", my_strlen(str));
return 0;
}
strlen计算字符串长度的时候到第一个 '\0' 就停止了,并且不把 '\0' 计算在内,所以打印的结果是6,而不是11。
strcat模拟实现
strcat是在一个字符串后面追加一个字符串,包含它的头文件为 string.h,它的格式为:
char * strcat ( char * destination, const char * source );
它有两个参数,类型都是字符指针,destination为追加内容的接收方,source为追加内容的提供方。接收方需要预留足够大的空间来接收提供方给予的字符串,否则会造成越界写入,导致报错。
#include <stdio.h>
#include <assert.h>
char* my_strcat(char* str1, const char* str2)
{
assert(str1 && str2);
char* ret = str1;
while (*str1)
{
str1++;
}
while (*str1++ = *str2++)
{
;
}
return ret;
}
int main()
{
char str1[] = "hello \0xxxxxxxx";
char str2[] = "world";
printf("%s\n", my_strcat(str1, str2));
return 0;
}
当读到str1的 '\0' 后,所要进行的操作就和strcpy一致了,所以直接使用strcpy的代码就行。
strstr模拟实现
strstr是用来判断一个字符串str1是否含有另一个字符串str2,如果含有,则返回第一次出现str2的地址,如果不含有,则返回NULL。
包含它的头文件为 string.h,它的格式为:
const char * strstr ( const char * str1, const char * str2 );
可以看到,strstr的两个参数都是不可修改的字符指针,我们可以使用两个可以修改的指针x1、x2来分别指向str1、str2,如果x1和x2相等则进入循环判断,这个时候我们还需要再创建一个指针y记录x1的位置,如果x2走完str2的内容,说明str1包含str2的内容,返回y即可。
#include <stdio.h>
#include <assert.h>
char* my_strstr(const char* str1, const char* str2)
{
assert(str1 && str2);
char* x1 = (char*)str1;
char* x2 = (char*)str2;
char* y = (char*)str1;
while (*y)
{
x1 = y;
while (*x1 == *x2 && *x1 != '\0')//不要忘了加 *x1 != '\0'
{
x1++;
x2++;
}
if (*x2 == '\0')
return y;
y++;
x2 = (char*)str2;
}
return NULL;
}
int main()
{
char str1[] = "abcbcd";
char str2[] = "bcd";
printf("%s\n", my_strstr(str1, str2));
return 0;
}