目录
- 1.求字符串长度
- strlen
- 模拟实现strlen
- 2.长度不受限制的字符串函数
- strcpy
- 模拟实现strcpy
- strcat
- 模拟实现strcat
- strcmp
- 模拟实现strcmp
- 3.长度受限制的字符串函数介绍
- strncpy
- 模拟实现strncpy
- strncat
- 模拟实现strncat
- strncmp
- 模拟实现strncmp
- 4.字符串查找
- strstr
- 模拟实现strstr
- strtok
- 5.错误信息报告
- strerror
C语言中,本身没有字符串类型的,字符串通常以字符数组和常量字符串的形式出现。
而有一些库函数可以对字符串进行操作,使我们对字符串的处理可以简单许多,但是注意的是:这些库函数不可以修改常量字符串
1.求字符串长度
strlen
size_t strlen ( const char * str );
strlen
函数计算的是字符串中'\0'
前面出现的字符个数,不包含'\0'
- 参数指向的字符串一定要以
'\0'
结尾,否则会计算出随机值 strlen
是求字符串长度的,求出的长度是不可能为负数,所以返回size_t
类型的值,size_t
其实就是无符号的整形unsigned int
由于
strlen
返回无符号整形,所以这里是一个易错点,以接下来的代码为例
int main()
{
char str1[] = "abcdefg";
char str2[] = "abcdefghijk";
if (strlen(str1) - strlen(str2) < 0)
printf("str1长度小于str2长度");
else
printf("str1长度大于str2长度");
return 0;
}
str1
长度为7,str2
长度为11,7-11 = -3,但是这里的7和11是无符号整形,他们俩相减会得到一个非常大的数,而不是-3,所以这个程序会输出str1长度大于str2长度
模拟实现strlen
这里有三种方法进行模拟实现
第一种:常规方法:
int my_strlen1(const char* str)
{
assert(str != NULL);
int count = 0;
while (*str != '\0')
{
count++;
str++;
}
return count;
}
第二种:递归:
int my_strlen2(const char* str)
{
assert(str != NULL);
if (*str != '\0')
{
return 1 + my_strlen2(str+1);
}
else
{
return 0;
}
}
第三种:指针相减:
int my_strlen3(const char* str)
{
assert(str != NULL);
const char* start = str;
while (*str != '/0')
{
str++;
}
return str - start;
}
两个指向同一块空间的两个指针,两个指针相减得到这两个指针间的元素的个数
2.长度不受限制的字符串函数
strcpy
char* strcpy(char * destination, const char * source );
- 这个函数的作用是将
source
中的字符串拷贝到空间destination
中 destination
是目标空间,函数将复制的字符串放到这个目标空间中source
是源字符串,函数会复制源字符串,因为只是对源字符串进行复制,并不会改变它,所以可以将源字符串写成const
- 目标空间必须足够大,以确保能存放源字符串。
- 目标空间必须可变。
- 源字符串必须以 ‘\0’ 结束,因为源字符串读到\0就停止拷贝。
- 函数会将源字符串中的 ‘\0’ 拷贝到目标空间。
strcpy
的使用:
int main()
{
char arr1[20] = {0};
char arr2[] = "abcdef";
strcpy(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
如果在源字符串中提前放一个\0
,那么函数只会拷贝到\0
int main()
{
char arr1[20] ="xxxxxxx";
char arr2[] = "ab\0cdef";
strcpy(arr1, arr2);
return 0;
}
这里可以看到,在
arr2
中ab
后面放了一个\0
,则在函数拷贝时只会拷贝ab\0
我们可以调试函数看一下:拷贝前的
arr1
:
拷贝后的arr1
:
可以看到,只拷贝了ab\0
模拟实现strcpy
最常规写法:
void my_strcpy(char* destination, const char* source)
{
assert(destination != NULL && source != NULL);
while (*source!='\0')
{
*destination = *source;
destination++;
source++;
}
*destination = *source;//拷贝\0
}
这么要注意的一点就是,
strcpy
拷贝过程中会源字符串中最后的\0
,但是如果以while (*source!='\0')
为循环条件的话,最后的\0
就不会被拷贝
所以要在循环外部额外再拷贝一次*destination = *source;
简化一点:
void my_strcpy(char* destination, char* source)
{
while (*src != '\0')
{
*destination++ = *source++;
}
*destination = *source;//拷贝\0
}
再简化:
前面的两种写法都需要在循环外部额外再对
\0
进行拷贝,而下面的写法直接将\0
的拷贝也放到了循环中进行
void my_strcpy(char* destination,const char* source)
{
assert(destination != NULL && source != NULL);
while (*destination++ = *source++)
{
;
}
}
strcat
char * strcat ( char * destination, const char * source );
- 这个函数的作用是将
source
中的字符串追加到destination
的后面 - 目标空间要有\0结尾,因为要直到从哪开始追加
- 源字符串要有\0结尾,因为要知道何时结束追加
- 目标空间必须有足够的大,能容纳下源字符串的内容。
- 目标空间必须可修改。
strcat
的使用:
int main()
{
char arr1[20] = "hello";
char arr2[] = " world";
strcat(arr1, " world");
printf("%s", arr1);
return 0;
}
当一个字符串,自己对自己追加的时候会有问题:
以字符串abcdef
追加自己为例
从\0开始追加,a追加到f,本来f后面是\0,到了\0就停止追加,但是前面追加时已经就将\0覆盖了,所以不会停止
所以当一个字符串自己追加自己,会发生死循环
模拟实现strcat
void my_strcat(char* des, char* src)
{
assert(des && src);
while (*des != '\0')
{
des++;
}
while (*des++ = *src++)
{
;
}
}
strcmp
int strcmp ( const char * str1, const char * str2 );
-
标准规定(在非VS环境中):
- 第一个字符串大于第二个字符串,则返回大于0的数字
- 第一个字符串等于第二个字符串,则返回0
- 第一个字符串小于第二个字符串,则返回小于0的数字
-
而在VS环境中:
- 第一个字符串大于第二个字符串,则返回1
- 第一个字符串等于第二个字符串,则返回0
- 第一个字符串小于第二个字符串,则返回-1
对于两个字符串的大小,比较的并不是字符串的长度,而是一个一个比较组成字符串的字符,字符对应ASCII码值大的字符大。例如:'z'
大于'a'
模拟实现strcmp
int my_strcmp(char* str1,char* str2)
{
assert(str1 && str2);
while (*str1 == *str2)
{
if (*str1 == '\0')
{
return 0;
}
str1++;
str2++;
}
//VS环境下:
if (*str1 > *str2)
return 1;
else
return -1;
//非VS环境下:
//return *str1-*str2;
}
3.长度受限制的字符串函数介绍
上面介绍的都是没有长度限制字符函数,它们都是进行到\0
就停止,而接下来的三个函数有长度限制
strncpy
char * strncpy ( char * destination, const char * source, size_t num );
strncpy
与strcpy
的区别就是:strcpy
会将源字符串一直拷贝到\0
,而strncpy
会拷贝前num
个字符strncpy
是不会在拷贝后自动加\0
的
//验证strncpy是不会在拷贝后自动加'\0'的
int main()
{
char arr1[20] = "xxxxxxx";
char arr2[] = "abcdef";
strncpy(arr1, arr2,2);
return 0;
}
将
arr2
前2个字符拷贝到arr1
中,在监视中看到:
所以可知:strncpy
不会自动添加\0
num
比源字符串要长,多出来的部分用\0来补
//验证num比源字符串要长,多出来的部分用\0来补
int main()
{
char arr1[20] = "xxxxxxxxxxxxx";
char arr2[] = "abc";
strncpy(arr1, arr2,6);
return 0;
}
模拟实现strncpy
void my_strncpy(char* str1, char* str2, int n)
{
assert(str1 && str2);
if (n < 0) return;
while (n>0)
{
if (*str2 == '\0')
{
*str1 = '\0';
str1++;
n--;
}
else
{
*str1 = *str2;
str1++;
str2++;
n--;
}
}
}
这里模拟实现时,要注意当
num
大于源字符串长度时需要补\0这点,其余地方很简单
strncat
char * strncat ( char * destination, const char * source, size_t num );
- 将
source
字符串中的前num
个字符追加到destination
后面 - 追加后,函数会在追加后的
destination
后面自动加\0
- 如果
num
大于source
的大小,则多余的部分补\0
模拟实现strncat
void my_strncat(char* str1, char* str2, int n)
{
assert(str1 && str2);
if (n < 0) return;
while (*str1 != '\0')
{
str1++;
}
while (n > 0)
{
if (*str2 == '\0')
{
*str1 = '\0';
str1++;
n--;
}
else
{
*str1 = *str2;
str1++;
str2++;
n--;
}
}
*str1 = '\0';
}
strncmp
int strncmp ( const char * str1, const char * str2, size_t num );
- 比较到出现另个字符不一样或者一个字符串结束或者num个字符全部比较完。
- 返回值的情况与
strcmp
相同
模拟实现strncmp
int my_strncmp(char* str1, char* str2, int n)
{
assert(str1 && str2);
if (n < 0) return;
while (n > 0)
{
while (*str1 == *str2)
{
if (n == 1)
{
return 0;
}
str1++;
str2++;
n--;
}
if (*str1 > *str2)
return 1;
else
return -1;
}
}
4.字符串查找
strstr
char * strstr ( const char *str1, const char * str2);
- 在
str1
字符串中查找str2
字符串 - 返回
str2
在str1
中出现的一个位置的地址
模拟实现strstr
void* my_strstr(char* str1, char* str2)
{
assert(str1 && str2);
if (*str2 == '0')
{
return (char*)str1;
}
const char* s1 = str1;
const char* s2 = str2;
const char* cp = str1;
while (*cp)
{
s1 = cp;
s2 = str2;
while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2)
{
s1++;
s2++;
}
if (*s2 == '0')
{
return (char*)cp;
}
cp++;
}
return NULL;
}
strtok
char * strtok ( char * str, const char * sep );
- sep参数是个字符串,定义了用作分隔符的字符集合
- 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
int main()
{
char arr1[] = "abc#defa|gh";
char arr2[] = "#|";
strtok(arr1,arr2);
}
有一个字符串abc#defa|gh
,可以看出它被字符#
和|
分隔开,strtok
的作用是取出被字符#
和|
分隔开的每块字符串
- strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
int main()
{
char arr1[] = "abc#defa|gh";
char arr2[] = "#|";
char arr3[20] = { 0 };
strcpy(arr3, arr1);//防止strtok直接修改被操作的字符串,所以需要拷贝一份
char* ret = strtok(arr3, arr2);
printf("%s", ret);
return 0;
}
取出第一块被分隔的字符串
abc
并且将#
改为\0
,此时的arr3
为abc\0defa|gh
,ret
为abc
的地址,输出ret
为:
- strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
- strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
- 如果字符串中不存在更多的标记,则返回 NULL 指针。
int main()
{
char arr1[] = "abc#defa|gh";
char arr2[] = "#|";
char arr3[20] = { 0 };
strcpy(arr3, arr1);//防止strtok直接修改被操作的字符串,所以需要拷贝一份
char* ret = strtok(arr3, arr2);
printf("%s\n", ret);
ret = strtok(NULL, arr2);//为了取出剩下部分的字符串,第一个参数需要改为NULL
printf("%s\n", ret);
ret = strtok(NULL, arr2);
printf("%s\n", ret);
return 0;
}
如果一个字符串被某些字符分隔为很多部分,如果我们一个一个地取出就会使代码冗长,这里我们可以使用循环解决
int main()
{
char arr1[] = "abc#defa|gh";
char arr2[] = "#|";
char arr3[20] = { 0 };
strcpy(arr3, arr1);
char* ret = NULL;
for (ret = strtok(arr3, arr2); ret != NULL; ret = strtok(NULL, arr2))
{
printf("%s\n", ret);
}
return 0;
}
输出结果:
5.错误信息报告
strerror
char * strerror ( int errnum );
- C语言的库函数在运行的时候,如果发生一些错误,就会将错误码放到一个变量中:
erron
- 错误码是一些数字:0 1 2 3 4 5……
strerror
就是返回这些错误码对应的字符串的首字符地址
我们可以看一下这些错误码对应的错误信息
int main()
{
printf("%s\n", strerror(0));
printf("%s\n", strerror(1));
printf("%s\n", strerror(2));
printf("%s\n", strerror(3));
printf("%s\n", strerror(4));
printf("%s\n", strerror(5));
return 0;
}
当然,平常使用时并不需要我们将具体数字传入函数中
我们通常将变量errno
传入函数中strerror(errno)
而这个errno
变量在头文件errno.h
中,使用前必须添加这个头文件。
另外还有一个
perror
函数,使用这个函数,可以直接将错误信息打印出来,并且如果perror
中传了字符串,这个函数会先将传过去的字符串打印出来,再打印一个冒号,最后打印错误信息