试着去模拟实现一些库函数,可以帮助我们更好的理解编程思想,巩固我们的知识。有不少朋友可能和我一样,刚开始尝试,会遇到很多问题,但是没关系,迈过这一步,你的能力就会有质的提升。
推荐一个网站查库函数的使用,里面是简洁的中文介绍,附带例子,对英语不友好的朋友佷方便。
C 标准库 -
strncat 是我最开始尝试实现的一个字符操作函数,其实并不难,就是用到了一些指针的知识,完全理解后就可以触类旁通的去实现其他函数了。
描述
把 src 所指向的字符串追加到 dest 所指向的字符串的结尾,直到 n 字符长度为止。
声明
下面是 strncat() 函数的声明。
char *strncat(char *dest, const char *src, size_t n)
参数
返回值
该函数返回一个指向最终的目标字符串 dest 的指针。
一个函数的实现主要看实现的功能、返回值、传递的参数,库函数已经把这些内容都描述的很清楚了,我就不再赘述。
函数返回一个指向目标的指针,需要传递的有要追加的字符串地址、目标字符串地址、传递的字符数三个形式参数,我们不妨照猫画虎声明一个自己写的函数:
char *my_strncat(char *dest, const char *src, size_t n)
接下来就可以根据函数功能的描述去实现函数体了。要往目标字符串后添加内容,需要先对 ‘\0’ 有一个明确的认识。
- ’\0’ 是判定字符数组结束的标识,表示这串字符到结尾了;
- 在字符数组中 ’\0’ 是占一个位置的;
- ’\0’在数组中占有空间却不被我们看到;
有了这个理解,我们自然思路就清晰了,从目标数组的停止标志位 ’\0’ 开始,依次替换成需要添加的字符串,循环n次,并最终返回最初目标字符串的地址即可达成目的。需要注意的是,目标字符串的空间要足够大,以保证接受完添加的字符后不会越界。接下来用函数实现上述思路:
#define _CRT_SECURE_NO_WARNINGS 1
#include
#include
char* my_strncat(char* dest, const char* src, size_t n)
//src不涉及修改,可以用const修饰成常量
{
assert(dest != NULL);
assert(src != NULL);//先断言,确保两个字符串地址不是空指针
char* start = dest; //先保存dest起始地址,因为后面地址会改变
while (*dest)
{
dest++;
}
while (n--)
{
*dest++ = *src++;
}
return start;
}
int main()
{
char arr1[10] = "hello ";
char arr2[10] = "world";
my_strncat(arr1, arr2, 3);//数组名就是首元素地址
printf("%s\n", arr1);
return 0;
}
输出结果;
有两个while循环虽然看起来很简单,但信息量极大,对于小白来说可能读起来略微吃力,我需要解释一下:
while (*dest)
{
dest++;
}
先理解一些小细节
这个while循环的意思就是遍历dest每个字符,遍历到’\0’时循环结束,此时dest指向’\0’。
while (*dest++)
{
;
}
可能会有人想到用这个方法,找到’ \0 ’ ,其实是错误的,涉及到后置++的用法(先使用,后+1),当dest=’ \0 '时循环确实停止了,但是dest还会+1指向下一个元素 ,因为while判断条件算做使用一次。这样做的后果就是dest的停止标志位还在,往后添加任何字符,最终都看不到。
while (n--)
{
*dest++ = *src++;
}
避免错误后,我们继续,到第二个循环时,dest指针指向 ‘\0’ ,接下来依次将src的字符传递给dest。
第一次循环时:
因为后置++是先使用再+1的操作,所以可以分解成以下几步
*dest = '\0';
*src = 'w';
*dest = *src = 'w';
*src = *src+1;
*dest = *dest + 1;
后面循环依次类推,共循环n次,也就是在dest后面添加了n个字符。因为返回的是一开始保存的dest数组的首元素地址,所以通过地址就可以找到修改后的字符串啦。
如果你能完全理解这个模拟函数的实现,那么后面几个函数对你来说已经不是什么难题了,你可以试着先查看库函数的使用,然后自己实现看看,如果不会可以接着往下看。
描述
把 src 所指向的字符串追加到 dest 所指向的字符串的结尾。
声明
下面是 strcat() 函数的声明。
char *strcat(char *dest, const char *src)
参数
返回值
该函数返回一个指向最终的目标字符串 dest 的指针。
strcat的实现和strncat差不多,只不过是往后添加时的停止判断条件改为遇到src的停止标志位 ’ \0 '时,添加字符结束。
函数声明
char* my_strcat(char* dest, const char* src)
函数实现:
#define _CRT_SECURE_NO_WARNINGS 1
#include
#include
char* my_strcat(char* dest, const char* src)
//src不涉及修改,可以用const修饰成常量
{
assert(dest != NULL);
assert(src != NULL);//先断言,确保两个字符串地址不是空指针
char* start = dest; //先保存dest起始地址,因为后面地址会改变
while (*dest)
{
dest++;
}
while (*dest++ = *src++)
{
;
}
return start;
}
int main()
{
char arr1[20] = "hello ";
char arr2[10] = "world";
my_strcat(arr1, arr2);//数组名就是首元素地址
printf("%s\n", arr1);
return 0;
}
描述
计算字符串 str 的长度,直到空结束字符,但不包括空结束字符。
声明
下面是 strlen() 函数的声明。
size_t strlen(const char *str)
参数
返回值
该函数返回字符串的长度。
strlen的实现也很简单,这里给出两种方法,一种就是利用指针遍历技术,遇到’ \0 '时停止计数,返回最终的计数值即可。另一种方法是用数组的尾地址(指针指向 ’ \0 ’ 的地址)减去起始地址也可以得到字符串长度。能这么做的原因是字符型指针步长正好为1。
函数声明
size_t my_strlen1(char* arr)
size_t my_strlen2(char* arr)
函数实现:
size_t my_strlen1(char* arr)
{
assert(arr != NULL);
size_t len = 0;
while (*arr++)
{
len++;
}
return len;
}
size_t my_strlen2(char* arr)
{
assert(arr != NULL);
char* start = arr;//首地址
while (*arr)
{
arr++;//循环结束指针指向'\0'
}
return arr - start;
}
int main()
{
char arr[10] = "hello";
size_t len1 = my_strlen1(arr);
size_t len2 = my_strlen2(arr);
printf("len1 = %d\n", len1);
printf("len2 = %d\n", len2);
return 0;
}
描述
把 str1 所指向的字符串和 str2 所指向的字符串逐个字符的ASCLL码进行比较,出现字符不相等情况,或者遇到’\0’时,立刻返回字符大小(ASCLL码)之差。
声明
下面是 strcmp() 函数的声明。
int strcmp(const char *str1, const char *str2)
参数
返回值
该函数返回值如下:
strcmp的实现其实也很简单,无非是先找到两个字符串不同的地方,然后进行相减运算,再返回结果就行。有了前面的铺垫,也很容易用代码实现。
函数声明
int my_strcmp(const char *str1, const char *str2)
函数实现:
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 != NULL);
assert(str2 != NULL);
while (!(*(unsigned char*)str1 - *(unsigned char*)str2) && *str1)
//只要str1和str2的字符相同并且str1没遇到'\0',就一直循环
{
str1++;
str2++;
}
return *str1 - *str2;
}
int main()
{
char arr1[10] = "adcdA";
char arr2[10] = "abcde";
int ret = my_strcmp(arr1, arr2);
if (ret < 0)
{
printf("str1);
}
else if (ret>0)
{
printf("str1>str2\n");
}
else
{
printf("str1=str2");
}
}
输出结果:
arr1的第二个字符d的ascll码为100,arr2的第二个字符ascll码大小为98,100 - 98 = 2 > 0,所以结果如下
描述
把 str1 和 str2 进行比较,最多比较前 n 个字节。
声明
下面是 strncmp() 函数的声明。
int strncmp(const char *str1, const char *str2, size_t n)
参数
返回值
该函数返回值如下:
和strcmp类似,只不过循环次数要有n的限定。
函数声明
int my_strncmp(const char* str1, const char* str2,size_t n)
函数实现
int my_strncmp(const char* str1, const char* str2,size_t n)
{
assert(str1 != NULL);
assert(str2 != NULL);
while (!(*(unsigned char*)str1 - *(unsigned char*)str2) && *str1 && --n)
//只要str1和str2的字符相同并且str1没遇到'\0',就一直循环
//这里只能用--n,而不能用n--,留给读者思考为什么?
{
str1++;
str2++;
}
return *str1 - *str2;
}
int main()
{
char arr1[10] = "abceA";
char arr2[10] = "abcde";
int ret = my_strncmp(arr1, arr2,3);
if (ret < 0)
{
printf("str1);
}
else if (ret>0)
{
printf("str1>str2\n");
}
else
{
printf("str1=str2\n");
}
}
描述
把 src 所指向的字符串复制到 dest。
需要注意的是如果目标数组 dest 不够大,而源字符串的长度又太长,可能会造成缓冲溢出的情况。
声明
下面是 strcpy() 函数的声明。
char *strcpy(char *dest, const char *src)
参数
返回值
该函数返回一个指向最终的目标字符串 dest 的指针。
把源字符串逐个遍历到目标字符串即可。
函数声明
char *my_strcpy(char *dest, const char *src)
函数实现
char* my_strcpy(char *dest, const char *src)
{
assert(dest != NULL);
assert(src != NULL);//先断言,确保两个字符串地址不是空指针
char*start = dest;
while (*dest++ = *src++)
{
;
}
return start;
}
int main()
{
char arr1[10] = "hellohe";
char arr2[10] = "world";
my_strcpy(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
描述
把 src 所指向的字符串复制到 dest,最多复制 n 个字符。当 src 的长度小于 n 时,dest 的剩余部分将用空字节填充。
声明
下面是 strncpy() 函数的声明。
char *strncpy(char *dest, const char *src, size_t n)
参数
返回值
该函数返回最终复制的字符串。
函数声明
char *my_strncpy(char *dest, const char *src, size_t n)
函数实现
char* my_strncpy(char *dest, const char *src,size_t n)
{
assert(dest != NULL);
assert(src != NULL);//先断言,确保两个字符串地址不是空指针
char*start = dest;
while (n--)
{
*dest++ = *src++;
}
return start;
}
int main()
{
char arr1[10] = "hellohe";
char arr2[10] = "world";
my_strncpy(arr1, arr2,5);
printf("%s\n", arr1);
return 0;
}