C语言--字符串函数
在编程的过程中,会经常处理字符串,为了方便操作处理,C语言标准库中提供了许多字符串函数给我们使用,下面就让我们来学习一下。
先开门见山:所有的字符串函数都要包含一个头文件#include <string.h>
一、strlen的使用和模拟实现
1.1 strlen的使用
函数原型:
size_t strlen ( const char * str );
功能:求字符串的长度
参数str:需要计算长度的字符串(本质是首元素地址)
返回类型:size_t
注:字符串是以'\0'为结束标志,所以strlen计算的是字符串开头到'\0'之间(不包括’\0')的字符个数
使用:
#include <stdio.h>
#include <string.h>
int main()
{
char str[] = "hello world!";
size_t len = strlen(str);
printf("%zd\n", len);//结果是12
return 0;
}
注意:strlen库函数是以’\0’为结束标志来计算字符串个数的,所以如果字符串中含有’\0’,strlen只会计算从开始到第一个’\0’之间(不包括’\0’)的字符个数
下面这段代码就是一个例子:
#include <stdio.h>
#include <string.h>
int main()
{
char str[] = "hello\0world!";
size_t len = strlen(str);
printf("%zd\n", len);//结果是5
return 0;
}
还有一点值得注意的是strlen库函数的返回类型是size_t
int main()
{
if (strlen("abc") - strlen("abcdefg") > 0)
{
printf(">");
}
else
{
printf("<");
}
return 0;
}
这段代码许多人肯定会认为strlen("abc")结果是3;strlen("abcdefg")的结果是6,3 - 6= - 3 < 0 ,会打印<。其实是错误的。
虽然一个是3,一个是6,但是它们是size_t类型的数字,本质上是一种无符号类型,是恒大于0的。
所以会打印>。
1.2 strlen的模拟实现
方法一:计数器
思路:从字符串开始向后遍历,如果没有遇到'\0',计数器count就+1。
size_t my_strlen(const char* str)//不需要对str本身改变,所以使用const修饰
{
assert(str);//防止str为NULL
size_t count = 0;
while (*str)//*str != '\0'
{
str++;
count++;
}
return count;
}
int main()
{
char str[] = "hello world!";
size_t len = my_strlen(str);
printf("%zd\n", len);
return 0;
}
方法二:
递归法(不创建临时变量法)
思路:如果计算字符串"abcd"的长度,就等价于计算 “a” + “bcd” 的长度(
1 + my_strlen(str + 1)字符是char类型,所以+1一次跳过一个字符),以此类推,最后就等价与计算 “a” + “b” + “c” + “d” + "\0"的长度。
size_t my_strlen(const char* str)
{
assert(str);
if (*str == '\0')
return 0;
else
return 1 + my_strlen(str + 1);
}
int main()
{
char str[] = "hello world!";
size_t len = my_strlen(str);
printf("%zd\n", len);
return 0;
}
方法三:指针 - 指针法
思路:在之前学习指针时,我们知道,一个
char*类型的指针变量+n代表跳过n个字节,一个char类型的变量就是一个字节
,也就是+n跳过n个字符。
指向前面的指针 + n(== n个字节 == n个字符) = 指向后面指针;
所以用指向后面的指针 - 指向前面的指针 = 字符个数
size_t my_strlen(const char* str)
{
assert(str);
const char* end = str;
while (*end != '\0')
{
end++;
}
return end - str;
}
int main()
{
char str[] = "hello world!";
size_t len = my_strlen(str);
printf("%zd\n", len);
return 0;
}
二、strcpy的使用和模拟实现
2.1 strcpy的使用
函数原型:
char * strcpy ( char * destination, const char * source );
功能:将源字符串(source指向的字符串)复制拷贝到目标字符串(destination指向的字符串)
source:源字符串 类型是const char*,即source指向的字符串的内容不能被修改
destination:目标字符串
返回目标字符串destination(的首地址),类型是char*
注:为了避免溢出,destination指向的字符串的空间应足够大,以满足source字符串拷贝
使用:
int main()
{
char str1[] = "hello world!";
char str2[20] = { 0 };
strcpy(str2, str1);
printf("%s\n", str2);
return 0;
}
注:
1.源字符串末尾的’\0’也会被拷贝到目标字符串
int main()
{
char str1[] = "hello world!";
char str2[20] = "xxxxxxxxxxxxxxxx";
strcpy(str2, str1);
printf("%s\n", str2);
return 0;
}

2.目标字符串必须是可修改的,不能是常量字符串
int main()
{
char str1[] = "hello";
char* p = "I love you";
strcpy(p, str1);
printf("%s\n", p);
return 0;
}
上述代码中p指向的字符串是常量字符串,不能修改,所以使用strcpy复制拷贝就不会成功。
2.2 strcpy的模拟实现
char* my_strcpy(char* dest, const char* src)
{
assert(dest && src);//防止dest和src为NULL;
char* ret = dest;//记录目标字符串的起始位置,因为后面dest会变化
while (*src != '\0')
{
*dest = *src;
dest++;
src++;
}
*dest = *src;//拷贝'\0'
return ret;
}
我们会发现这段代码的核心部分会有些冗余,可以对其优化:
char* my_strcpy(char* dest, const char* src)
{
assert(dest && src);//防止dest和src为null;
char* ret = dest;//记录目标字符串的起始位置,因为后面dest会变化
while (*dest++ = *src++)
;
return ret;
}
当循环拷贝到*src == '\0'的时候,while中条件判断表达式*dest++ = *src++还会执行一遍,表达式的结果作为条件判断的结果,从而'\0'也完成了拷贝。
三、strcat的使用和模拟实现
3.1 strcat的使用
函数原型:
char * strcat ( char * destination, const char * source );
功能:将source直线的字符串连接到destination指向的字符串的后面;
source:源字符串,类型是const char*,即source指向的字符串的内容不可以被修改
destination:目标字符串
返回destination指向的字符串(的首字符地址),类型是char*
注意destination指向的字符串数组需要有足够大的空间,以用来存放连接后的字符串
dest(destination简写)指向的字符串中需包含'\0',strcat会从'\0'开始连接,dest指向的字符串末尾的'\0'会被source的第一个字符覆盖。
使用:
int main()
{
char str1[20] = "hello ";
char str2[] = "world!";
strcat(str1, str2);
printf("%s\n", str1);
return 0;
}
结果:

strcat函数进行字符串追加的时候,会把source指向的字符串的末尾'\0'也追加到destination指向的字符串后面。
int main()
{
char str1[20] = "xxxxxxxx\0xxxxxxxxx";
char str2[] = "world!";
char* ret = strcat(str1, str2);
printf("%s\n", str1);
printf("%s\n", ret);
return 0;
}
从第一个'\0'开始向后追加,world!'\0'都追加在后面

注意:destination指向的字符串和source指向的字符串不能有重叠,也就不能自己给自己追加。
#include <stdio.h>
#include <string.h>
int main()
{
char str1[] = "hello world!";
strcat(str1, str1 + 3);
return 0;
}
str1 和 str1 有重叠的部分,就无法得出正确结果。
3.2 strcat的模拟实现
char* my_strcat(char* dest, const char* src)
{
assert(dest && src);
char* ret = dest;
//1.找到dest的末尾'\0'
while (dest)
{
dest++;//注意++不能直接上面条件判断表达式中写,
//当*dest == '\0'的时候,它还是会+1,但是我们要找到的是'\0'这个位置
}
//2.连接
while (*dest++ = *src++)
;
return ret;
}
int main()
{
char str1[20] = "hello ";
char str2[] = "world!";
strcat(str1, str2);
printf("%s\n", str1);
return 0;
}
四、strcmp的使用和模拟实现
4.1 strcmp的使用
函数原型
int strcmp ( const char * str1, const char * str2 );
功能:将字符串str1和str2进行比较
str1,str2:两个要比较的字符串
比较逻辑:此函数开始比较每个字符串的第一个字符。如果它们彼此相等,则继续处理以下对,直到字符不同或到达终止字符'\0'。
返回值类型:int
如果第一个不相等的字符在str1中的值 > str2中的,返回一个大于0的数;
str1 == str2 ,返回0;
str1 < str2 ,返回一个小于0的数。
使用:
int main()
{
char str1[] = "abcd";
char str2[] = "abcf";
int ret = strcmp(str1, str2);
if (ret > 0)
{
printf("str1大\n");
}
else if (ret < 0)
{
printf("str2大\n");
}
else
{
printf("相等\n");
}
return 0;
}
结果:前面三个字符abc都一样,因为f > d,所以返回一个小于0的数。

4.2 strcmp的模拟实现
//strcmp的模拟实现
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
while (*str1 == *str2)
{
if (*str1 == '\0')
return 0;//当比较到'\0'的时候说明两字符串已经相等,返回0
str1++;
str2++;
}
return *str1 - *str2;//如果存在不相等,返回字符差值,就可以直接表示二者大小
}
int main()
{
char str1[] = "abcd";
char str2[] = "abcf";
int ret = my_strcmp(str1, str2);
printf("%d\n", ret);
return 0;
}
五、strncpy的使用
函数原型:
char * strncpy ( char * destination, const char * source, size_t num );
功能:将源字符串(source指向的字符串)的前num个字符,拷贝到目标字符串(destination指向的字符串)中
source:源字符串 类型是const char*,即source指向的字符串的内容不能被修改
destination:目标字符串
num:需要复制拷贝的最大字符个数,类型是size_t
返回目标字符串destination(的首地址),类型是char*
使用:
#include <stdio.h>
#include <string.h>
int main()
{
char str1[] = "hello ";
char str2[] = "world!xxxx";
char* ret = strncpy(str1, str2, 3);
printf("%s\n", str1);
printf("%s\n", ret);
return 0;
}
- 如果源字符串source的字符个数 >= num,则直接拷贝num个字符(注意:不包含
'\0')覆盖到目标字符串destination中
#include <stdio.h>
#include <string.h>
int main()
{
char str1[] = "hello xxx";
char str2[] = "world!xxxx";
char* ret = strncpy(str1, str2, 3);
printf("%s\n", str1);//worlo xxx
printf("%s\n", ret);
return 0;
}
#include <stdio.h>
#include <string.h>
int main()
{
char str1[] = "hello xxx";
char str2[] = "world!";
char* ret = strncpy(str1, str2, 6);//只拷贝覆盖6个字符,不包含'\0'
printf("%s\n", str1);//world!xxx
printf("%s\n", ret);
return 0;
}
- 如果源字符串source的字符个数 < num,则会在源字符串后面添加’\0’,直到num个。
#include <stdio.h>
#include <string.h>
int main()
{
char str1[] = "hello xxxxxx";
char str2[] = "world!";
char* ret = strncpy(str1, str2, 8);
printf("%s\n", str1);//world!
printf("%s\n", ret);
return 0;
}

六、strncat的使用
函数原型:
char * strncat ( char * destination, const char * source, size_t num );
功能:将源字符串(source指向的字符串)的前num个,追加到目标字符串(destination指向的字符串)之后,再追加一个
'\0'
source:源字符串 类型是const char*,即source指向的字符串的内容不需要被修改
destination:目标字符串
num:需要追加的个数,类型是size_t
返回目标字符串destination(的首地址),类型是char*
使用:和strcat函数一样,也是从字符串的第一个'\0'开始向后追加。
int main()
{
char str1[20] = "hello ";
char str2[] = "world!";
char* ret = strncat(str1, str2, 3);
printf("%s\n", str1);//hello wor
printf("%s\n", ret);
return 0;
}
- 如果源字符串source的字符个数 >= num,则在追加完num个字符后,会在末尾添加上一个
'\0'; - 如果源字符串source的字符个数 < num,因为已经把源字符串的
'\0'已经包括在内了,不会在添加'\0'。
int main()
{
char str1[20] = "hello \0xxxxxxxxxxx";
char str2[] = "world!";
char* ret = strncat(str1, str2, 8);
printf("%s\n", str1);//hello wor
printf("%s\n", ret);
return 0;
}

七、strncmp的使用
函数原型:
int strncmp ( const char * str1, const char * str2, size_t num );
功能:将字符串str1和str2的前num个字符进行比较
str1,str2:两个要比较的字符串
比较逻辑:此函数开始比较每个字符串的第一个字符。如果它们彼此相等,则继续处理以下对,直到字符不同或前num个字符比较完成都相等到达终止字符'\0'。
返回值类型:int
如果第一个不相等的字符在str1中的值 > str2中的,返回一个大于0的数;
str1 == str2 ,返回0;
str1 < str2 ,返回一个小于0的数。
使用:
int main()
{
char str1[] = "abcdef";
char str2[] = "abhdef";
int ret = strncmp(str1, str2, 3);
printf("%d\n", ret);//-1
return 0;
}
八、strstr的使用和模拟实现
8.1 strstr的使用
函数原型:
char * strstr ( const char * str1, const char * str2 );
功能:查找子字符串,在str1字符串中查找str2字符串
如果存在,则返回str2在str1中第一次出现的位置(指针);
如果不存在,则返回空指针NULL;
返回类型是char*
使用:
int main()
{
char str1[] = "This is an apple";
char str2[] = "is";
char* ret = strstr(str1, str2);
printf("%s\n", ret);//is is an apple
return 0;
}
8.2 strstr的模拟实现
思路: 首先,既然是字符串中找子字符串,那么就会需要通过两个指针一步一步向前走来进行比较。
第一种情况:从第一个字符开始向后匹配,str1的某一个位置开始匹配,匹配成功时,我们需要返回str2在str1中第一次出现的指针,但是此时指针的当前位置在末尾,所以需要一个指针来记录是从str1字符串的哪里开始匹配的。
第二种情况:当匹配过程中,发现从str1该位置开始的匹配失败
str1可以根据记录开始位置的指针回去,然后从下一个位置再开始匹配,但是此时str2同样也需要回到起始位置,所以还需要一个额外的指针记录str2字符串的起始位置。
第三种情况:从str1的所有位置开始的匹配都不成功,所以在str1中不存在str2子串,返回空指针
NULL;
模拟实现:
char* my_strstr(const char* str1, const char* str2)
{
assert(str1 && str2);
const char* s1 = NULL;//使用s1,s2代替str1,str2进行匹配
const char* s2 = NULL;
const char* cur = str1;//当前str1匹配的起始位置
if (*str2 == '\0')//如果str2传的是空字符串(注意不是NULL),则返回str1的地址
return (char*)str1;
while (*cur != '\0')
{
s1 = cur;//让str1开始下一个位置的匹配
s2 = str2;//让str2回到起始位置
while (*s1 != '\0' && *s2 != '\0'&& *s1 == *s2)//如果相等,开始匹配
{
s1++;
s2++;
}
if (*s2 == '\0')//当子串str2遇到末尾'\0',说明匹配成功
{
return (char*)cur;//返回当前开始匹配的位置(指针)
}
//没有遇到末尾'\0'就出循环,说明不相等,从该cur位置匹配失败,后面一位开始匹配
cur++;
}
//当str1遇到末尾'\0'还没有找到,说明不存在str2子串
return NULL;
}
int main()
{
char str1[] = "this is an apple";
char str2[] = "is";
char* ret = strstr(str1, str2);
if (ret)
{
printf("%s\n", ret);
}
else
{
printf("找不到");
}
return 0;
}
九、strtok的使用
函数原型:
char * strtok ( char * str, const char * delimiters );
功能:拆分含特定分隔符的字符串
str:要被拆分的字符串
delimiters:包含特定分隔符的字符串
每一次调用,返回一段字符串的起始地址,类型是char*
该函数会改变字符串的内容,所以使用strtok操作的字符串一般都是临时拷贝的内容并且可修改。
该函数与其他字符串函数的调用方式有所差异:
第一次调用时,需要参数str,第一个字符作为起始标记,函数会记住这个起始标记。
然后从起始位置开始扫描,直到遇到第一个分隔符,然后将该分隔符置为'\0',返回该段字符串的起始位置(地址);
后面再次调用时,str参数就只需要传NULL,因为函数会记录上一段从哪里截断拆分的,会从上一段结束的'\0'的后面进行下一次的拆分,每一次拆分都会将每一段的后面字符串的分隔符置为'\0',返回该段字符串的起始位置(地址)。
如果遇到str的末尾,则拆分结束,后续的调用函数都将返回空指针NULL。
简单使用:
int main()
{
char str1[] = "abc.def@ghi*jkl";
char str2[] = ".@*";
char* ret = strtok(str1, str2);
printf("%s\n", ret);
ret = strtok(NULL, str2);
printf("%s\n", ret);
ret = strtok(NULL, str2);
printf("%s\n", ret);
ret = strtok(NULL, str2);
printf("%s\n", ret);
ret = strtok(NULL, str2);
if (ret != NULL)
{
printf("%s\n", ret);
}
else
{
printf("已遇到str末尾,不需要拆分\n");
}
return 0;
}
调试:每一次函数调用拆分都会将分隔符置为’\0’



可是如果需要对特别长的包含分隔符的字符串进行拆分的话,一个一个写函数调用岂不是太麻烦?
所以我们可以使用for循环来解决:
初始化进行第一次调用切分ret = strtok(str1, str2),条件判断是调用返回值不为NULL,如果不为NULL,就可以进行下一次调用切分ret = strtok(NULL, str2)
int main()
{
char str1[] = "abc.def@ghi*jkl";
char str2[] = ".@*";
char* ret = NULL;
for (ret = strtok(str1, str2); ret != NULL; ret = strtok(NULL, str2))
{
printf("%s\n", ret);
}
return 0;
}
十、strerror的使用
函数原型:
char * strerror ( int errnum );
功能:获取指向错误码对应的错误信息字符串的指针
errnum:错误码
返回错误信息字符串(的指针),类型是char*
在不同的系统和C语言标准库中都规定了一些错误码,一般会放在errno.h这个头文件中说明,C语言程序启动后会有一个全局的errro变量来记录当前程序的错误码,当程序启动后无差错时,错误码errno为0。
当程序运行过程中出现错误,不同的错误会将不同的错误码存放在errno中。单凭错误码难以查明错误原因,而每一个错误码都会对应着有一个错误信息,该strerrno函数就是可以将错误码所对应的错误信息的字符串(的地址)返回。
使用变量errno必须要包含头文件#include <errno.h>
比如在文件操作中,如果文件不存在,以读的形式打开该文件,就会产生错误码:
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main()
{
//以读的形式打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)//文件不存在,打开失败
{
printf("%d %s", errno, strerror(errno));
return 1;
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}

我们也可以看看其他错误码所对应的字符串:
int main()
{
int i = 0;
for (i = 0; i < 20; i++)
{
printf("%d : %s\n", i, strerror(i));
}
return 0;
}

拓展:perror
如果打印错误信息需要printf和strerror函数,有点繁琐,所以有一个perror函数的作用相当于二者之和。
函数原型
void perror ( const char * str );
perror有直接打印error错误信息的能力
使用perror需要传一个字符串(任意),然后打印的时候会先打印该字符串,在打印冒号和空格,最后打印错误信息
不仅效果一样,而且还能添加字符串说明哪里出现的错误
int main()
{
//以读的形式打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)//文件不存在,打开失败
{
perror("fopen");
printf("%s", strerror(errno));
return 1;
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}

该函数在后面的学习中,用来打印错误信息回经常使用到。
结语:C语言-- 字符串函数 章节到这里就结束了。
本人才疏学浅,文章中有错误和有待改进的地方欢迎大家批评和指正,非常感谢您的阅读!如果本文对您又帮助,可以高抬贵手点点赞和关注哦!






4071

被折叠的 条评论
为什么被折叠?



