我计划讲解C语言string.h这个头文件中,最常见的13个库函数。为了让大家更加深入的理解这些函数,部分函数我会模拟实现。篇幅所限,如果文章太长了,可能会较难坚持读完,所以我会分几篇博客来讲述。本篇博客主要讲解的函数有:strlen, strcpy, strcat, strcmp。这四个函数是最基础,最常见的字符串操作函数,在字符串相关的场景中会频繁使用,希望大家在阅读完本篇博客后,对它们的原理、使用方式了如指掌,运用自如。考虑到有朋友可能不太理解C语言字符串的一些基础概念,本篇博客会先铺垫一些基础知识,已经熟悉C语言字符串的朋友们可以跳过。
什么是字符串?就是一串字符。C语言如何表示字符串呢?用双引号括起来一串字符即可。如:"hello world"
就是一个字符串。
C语言中的字符串的结束标志是\0
这个转义字符。比如"hello world"
这个字符串,本质上,在内存中存储的是hello world\0
,在所有的字符后面,会隐藏一个\0
。注意:\0
作为一个整体,是一个字符,它的ASCII码值是0。
字符串一般用字符数组来存储,比如:
char arr[] = "hello world";
就把"hello world"
这个字符串存储到了字符数组arr里。此时数组arr的长度是多少呢?要把hello world这11个字符算上,后面还有一个\0
,总共12个字符。但是,如果计算的是字符串的长度,是不算最后的\0
的,长度就是11。
字符串的长度计算的是\0
之前出现了几个字符,但是不包含\0
本身!那如何求字符串长度呢?这就要引出今天要讲解的第一个库函数了。
size_t strlen ( const char * str );
strlen是用来求字符串的长度的。只需要给它传字符串首字符的地址,它就会计算出该字符串的长度。注意:字符串常量,即单引号引起来的字符串,作为一个表达式,其值为首字符的地址,可以作为strlen的参数。除此之外,字符串也可以保存到字符数组中,数组名表示首元素地址,也可以作为参数。
字符串直接作为参数:
int len1 = strlen("abc"); // 3
char* str = "defg";
int len2 = strlen(str); // 4
数组名作为参数:
char arr[] = "abc";
int len = strlen(arr); // 3
strlen的返回值是size_t类型的。size_t是一个无符号整型。所以以下程序会输出什么?
if (strlen("abc") - strlen("abcd") < 0)
printf("<\n");
else
printf(">=");
看起来应该是“小于”,但结果是“大于等于”,原因是无符号整型的差还是无符号整型,一定是大于等于0的。
下面我们来模拟实现strlen。实现思路很简单,从首字符开始,向后数,直到遇到\0
就停下来。
size_t my_strlen(const char* str)
{
assert(str);
size_t count = 0;
while (*str)
{
++count;
++str;
}
return count;
}
当然,我们也可以一直向后找\0
,根据“指针-指针得到的是指针之间的元素个数”的原理,用\0
的地址减首字符的地址,也可以得到字符串的长度。
size_t my_strlen(const char* str)
{
assert(str);
const char* eos = str; // end of str
while (*eos)
{
++eos;
}
return eos - str;
}
当然,如果不创建临时变量,也可以使用递归实现,这种实现并不推荐,因为递归是有缺陷的,递归深度太深可能导致栈溢出。递归实现思路是:字符串的长度=1+从下一个字符开始数的长度。也就是说,strlen(str) = 1+strlen(str+1)
。当然,如果str是空字符串,即*str=='\0'
,长度就为0。
size_t my_strlen(const char* str)
{
assert(str);
if (*str)
return 1 + my_strlen(str + 1);
else
return 0;
}
strlen总结:
\0
之前出现了几个字符。\0
结尾,否则结果是随机值。char * strcpy ( char * destination, const char * source );
strcpy是用来完成字符串拷贝的。它有2个参数,分别是目的地和起始位置。比如,把字符串arr1拷贝到arr2里,要这么写:
char arr1[20] = {0};
char arr2[] = "abc";
strcpy(arr1, arr2);
注意:拷贝时,会把arr2中的"abc"拷贝到arr1中,包括结尾的\0
。
strcpy会返回目标空间的起始地址,方便函数的链式访问,比如:
printf("%s\n", strcpy(arr1, arr2));
以上代码,在把arr2中的字符串拷贝到arr1中后,顺便把arr1打印出来,看看有没有拷贝成功。
下面讲讲模拟实现。其实重点是拷贝的过程,也就是*dst++ = *src++
,把src指向的字符拷贝到dst指向的空间中,并且2个指针向后走,直到src遇到\0
,此时结束循环,返回起始地址。注意dst在拷贝的过程中一直在向后走,所以需要在最开始先保存下来,方便最后返回目标空间的地址。
char* my_strcpy(char* dst, const char* src)
{
assert(dst && src);
char* ret = dst;
while (*dst++ = *src++)
{
;
}
return ret;
}
strcpy总结:
\0
结束,否则会一直拷贝字符,直到遇到内存中的\0
。\0
也拷贝到目标空间中。char * strcat ( char * destination, const char * source );
strcat是完成字符串追加的,它可以在目标字符串后面追加源字符串。可以理解为,先从目标空间中找到\0
,即目标字符串的结尾,然后从\0
所在的位置开始,向后追加源字符串。比如:在abc
后面追加def
后就得到了abcdef
,追加的时候会把源字符串的\0
也追加过去。函数会返回目标字符串的起始地址。
其实可以简单理解为:先找到目标字符串结尾的\0
,然后从把源字符串以strcpy
的方式拷贝到目标字符串后面,大家看到模拟实现后就明白了。
char* my_strcat(char* dst, const char* src)
{
assert(dst && src);
char* ret = dst;
while (*dst)
{
++dst;
}
while (*dst++ = *src++)
{
;
}
return ret;
}
其实就是在strcpy的模拟实现的基础上,加上了下面的代码,即找dst中的\0
。
while (*dst)
{
++dst;
}
根据以上的实现,能不能自己给自己追加呢?比如:
char arr[10] = "abc";
strcat(arr, arr);
我们想再arr后面追加arr,预期结果是,“abc"后面追加"abc"得到"abcabc”,但是根据以上模拟实现的代码,在追加的同时,会把src中的\0
给覆盖掉,所以源字符串中就内有\0
了,在拷贝的时候,本来是遇到\0
就停止了,但是一直找不到\0
,就导致无限循环。
那如何实现自己给自己追加呢?这就要用到strncpy函数了,这个函数我会在下一篇博客中介绍。
strcat总结:
\0
结束。int strcmp ( const char * str1, const char * str2 );
strcmp是用来比较2个字符串的。比较方式是:从第一个字符开始,一个一个往后比,直到遇到第一对不同的字符或者都遇到 根据以上的描述,可以模拟实现,如下: strcmp总结:\0
。如果遇到第一对不同的字符,则比较其ASCII码值,哪个大,对应的字符串就更大;如果都遇到\0
,则2个字符串相等。函数会根据不同的大小关系返回不同的值,当str1>str2时,返回一个正整数;如果str1int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
while (*str1 == *str2)
{
if (*str1 == '\0')
return 0;
++str1;
++str2;
}
return *str1 - *str2;
}
\0
。如果遇到不同的字符,则ASCII码值大的字符对应的字符串更大;如果都遇到\0
,则2个字符串相等。\0
,没有的话,会一直向后比较字符,如果都相等,会一直比下去。总结
\0
之前出现的字符个数。\0
,再拷贝。