我们平常在写代码时,有时明明只是想简单的将数组复制,将字符串拷贝一份等等,却需要重新写一个函数来实现。然而,大部分我们认为的简单却又复杂的操作都有所对应的库函数,今天,我们就来了解一些库函数并且模拟实现它们。
目录
函数介绍
strlen
strlen的简单认识
strlen,从单词来看,就是The length of a C string,字符串长度,我们可以推测:这个函数是用来求字符串长度的。我们从strlen - C++ Reference (cplusplus.com):
我们可以看到,它确实是用来求字符串长度的,我们看到它的参数是 const char * str,一个被const修饰的字符指针,所以我们只需要将字符指针传给该函数即可。那么,我们接下来就来简单的应用它(注意:strlen的返回值是size_t类型的,那么我们就用size_t来进行接收,相应的打印时需要用%zd来进行打印):
#include<string.h> int main() { const char* str = "abcdef"; size_t len1 = strlen("abcdef");//注意:这里的"abcdef"是地址而不是字符串,我们从str的参数类型就可以看出 size_t len2 = strlen(str);//这里我们将地址传给strlen printf("%zd\n", len1); printf("%zd\n", len2); return 0; }
运行结果如下:
我们在上面的代码中使用了size_t,那么,什么是size_t?size_t又是怎么来运用的呢?
既然strlen的返回值是size_t,那么我们就让两个size_t的值相减来进行判断:
int main() { size_t ret = strlen("abc") - strlen("abcdef"); if (ret > 0) printf(">=\n");//如果>0,返回>= if (ret < 0) printf("<");//如果<0,返回< return 0; }
我们从数学的角度出发,abc的长度明显是小于abcdef的,那么就应该打印<。然而,打印结果是:
这种情况我们就只能想到一种可能,那就是size_t是无符号整型,无符号整型与无符号整型相减还是无符号整型,那么就会打印出>=。
在了解如何使用strlen后,我们来模拟实现它。
模拟实现strlen
那么,如何实现strlen呢?这里我们用3种方法来对它进行实现:
计数器法
我们知道,字符串是以'\0'结束的,那么我们就可以创建一个指针指向字符串的起始位置,创建一个计数变量,让指针依次移动,同时让计数变量++,到指针指向'\0'时停止计数,代码如下:
int my_strlen(char* str) { assert(str);//这里我们进行断言,判断是否为空 int count = 0;//创建计数器 while (*str)//当str指向'\0'时,退出循环 { count++; str++; } return count; } int main() { char arr[] = "abcdef"; int ret = my_strlen(arr); printf("该字符串长度为:%d\n", ret); return 0; }
运行结果如下:
递归法
我们也可以用递归的方法来实现,其实递归也是一样的,让函数不断递归,在指向'\0'的时候停止,在函数进行递归时,每次递归数+1,从而完成让指针移动和计数,代码如下:
#include<assert.h> int my_strlen(char* str) { assert(str);//进行断言,判断是否为空 if (*str == '\0') return 0; return my_strlen(str + 1) + 1;//我们让函数在递归的同时不断+1,直到'\0'。 } int main() { char arr[] = "abcdef"; int ret = my_strlen(arr); printf("该字符串长度为:%d\n", ret); return 0; }
运行结果如下:
指针减指针
什么是指针减指针呢?其实就是地址减地址(狗头),指针减指针所得到的是两个地址之间元素的个数。因此,我们需要得到指向'\0'的地址,在让它与首地址相减即可,代码如下:
int my_strlen(char* str) { char* tmp = str; while (*str) { str++; } int num = (int)(str - tmp);//我们需要得到int类型,因此强制转换成int类型 return num; } int main() { char arr[] = "abcdef"; int ret = my_strlen(arr); printf("该字符串长度为:%d\n", ret); return 0; }
运行结果如下:
strcpy
strcpy的简单认识
我们很容易就看出来,strcpy就是对字符串进行拷贝,那么应该如何来使用呢,我们先对其进行了解:
从对其参数的观察,我们可以发现,strcpy是用字符指针将源头的参数拷贝到目的地参数。那么,我们就来简单实现:
#include<string.h> int main() { char arr1[20] = { 0 }; char arr2[] = "hello world!"; strcpy(arr1, arr2); printf("%s\n", arr1);//在拷贝完成后对目的地进行打印 return 0; }
运行结果如下:
但是,我们在使用strcpy时,需要注意以下几点:
在对strcpy进行使用时,我们注意这几点:
1.源字符串必须以 '\0' 结束。
2.会将源字符串中的 '\0' 拷贝到目标空间。
3.目标空间必须足够大,以确保能存放源字符串。
4.目标空间必须可变。也就是说,我们使用strcpy进行拷贝时,会将'\0'也一并拷贝过去,倘若没有'\0',会发生越界,那我们来验证:
int main() { char arr1[20] = "xxxxxxxxxxxxxxxxxx"; char arr2[] = "hello world!"; strcpy(arr1, arr2); printf("%s\n", arr1); return 0; }
进行调试:
这里我们很清楚的看到,strcpy是会将'\0'也一并拷贝过去的。
了解完strcpy的用法后,我们对strcpy进行模拟实现。
模拟实现strcpy
模拟实现strcpy,我们只需要将源头的指针解引用后赋给目的地 ,由于strcpy的返回值是char*,我们在模拟实现时需要先另存一份目的地的地址:
#include<assert.h> char* my_strcpy(char* dest, const char* src)//源头不可修改 { assert(dest && src); char* tmp = dest; while (*dest++ = *src++)//不止一次拷贝,因此使用循环 { ;//这里我们需要将'\0'也一并拷贝过去,所有这里后置++。 } return tmp; } int main() { char arr1[20] = { 0 }; char arr2[] = "abcdef"; my_strcpy(arr1, arr2); printf("%s\n", arr1); return 0; }
运行结果如下:
strcat
strcat的简单认识
strcat是对字符串进行追加,那么问题来了,什么是对字符串进行追加?一个简单的例子:有一个字符串变量a1="abc";和另一个字符串变量a2="def";在进行追加之后,我们可以得到"abcedf",这就是字符串追加。那么,我们应该如何对其进行使用呢?
我们从其参数可以知道,strcat是将源头的参数追加到目的地的后方,那么我们简单应用:
int main() { char arr1[20] = "abc"; char arr2[] = "def"; strcat(arr1, arr2); printf("%s\n", arr1); return 0; }
运行结果如下:
于是我们知道,使用strcat会将目的地的'\0'给覆盖,那么strcat在追加时会将源头的'\0'也追加过去吗?我们进行验证:
int main() { char arr1[20] = "abc\0xxxxx"; char arr2[] = "def"; strcat(arr1, arr2); printf("%s\n", arr1); }
我们进行调试:
我们得知,strcat会将'\0'也一并追加过去。
那么,我们接下来对其进行模拟实现。
模拟实现strcat
如何模拟实现strcat呢?那么我们首先需要找到目的地的'\0'的位置,然后再将源头的参数一一追加过去,strcat的返回值也是char*类型,那么我们同样再保存一份目的地的地址,代码如下:
#include<assert.h> char* my_strcat(char* dest,const char* src) { assert(dest && src);//断言,看查是否为空 char* tmp = dest; //找到目的地的末尾 while (*dest != '\0') { dest++; } //进行追加 //在这时我们进行追加,我们会发现接下来的过程和strcpy是一样的,代码如下: while (*dest++ = *src++) { ; } return tmp;//返回目的地的地址 } int main() { char arr1[20] = "abc"; char arr2[] = "def"; my_strcat(arr1, arr2); printf("%s\n", arr1); }
运行结果如下:
注意事项
我们在使用strcat时,需要注意:
1.源字符串必须以 '\0' 结束。
2.目标空间必须有足够的大,能容纳下源字符串的内容。
3.目标空间必须可修改。那么,strcat可以自己给自己追加吗?我们进行实验:
#include<assert.h> char* my_strcat(char* dest,const char* src) assert(dest && src); char* tmp = dest; //找到目的地的末尾 while (*dest != '\0') { dest++; } //进行追加 while (*dest++ = *src++) { ; } return tmp;//返回目的地的地址 } int main() { char arr1[20] = "abc"; char arr2[] = "def"; my_strcat(arr1, arr1); printf("%s\n", arr1); }
我们运行后,程序崩溃,从而得出结论:我们所写的模拟strcat是不能对自己进行追加的,为什么呢?我们画图分析:
但是,库函数的strcat是可以实现对自己的追加的,说明库函数进行了优化。
strcmp
strcmp的简单认识
strcmp我们在之前使用比较频繁,我们知道,它是进行字符串比较的:
我们从上图中得知,strcmp不是来比较长度的,它是比较对应元素的大小的,这里举一个例子:
而strcmp的返回值是int类型,这是为什么呢?那么它该如何使用?我们接着往下看:
从这里我们可以知道,strcmp是在对元素进行比较后通过返回值与0进行比较来进行判断。
那么我们接下来就对其进行简单应用:
#include<string.h> int main() { char arr1[] = "abcdef"; char arr2[] = "abg"; int ret = strcmp(arr1, arr2); if (ret > 0) printf("arr1>arr2\n"); else if (ret == 0) printf("arr1=arr2\n"); else printf("arr1<arr2\n"); return 0; }
运行结果如下:
模拟实现strcmp
模拟实现strcmp,我们只需要使用两个指针,让这两个指针在移动的同时对所指元素解引用并进行比较,当一个指针或两个指针同时指向'\0'时,停止移动,返回一个整型进行判断,代码如下:
#include<assert.h> int my_strcmp(const char* str1, const char* str2) { assert(str1 && str2); if (*str1 == *str2) { if (*str1 == '\0') return 0; str1++; str2++; } else if (*str1 > *str2) return 1; else return -1; } int main() { char arr1[] = "abcdef"; char arr2[] = "abg"; int ret = my_strcmp(arr1, arr2); if (ret > 0) printf("arr1>arr2\n"); else if (ret == 0) printf("arr1=arr2\n"); else printf("arr1<arr2\n"); return 0; }
运行结果如下:
以上的函数都有一种特点:长度都不会受到限制,C语言在提供长度受限制的函数的同时,也给我们提供了一些长度受限制的函数,下面我们来认识几个长度受限制的函数。
strncpy
strncpy的简单认识
strncpy相比于strcpy多了一个字母n,那么它们有何区别呢?我们往下看:
我们可以看到,strncpy的参数多了一个参数 size_t num ,再结合下文我们了解到,strncpy要将想要拷贝的元素个数传递过去,在源头和目的地函数上,没有多大区别,下面我们使用它:
#include<string.h> int main() { char arr1[20] = { 0 }; char arr2[] = "abcdefg"; strncpy(arr1, arr2, 3); printf("%s\n", arr1); return 0; }
运行结果如下:
那么,strncpy在进行拷贝的时候会将'\0'拷贝过去吗?我们进行验证:
#include<string.h> int main() { char arr1[20] = "xxxxxxxxxxxxxxxxxx"; char arr2[] = "abcdefg"; strncpy(arr1, arr2, 3); printf("%s\n", arr1); return 0; }
进行调试:
我们可以看到,strncpy是没有将'\0'拷贝过去的,既然没有拷贝'\0',这时我们再想一个问题,如果想要拷贝的个数大于数组的元素个数时,会发生什么?
#include<string.h> int main() { char arr1[20] = "xxxxxxxxxxxxxxxxxx"; char arr2[] = "abcd"; strncpy(arr1, arr2, 6);//将数组的字符改为4个,此处拷贝6个 printf("%s\n", arr1); return 0; }
进行调试:
我们会发现,strncpy会在大于拷贝元素的后面补'\0'。所以strncpy比起strcpy,会相对的安全一点,因为我们在使用strncpy时会考虑拷贝的元素有几个,能否拷贝过去等问题。
在了解完其用法后,我们来对strncpy进行模拟实现。
模拟实现strncpy
我们在上面了解到,strncpy只是在strcpy的基础上增加了想要拷贝的个数num,那么我们只需要将想要拷贝的个数传过去,在大于拷贝元素的后面补'\0'就可以了,代码如下:
#include<assert.h> char* my_strcpy(char* dest, const char* src, size_t num)//源头不可修改 { //这里进行断言 assert(dest && src); char* tmp = dest;//这里将原来的目的地地址进行拷贝,下面的循环会对地址进行修改 size_t sz = strlen(src); if (num <= sz) { for (size_t i = 0; i < num; i++)//strncpy是不会将'\0'一并拷贝过去的 { *dest = *src; dest++; src++; } } else if (num > sz) { size_t n = num; num = sz; for (size_t i = 0; i < num; i++) { *dest = *src; dest++; src++; } for (size_t j = sz; j < n; j++) { *dest = '\0'; dest++; } } return tmp; } int main() { char arr1[20] = { 0 }; char arr2[] = "abcdef"; size_t num = 0; printf("请输入想要拷贝的个数:<"); scanf("%zd", &num); my_strcpy(arr1, arr2, num); printf("%s\n", arr1); return 0; }
运行结果如下:
strncat
对strncat的简单认识
strncat和strcat对比,也是参数上多了size_t num,这意味着我们在追加的时候需要考虑追加几个元素:
我们可以看到,在除了传递追加的个数以外的使用方法是没有发生改变的,那么我们接下来就对其进行使用:
#include<string.h> int main() { char arr1[20] = "abc"; char arr2[] = "defghi"; strncat(arr1, arr2, 3); printf("%s\n", arr1); return 0; }
运行结果如下:
这时我们就会产生同样的问题,strncat在使用的时候会将'\0'给追加过去吗?我们对代码进行修改和验证:
#include<string.h> int main() { char arr1[20] = "abc\0xxxxxxxx"; char arr2[] = "defghi"; strncat(arr1, arr2, 3); printf("%s\n", arr1); return 0; }
进行调试:
我们可以清楚的看到,strncat是会将'\0'也一并拷贝过去的,那么,如果我特意把要追加的元素大于数组当中的元素个数会发生什么呢?
#include<string.h> int main() { char arr1[20] = "abc\0xxxxxxxx"; char arr2[] = "defghi"; strncat(arr1, arr2, 10);//我们改为追加10个元素 printf("%s\n", arr1); return 0; }
我们进行调试:
运行结果如下:
我们可以发现,是不会发生什么的,在追加完数组的元素之后,strncat不会对后续做出处理,那么,我们就在此基础上,模拟实现strncat。
模拟实现strncat
相比于模拟实现strcat,我们额外注意两点:
1.追加的个数。
2.如果追加的数超过数组元素个数,不做处理 。那么我们就对其进行模拟实现:
#include<assert.h> char* my_strcat(char* dest, const char* src, size_t num) { assert(dest && src);//断言,看查是否为空 char* tmp = dest; //找到目的地的末尾 while (*dest != '\0') { dest++; } //开始追加,注意追加个数(因为追加会将'\0'也一并拷贝,所以超出的个数就不做处理 for (size_t i = 0; i < num; i++) { *dest = *src; dest++; src++; } return tmp;//返回目的地的地址 } int main() { char arr1[20] = "abc"; char arr2[] = "def"; size_t num = 0; printf("请输入想要追加的个数:<"); scanf("%zd", &num); my_strcat(arr1, arr2, num); printf("%s\n", arr1); }
运行结果如下:
strncmp
对strncmp的简单认识
我们可以看到:
strncmp,它的参数也是多了size_t num,也就是说,它是根据操作者想要比较的元素个数来进行比较的,于是我们对其进行使用:
#include<string.h> int main() { char arr1[20] = "abcdef"; char arr2[] = "abcqw"; int ret = strncmp(arr1, arr2, 4); if (ret > 0) { printf(">\n"); } else if (ret == 0) { printf("=\n"); } else printf("<\n"); return 0; }
运行结果如下:
那么接下来我们就对strncmp进行模拟实现。
模拟实现strncmp
也是一样的,我们只需要在模拟实现strcmp的基础上将元素个数传递过去就可以啦:
#include<assert.h> int my_strncmp(const char* str1, const char* str2, int num) { assert(str1 && str2); int ret = 0; if (*str1 == *str2) { for (int i = 0; i < num-1; i++) { if (*str1 == '\0') { ret = 0; break; } str1++; str2++; } if (*str1 > *str2) ret = 1; else if (*str1 < *str2) ret = -1; } return ret; } int main() { char arr1[] = "abcdef"; char arr2[] = "abg"; int num = 0; printf("请输入想要比较的元素个数:<"); scanf("%d", &num); int ret = my_strncmp(arr1, arr2, num); if (ret > 0) printf("arr1>arr2\n"); else if (ret == 0) printf("arr1=arr2\n"); else printf("arr1<arr2\n"); return 0; }
运行结果如下:
小结
今天我们认识的函数都与字符有关,那么如果我使用整型,浮点型,也有对应的库函数吗?当然是有的,但是今天我们就先认识这么多吧,在计算机知识得海洋里,我们还是得继续加油啊,继续冲锋!好啦,今天先到这里,我们下次再见!