全文目录
引言
在之前C语言的学习中,我们经常会对字符串进行一些操作。例如计算一个字符串的长度;将两个字符串进行比较;将字符串的值存放到另一字符串中;给一个字符串后面追加一个字符串;在一个字符串中查找另一个字符串等。由于这些操作十分普遍,C语言定义了一些库函数用来实现相关的操作。如strlen(计算字符串长度)、strcmp(字符串比较)、strcpy(字符串拷贝)、strcat(字符串追加)、strstr
(字符串查找)等。
接下来将逐一讲解这些字符串的用法以及一些实现:
strlen
对于库函数strlen,相信大家已经很熟悉了,它的作用是计算字符串的长度。
函数声明
我们可以查询到这个库函数的声明:
这个库函数被声明在string.h文件中。
参数类型是const char* 。接收的是开始计数的字符的地址,由这个地址向后计算到’\0’停止('\0’不计入字符串长度)。因为计算字符串长度时,开始位置的地址不需要被改变,所以用const修饰起始位置的地址会更安全。
返回值是size_t。这个类型是无符号整型,即unsigned int型。因为字符串的长度一定是一个正数,所以用无符号整型来返回更加合适。
函数使用
例如:
#include<stdio.h>
#include<string.h>
int main()
{
char s[] = "abcdef";
printf("%d\n", strlen(s));
return 0;
}
在这段代码中,s是一个字符串,s中存储的是’a’、‘b’、‘c’、‘d’、‘e’、‘f’‘、\0’,这7个元素。数组名s是首元素地址,类型为char*,作为strlen的参数。从这个字符指针向后运算到’\0’停止(不含\0),结果就是6。
my_strlen实现
了解了strlen函数使用后,我们就可以自己实现一个my_strlen。
这个my_strlen函数接收一个字符指针,并从这个指针向后计算到’\0’停止。我们可以定义一个count变量用来计数,然后循环,每次这个字符指针向后移动一位。
并且我们可以用assert函数来断言,确保给这个函数传递的指针是有效指针(库函数assert被声明在头文件assert.h头文件中,判断为假就会报错)。
int my_strlen(const char* s)
{
assert(s);
int count = 0;
while (*(s++) != 0)
{
count++;
}
return count;
}
再用这个my_srtrlen函数计算计算字符串长度:
#include<stdio.h>
#include<cassert>
int my_strlen(const char* s)
{
assert(s);
int count = 0;
while (*(s++) != 0)
{
count++;
}
return count;
}
int main()
{
char s[] = "abcdef";
printf("%d\n", my_strlen(s));
return 0;
}
strcmp
当我们需要判断两个字符串是否相等时,不能使用==操作符进行直接判断。这时就需要用到字符串比较函数,它的作用时比较两个字符串。
函数声明
我们可以查询到这个库函数的声明:
这个库函数被声明在string.h文件中。
参数类型是两个const const* 。是两个要进行比较的起始位置,从这两个地址开始逐字节比较该字符的ASCII码值。若遇到两个字符的ASCII码值不相等时或两个字符都为’\0’时停止比较。因为比较两个字符串时,开始位置的地址不需要被改变,所以用const修饰起始位置的地址会更安全。
返回值是int。当比较结果第一个字符串大于第二个字符串时返回一个大于0的数;等于是返回0;小于时返回一个小于0的数(vs环境下返回-1、0、1)。
函数使用
例如:
#include<stdio.h>
#include<string.h>
int main()
{
char s1[] = "abcdef";
char s2[] = "abcfe";
printf("%d\n", strcmp(s1,s2));
return 0;
}
数组名s1、s2是首元素地址,类型是char*。从这两个地址指向的字符开始比较,前三个字符相等,到第四个字符时s1中的’d’小于s2中的字符’f’。此时返回一个小于0的数(vs环境下返回-1)。
my_strcmp的实现
在了解了strcmp函数之后,我们就可以自己实现一下my_strcmp:
int my_strcmp(const char* s1, const char* s2)
{
assert(s1&&s2);
while (*s1 == *s2)
{
if (*s1 == '\0')
{
return 0;
}
s1++;
s2++;
}
return *s1 - *s2;
}
在这段代码中:
首先断言了s1与s2的有效性。判断s1指向的字符与s2指向的字符是否相等,如果相等进入循环。在循环中判断s1是否为’\0’,若为’\0’则说明s1与s2指向的元素都为’\0’,此时,两个字符串相等,返回0。如果不等于’\0’,则s1于s2都向后移动一位,进入下一次循环。当s1指向的字符于s2指向的字符不相等时,跳出循环,返回这两个字符的ASCII码值的差。
我们可以用我们的my_strcmp函数来进行字符串的比较:
#include<stdio.h>
#include<assert.h>
int my_strcmp(const char* s1, const char* s2)
{
assert(s1&&s2);
while (*s1 == *s2)
{
if (*s1 == '\0')
{
return 0;
}
s1++;
s2++;
}
return *s1 - *s2;
}
int main()
{
char s1[] = "abcdef";
char s2[] = "abcfe";
printf("%d\n", my_strcmp(s1,s2));
return 0;
}
d于f的ASCII码值的差就是-2。
strcpy
如果我们需要将一个字符串的内容拷贝到另一个字符串。C语言规定了库函数strcpy来实现这个作用。
函数声明
我们可以查询到这个函数的声明:
这个函数被声明在头文件string.h中
第一个参数的类型时char*,是目标字符的字符指针;第二个参数是const char*,是源头字符的字符指针(由于源头字符串在拷贝时不会被改变,所以用const修饰会更安全)。strcpy会从目标字符指针开始逐字符将源头字符串的内容拷贝到以目标字符指针开始的字符串中。‘\0’是这个操作的结束标志,当源头字符串遍历到’\0’时,停止拷贝,并将’\0’也拷贝到目标字符串。
返回值时char*型的,返回目标字符串的起始地址。
需要注意的是,目标字符串的大小要确保能够存放下源头字符串。否则就会出现越界访问的问题。
函数使用
例如:
#include<stdio.h>
#include<string.h>
int main()
{
char s1[] = "abcdef";
char s2[] = "fcbd";
printf("%s\n", strcpy(s1,s2));
return 0;
}
这段代码,是将字符串s2中的内容拷贝到s1中。数组名s1与s2都是首元素地址,strcpy的返回值是字符指针。%s打印字符指针即从这个字符指针指向的字符开始打印到’\0’结束。结果就是fcbd。
my_strcpy实现
在了解了strcpy函数的使用后,我们就可以模拟实现一个my_strcpy:
char* my_strcpy(char* s1, const char* s2)
{
assert(s1&&s2);
char* ret = s1;
while (*s1++ = *s2++)//赋值语句的值就是所赋的值
{
;
}
return ret;
}
在这段代码中:
首先断言s1与s2的有效性。接着将目标字符串的首元素地址赋给字符指针ret方便返回(因为后面会对字符指针s1进行自增)。然后循环,将s2指向的元素赋值给s1指向的元素。将*s1++ = *s2++写在while的判断部分,后置++使这个字符指针先使用后自增,并且由于赋值语句的值就是所赋的值,在s2指向的元素为’\0’并被赋值给s1所指的元素时,循环结束,返回字符指针ret。
然后我们可以使用这个my_strcpy函数进行字符串拷贝:
#include<stdio.h>
#include<assert.h>
char* my_strcpy(char* s1, const char* s2)
{
assert(s1&&s2);
char* ret = s1;
while (*s1++ = *s2++)//赋值语句的值就是所赋的值
{
;
}
return ret;
}
int main()
{
char s1[] = "abcdef";
char s2[] = "fcbd";
printf("%s\n", strcpy(s1,s2));
return 0;
}
strcat
strcat是字符串追加库函数,可以将一个字符串追加到另一字符串后。
函数声明
我们可以查询到这个库函数的声明:
库函数strcat被声明在头文件string.h中。
第一个参数的类型时char*,是目标字符的字符指针;第二个参数是const char*,是源头字符的字符指针(由于源头字符串在追加时不会被改变,所以用const修饰会更安全)。strcat会从目标字符指针所指字符串的末尾开始逐字符将源头字符串的内容拷贝到目标字符串。‘\0’是这个操作的结束标志,当源头字符串遍历到’\0’时,停止追加,并将’\0’也追加到目标字符串的末尾。
返回值时char*型的,返回目标字符串的起始地址。
需要注意的是,追加字符串时,目标字符串要保证有能够追加字符串的长度,否则就会出现越界访问的问题。
函数使用
例如:
#include<stdio.h>
#include<string.h>
int main()
{
char s1[15] = "abcdef";
char s2[] = "zxcv";
printf("%s\n", strcat(s1,s2));
return 0;
}
这段代码,是将字符串s2中的内容追加到到s1末尾。数组名s1与s2都是首元素地址,strcat的返回值是字符指针。%s打印字符指针即从这个字符指针指向的字符开始打印到’\0’结束。结果就是abcdefzxcv。
my_strcat实现
接下来,通过实现strcat可以更好的理解这个库函数:
char* my_strcat(char* s1, const char* s2)
{
assert(s1&&s2);
char* ret = s1;
while (*s1++)
{
;
}
s1--;
while (*s1++ = *s2++)
{
;
}
return ret;
}
在这段代码中:
同样的,我们先使用assert断言s1与s2的有效性。
接下来要将s2追加在s1的末尾,就需要先找到字符串s1的结束标志。while循环遍历,s1自增,直到s1指向’\0’时终止循环。但是由于是后置++,所以在跳出循环后s1指向的是’\0’的下一个字符,所以需要再将s1自减再进行追加。
然后再循环,将s2指向的元素赋值给s1指向的元素。将*s1++ = *s2++写在while的判断部分,后置++使这个字符指针先使用后自增,并且由于赋值语句的值就是所赋的值,在s2指向的元素为’\0’并被赋值给s1所指的元素时(刚进入循环时这里的s1指向的字符是’\0’),循环结束,返回字符指针ret。
使用如下:
#include<stdio.h>
#include<assert.h>
char* my_strcat(char* s1, const char* s2)
{
assert(s1&&s2);
char* ret = s1;
while (*s1++)
{
;
}
s1--;
while (*s1++ = *s2++)
{
;
}
return ret;
}
int main()
{
char s1[15] = "abcdef";
char s2[] = "zxcv";
printf("%s\n", my_strcat(s1,s2));
return 0;
}
strstr
库函数strstr用于在一个字符串中查找另一个字符串。
函数声明
我们可以查询到这个库函数的定义:
这个库函数在头文件string.h中声明。
有两个参数,类型为const void*,接收两个字符串(查找时也不需要改变字符串的内容,所以用const修饰)。
返回值为char*型。当在str1中查找到str2字符串时,返回str2字符串第一次出现时的首地址;当没有查找到时,返回一个空指针。
函数使用
例如:
#include<stdio.h>
#include<string.h>
int main()
{
char s1[15] = "abcdef";
char s2[] = "cde";
printf("%s\n", strstr(s1,s2));
return 0;
}
在这段代码中数组名s1,s2作为参数传给库函数strstr,它会在串s1中查找s2。如果查找到了就将第一次出现的首元素地址返回;没查找到就返回空指针。
我们可以将返回的字符指针用%s打印出来,会从这个字符指针所指的字符向后打印直到’\0’结束。结果就是cdef。
当然,我们也可以在main函数中进行判断:当返回值不是NULL时,打印yes,否则打印no:
#include<stdio.h>
#include<string.h>
int main()
{
char s1[15] = "abcdef";
char s2[] = "cde";
if (strstr(s1, s2))
{
printf("yes\n");
}
else
{
printf("no\n");
}
return 0;
}
my_strstr实现
在了解了strstr函数的使用后,我们就可以尝试自己实现一下my_strstr:
对于strstr函数的实现我们最容易想到的就是遍历字符串str1,在这其中寻找str2(当然还有效率更高的算法如KMP算法,后面会专门的博客去说明)。现在就先用暴力遍历的方式实现my-strstr:
我们想要在str1中找到str2,就需要将数组str1的每一个元素作为寻找的起点来向后遍历一个str2的长度。遍历过程中,如果字符相等则比较下一个字符;如果不相等则需要跳出循环,让寻找起点向后移动一位,并且str2需要回到首元素而开始进行下一次循环。
要实现这样的目的,我们需要创建一个字符指针变量cp用来存放寻找起点;一个字符指针s1用来在str1中移动遍历;一个字符指针s2用来在str2中移动遍历。
先将cp赋值为str1,表示以str1串的首元素地址作为第一次的寻找起点。
开始总的循环(这个循环的最大执行次数是str1串的长度),将s1赋值为cp,表示将在str1串中移动的字符指针设置为寻找起点;将s2赋值为str2表示将在str2中移动的字符指针设置为str2串的首元素地址。
然后开始小的寻找循环:若s1指向的元素不是’\0’并且s1指向的元素与s2相等,则s1++、s2++。自增之后,若s2指向的字符为’\0’,就说明在s1中已经查找到了全部的s2串的内容,此时返回这次寻找循环的寻找起点cp。若进行完一次寻找循环之后(没有返回cp)判断s1指向的元素为’\0’,则表示寻找完str1串中的所有元素都没有找到连续的全部的str2串的内容,此时返回NULL。
在总的循环结束之后(没有返回任何东西),也说明没有找到,返回NULL。
代码如下:
#include<stdio.h>
#include<assert.h>
char* my_strstr(const char* str1, const char* str2)
{
const char* s1 = NULL;
const char* s2 = NULL;
const char* cp = str1;
for (s1 = str1, s2 = str2; *cp != '\0'; cp++)
{
s1 = cp;
s2 = str2;
while (*s1 != '\0' && *s1 == *s2)
{
s1++;
s2++;
if (*s2 == '\0')
{
return cp;
}
}
if (*s1 == '\0')
{
return NULL;
}
}
return NULL;
}
int main()
{
char str1[] = "ababababcde";
char str2[] = "ababc";
if (my_strstr(str1, str2) != NULL)
{
printf("YES\n");
}
else
{
printf("NO\n");
}
return 0;
}
总结
以上就是一些字符串函数的介绍了,但是我们在使用这些库函数的时候可能会遇到一些问题或受到一些限制。
比如我想将一个字符串中的一部分拷贝到一个相同大小的空间中,由于strcpy的拷贝是以’\0’为结束标志的,所以并不能实现这样自由的拷贝;
又比如我想将一个字符串追加到它本身后,但是由于strcat在追加时会覆盖掉目标串的’\0’,并且它的最佳也是以’\0’作为结束标志的。自己给自己追加时就会因为找不到结束标志而无休止的追加下去。
这样类似的限制是很普遍的,所以有了一些限制操作个数的字符串函数,使其不受’\0’的限制。在下一篇内容中将介绍这些字符串函数。
如果对本文有任何问题,欢迎在评论区进行讨论哦
希望与大家共同进步哦