目录
1. strlen函数:
1.1 strlen函数的特点:
1.2 模拟实现strlen函数:
2.长度不受限制的字符串函数:
2.strcpy函数:
2.1strcpy函数特点:
2.2 模拟实现strcpy函数:
3. strcat函数:
3.1 strcat函数的特点:
3.2 模拟实现strcat函数:
4.strcmp函数:
4.1strcmp函数的返回值:
4.2 模拟实现strcmp函数:
5.长度受限制的字符串函数:
5.1 strncpy函数:
5.2 strncat函数 :
5.3 strncmp函数:
6. strstr函数:
6.1 strstr函数的功能:
6.2 模拟strstr代码实现:
7.strtok函数:
7.1 strtok函数解析及应用:
8. 文章内容大体框架(思维导图):
之前的文章,不管是对指针的、数组进行介绍的文章,还是之前对于简单的循环语句等进行介绍的文章,都使用了一些函数,其中strlen和strcmp这两个针对于字符串的函数使用率很高,本篇文章对字符串函数进行一个相对系统的介绍
对于strlen的前两条性质,在之前的文章中多次提到,但是对于第三条性质则较为陌生。下面将给出代码进行相应的解释:
int main()
{
char arr1[] = "abcde";
char arr2[] = "abcdef";
if (strlen(arr1) - strlen(arr2) > 0)
{
printf("a");
}
else
{
printf("b");
}
return 0;
}
在上面给出的代码中,创建了arr1和arr2两个字符数组用于存放字符串。如果strlen函数的返回值不是无符号的,则很明显if语句中的值是小于0的,所以应该打印字符'b'。但是打印出来的结果:
所以,可以证明,strlen函数的返回值是无符号的,在之前的文章中多次提到,数据在内存中的存储是以补码的性质进行存储的,而对于无符号的strlen函数的返回值,if语句中的第一个返回值结果是5,第二个则是6,二者相减结果为-1,-1的补码形式:11111111 11111111 11111111 11111110,对于无符号,可以理解为,补码的最高位,也就是象征着符号位的二进制位,对于无符号的数据是失效的,在进行运算时不看成符号位,而是看成普通的二进制位进行运算。
代码如下:
size_t my_strlen(char* s)
{
int count = 0;
while (*s++ != '\0')
{
count++;
}
return count;
}
int main()
{
char arr[] = "abcde";
size_t num = my_strlen(arr);
printf("%u", num);
return 0;
}
由于之前在对于函数递归进行介绍及应用时,已经用递归模拟strlen函数的实现,所以不再对代码进行过多的解释。下面同样给出用函数递归模拟的strlen函数:
size_t my_strlen(char* s)
{
if (*s != '\0')
{
return 1 + my_strlen(s + 1);
}
}
int main()
{
char arr[] = "abcedf";
size_t num = my_strlen(arr);
printf("%u", num);
return 0;
}
长度不受限制的字符串函数,本文一共介绍三个,这三个函数的共同点是字符串结束的标志,即'\0',对于这三个函数很重要,这三个函数分别是:
strcpy函数可以用于拷贝字符串,将一个空间的字符串拷贝到提前设定好的目标空间中。
对于strcpy函数,重要的一点就在于源字符串的'\0',因为进行源字符像目标空间进行拷贝时,会把源字符的'\0'也拷贝到目标空间中。 下面将通过代码来进行说明:
int main()
{
char arr[] = "xxxxxxxxxx";
char arr1[] = "abcedf";
strcpy(arr, arr1);
printf("%s", arr);
return 0;
}
字符数组arr中的内容是由10个'x‘组成的字符串,arr1的内容是连续的6字母组成的字符串,对于arr1中的字符串,第7位就是字符串的结束标志,即'\0',在调用strcpy函数的过程中,可以由编译器的监视窗口来观察arr中字符串的替代情况:
可以从上面的结果图看到,数组arr中标号为[6]的元素,被替代为'\0',而此编号的元素,恰好是这个数组中的第7个元素。对于字符串,'\0'是字符串的结束标志,对于strcpy函数则同理,'\0'是作为拷贝字符结束的标志,同时,对于没有'\0'的情况,例如在字符数组中输入了若干个字符,则不能正常的运行,但是在若干字符后面人为的加上'\0',则可以正常运行,例如:
int main()
{
char arr[] = "xxxxxxxxxx";
char arr1[] = {'a','b','c','d','\0'};
strcpy(arr, arr1);
printf("%s", arr);
return 0;
}
打印结果依旧是对arr中字符串的内容进行替换:
对于第4条性质:目标空间必须可变,这里给出一个反例,创建一个常量字符串作为目标空间:
int main()
{
const char* arr = "abcde";
char arr1[] = {'a','b','c','d','\0'};
strcpy(arr, arr1);
printf("%s", arr);
return 0;
}
此时的目标空间arr是一个常量字符串,如果将常量字符串的首元素地址作为参数传给strcpy函数,则系统会报错:
代码如下:
char* my_strcpy(char* dest, char* scr)
{
char* p = dest;
assert(dest != NULL);
assert(scr != NULL);
while (*dest++ = *scr++)
{
;
}
*dest = *scr;
return p;
}
int main()
{
char arr1[] = "xxxxxxxxx";
char arr2[] = "abcde";
my_strcpy(arr1, arr2);
printf("%s", arr1);
return 0;
}
之所以函数的类型是char*,是为了方便后面实现链式访问,并且,在cplusplus网站(strcpy - C++ Reference (cplusplus.com)中,也可以看到,strcpy这个库函数在进行定义时的类型就是char*
对于assert,则是检验传递过来的地址是否有效。在下面的while循环中,通过对地址传递过来的地址解引用并且后置++,可以将arr2中的字符对arr1中的字符进行代替。如果此时arr2的地址解引用后时'\0',则跳出while循环。并没有将arr2中的'\0'对arr1中的内容进行替换。所以,在下面额外的加上一行代码,(即上面的代码中,return p;的上一行)
*dest = *scr;
此时的arr2的地址保存的正是'\0'的地址,可以将'\0'对arr1中的内容进行代替。
上面说到,为了实现链式访问,strcpy函数的类型是char*,此类型的返回值应返回目标空间的首元素地址,所以,在模拟实现这个函数时,首选用用一个指针将这个地址保存:
char* p = dest;
并在输入返回值时,输入这个指针即可。
在对strcat函数进行查询后,可以看到函数内的两个参数分别是两个地址,strcat函数的功能:正是把source地址对应的字符串;追加到destination地址对应的字符串后面。
从上图中可以发现,strcat函数的特点与strcpy函数的特点基本相同,对于目标空间、源字符串的'\0'都有要求。
char* my_strcat(char* dest, const char* scr)
{
assert(deat);
assert(scr);
char* p = dest;
while (*dest)
{
dest++;
}
while (*dest++ = *scr++)
{
;
}
return p;
}
int main()
{
char arr1[20] = "hello";
char arr2[] = "world";
my_strcat(arr1, arr2);
printf("%s", arr1);
return 0;
}
追加字符的思路和上面的strcpy中替换字符的思路是一样的,不同的是,由于arr2中的字符实在arr1的末尾追加的,所以,需要先找到arr1中'\0'的地址。
对于strcat,需要注意,尽量不要用strcat函数对一个字符串进行追加自己的操作,这样会导致字符串中的'\0'被代替,向后进行追加时,会因为找不到'\0'造成程序崩溃
前面文章中多次用到strcmp函数,此函数被用来比较两个字符串的大小,由于多次使用,所以不过多解释,只给出strcmp函数的返回值,即如何模拟实现strcmp函数。
如下图所示,strcmp函数的返回值:
int my_strcmp(char* s1, char* s2)
{
while (*s1 == *s2)
{
if (*s1 == '\0')
{
return 0;
}
s1++;
s2++;
}
return(*s1 - *s2);
}
int main()
{
char arr1[] = "bcq";
char arr2[] = "bcq";
int ret = my_strcmp(arr1, arr2);
if (ret > 0)
{
printf("大于");
}
else if( ret < 0)
{
printf("小于");
}
else
{
printf("相等");
}
return 0;
}
对于长度受限制的字符串函数,会对照长度不受限制的字符串函数介绍三种,分别是:
对于strncpy函数,和strcpy函数的区别就在于二者的函数参数上:
相对于strcpy函数,strncpy函数的参数中多了一个size_t num,这个参数的作用是限制函数作用与于字符串的长度,假设strncpy的size_t num这个参数的值为5,则对目标空间进行替换时,最多就替换五个元素。下面给出一个例子来演示此函数的作用:
例如下面给出的代码,在函数参数中,规定函数作用的字符数量 = 3
int main()
{
char arr[20] = "abcdef";
char arr2[] = "xxxxxxxxxxx";
strncpy(arr, arr2, 3);
printf("%s ", arr);
return 0;
}
通过监视窗口可以对arr中元素的替换情况进行查看:
可以明确的看到,虽然arr2中的字符'x‘的数量远大于3,但是经过函数参数的限制后,arr中也只有3个元素被替换成了'x'。此时字符串中的元素个数>函数参数限制的个数。
当字符串中的元素个数 < 函数参数限制的个数时,例如:
int main()
{
char arr[20] = "abcdef";
char arr2[] = "xxx";
strncpy(arr, arr2, 5);
printf("%s ", arr);
return 0;
}
此时arr中的'x'个数为3,而函数参数规定的替换个数为5,通过监视看到arr中元素替换情况如下:
此时,剩下的两个没有'x'去进行代替的arr中的字符串的元素的位置,会由'\0'进行替代。
如果把上面代码把strncpy改为strncat,同时为了方便后面对元素的替换情况进行查看,将字符串'abcdef'改为'abcdef\0zzzz'即:
int main()
{
char arr[20] = "abcdef\0yyyy";
char arr2[] = "xxx";
strncat(arr, arr2, 5);
printf("%s ", arr);
return 0;
}
从监视窗口查看arr中元素替换情况,即:
此时,第4个'y'因为没有足够的'x'进行替换,所以在后面补充了一个'\0',但是第5个’y'却不变。
如果再将替换的数量改为7,代码如下所示:
int main()
{
char arr[20] = "abcdef\0yyyyyyy";
char arr2[] = "xxx";
strncat(arr, arr2, 5);
printf("%s ", arr);
return 0;
}
结果如下:
可以看到,虽然 要求的替换数大于'x'的数量,但是不管大多少,替换后只会补充一个'\0',这和strncpy是不同的,strncpy函数对于缺少元素而无法在目标空间中被替换的元素,缺多少个,就补充多少个'\0',但是对于strncat函数,即使缺少的元素很多,也只是补充一个'\0'。
当规定替换的数量 >元素数时,即:
int main()
{
char arr[20] = "abcdef\0yyyy";
char arr2[] = "xxx";
strncat(arr, arr2, 2);
printf("%s ", arr);
return 0;
}
通过结果可以看到,arr中当替换完规定的数量的元素后,依旧就用‘\0'替换一个元素,用于作为一个字符串的结束标志。
对于strncmp,同样也是起了限制作用,这里的限制作用,指限制进行比较大小的字符数量,将由下面的代码进行演示:
代码如下,限制进行比较大小的字符数量为3
int main()
{
char arr[] = "abbq";
char arr2[] = "abbq";
int ret = strncmp(arr, arr2,3);
printf("%d", ret);
return 0;
}
结果如下:
可以验证,两个字符数组进行比较时,仅仅是比较前三个元素的大小。
strstr函数的功能是从一个字符串中,寻找符合内容的子字符串。例如下面给出的例子:
int main()
{
char arr1[20] = "abcccdgfbb";
char arr2[] = "def";
char* ret = strstr(arr1, arr2);
if (ret != NULL)
{
printf("%s", ret);
}
else
{
printf("找不到");
}
return 0;
}
arr2中的内容便是需要去在arr1中寻找的子字符串,结果如下:
首先给出两个数组,如下列代码所示:
int main()
{
char arr1[] = "abbbcdef";
char arr2[] = "bbc";
}
假设用下面的图形代表两个数组及他们中的内容:
如果想在arr1中找到arr2中的内容,第一步便是要定位首元素。
在定位首元素完成后,此时会有两种情况:
1.首元素后面的元素也相等
2.首元素后面的元素不相等
对应第一种情况,如果首元素后面的元素也相同,则继续向后比较,如果不相等,则同样对应了第二种情况,再如果,此时后面的元素是'\0',说明对于两个数组中的字符串比对已经完成,也就是找到了子句。
对应第二种情况,如果说在比对过程中两个元素不同,这并不能代表,arr1种没有符合arr2的子句,例如上面所举的例子,abbbcdef和bbc,如果从第一个字母’b'开始比较,则比较的字符串是bbb,而arr2中的字符串是bbc,此时,如果,从arr1的第2个‘b'开始比较,则二者进行比较的字符串是'bbc'和'bbc’所以,通过这个例子,可以说明,对于比对过程中出现了两个元素不同的情况是,应将arr1中进行比对的首元素的地址+1,如果此时arr1中进行比对的元素出现了'\0',此时才说明,arr1中没有arr2的子句。
至于如何进行比对元素不同后的地址+1操作,可以再设置一个变量。下面将给出流程图加文字的方式进行说明:
1. *s2和*s1对比,此时二者不相等,于是,arr1应从下个字符开始对比,所以:
cp++,s1++,s2不动
2. *s2和*s1解引用对比,此时两者相等,所以,s2++,s1++,二者再次分别解引用进行对比,此时,*s2 == *s1,所以,s1++,s2++,此时 *s1 != *s2,所以,s2回归首元素地址,cp++,s1记录此时cp的地址
3.此时,cp地址为arr1中第三个元素,*s1和*s2(此时s2记录了arr2中的地址)对比,因为*s2 == *s1,s1++,s2++,此时再进行对比,*s1 == *s2, s1++,s2++,此时,*s1 == *s2,此时,再对s2进行++,则s2中存储着'\0'的地址,此时对比完成,表示可以在arr1中找到子句。
代码实现:
char* my_strstr(char* str1, char* str2)
{
char* cp = str1;
char* s1 = cp;
char* s2 = str2;
while (*cp)//用于检查arr1中,此时元素是否时'\0',是的话,则找不到子句
{
s1 = cp;
s2 = str2;
while ((*s1 && *s2) && (*s1 == *s2))//用于比对
//并且检验arr1或者arr2是否是'\0',若arr1是,则找到不到子句
//arr2是,则已经找到
{
s1++;
s2++;
}
if (*s2 == '\0')//当*s2 == '\0'时,说明前面的元素对比都是相等,也就是说找到了
{
return cp;
}
cp++;
}
return NULL;
}
int main()
{
char arr1[] = "abbbcdef";
char arr2[] = "bbc";
char * ret = my_strstr(arr1, arr2);
if (ret!= NULL)
{
printf("%s", ret);
}
else
{
printf("找不到");
}
}
strtok函数有两个参数,分别是:
第2个参数sep是一个字符串,定义了作为分隔符用的字符串的集合。
第1个参数str是一一个指向字符串的指针,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
对于strtok函数的特点及使用,将由一幅图给出:
为了进一步验证及理解strtok函数运行,将给出一段代码进行解释:
int main()
{
char arr1[] = "[email protected]";
char copy[20];
strcpy(copy,arr1);
char sep[] = "@.";
char* ret = strtok(copy, sep);
printf("%s\n", ret);
ret = strtok(NULL, sep);
printf("%s\n", ret);
ret = strtok(NULL, sep);
printf("%s", ret);
return 0;
}
因为strtok函数会改变被操作的字符串,所以,先用strcpy函数将arr1中的内容拷贝到copy中,再规定参数sep。
在第一次调用strtok函数时,strtok 函数会通过第一个参数copy来找到copy中的字符串,因为字符串中存在sep中用来分割字符串的字符'@',所以,将'@"用'\0'替代,返回这个字段的起始位置.。(即返回分隔符号之前的字符串的开头)。同时,因为第一个参数不是NULL,所以,在找到'@'后,会保存这个符号的地址。
第二次调用strtok函数时,此时函数的第一个参数为NULL,所以,函数将在上一次函数运行时被保存的位置开始向后查找,字符串中包含sep中的字符'.',所以,和第一次调用时一样,将‘.’用'\0进行代替,并返回此字符串的起始位置。
第三次调用同理,不过多说明。
结果如下: