目录
- 1.字符分类函数
- 2.字符转换函数
- [3. strlen](#3. strlen)
-
- [3.1 代码演示](#3.1 代码演示)
- [3.2 strlen函数的返回值](#3.2 strlen函数的返回值)
- [3.3 strlen的模拟实现](#3.3 strlen的模拟实现)
- [4. strcpy](#4. strcpy)
-
- [4.1 代码演示](#4.1 代码演示)
- [4.2 模拟实现](#4.2 模拟实现)
- [5. strcat](#5. strcat)
-
- [5.1 代码演示](#5.1 代码演示)
- [5.2 模拟实现](#5.2 模拟实现)
- [6. strcmp](#6. strcmp)
-
- [6.1 代码演示](#6.1 代码演示)
- [6.2 模拟实现](#6.2 模拟实现)
- 7.strncpy
-
- [7.1 代码演示](#7.1 代码演示)
- [7.2 strcpy 与 strncpy 函数的对比](#7.2 strcpy 与 strncpy 函数的对比)
- [8. strncat](#8. strncat)
-
- [8.1 代码演示](#8.1 代码演示)
- [8.2 strcat 与 strncat 函数的对比](#8.2 strcat 与 strncat 函数的对比)
- 9.strncmp
-
- [9.1 代码演示](#9.1 代码演示)
- [9.2 strcmp 与 strncmp 函数的对比](#9.2 strcmp 与 strncmp 函数的对比)
- [10. strstr](#10. strstr)
-
- [10.1 代码演示](#10.1 代码演示)
- [10.2 strstr的模拟实现](#10.2 strstr的模拟实现)
- 11.strtok
-
- [11.1 代码演示](#11.1 代码演示)
- [11.2 注意事项](#11.2 注意事项)
- [12. strerror](#12. strerror)
-
- [12.1 代码演示](#12.1 代码演示)
- [12.2 perror](#12.2 perror)
1.字符分类函数
在 C 语言中,字符函数 是一类专门用来处理单个字符的函数,主要用于判断字符的类型,或者对字符进行大小写转换。
这些函数通常用于判断一个字符是否属于某一类,例如:
| 函数名 | 功能说明 |
|---|---|
isalnum() |
判断字符是否为字母或数字 ,a ~ z , A ~ Z,0 ~ 9 |
isalpha() |
判断字符是否为字母 ,字母a~z或A ~ Z |
isblank() |
判断字符是否为空白字符 ,如空格或制表符 |
iscntrl() |
判断字符是否为控制字符 |
isdigit() |
判断字符是否为十进制数字 ,十进制数字为'0' ~ '9' 的字符 |
isgraph() |
判断字符是否具有图形表示 ,即可显示但不包括空格 |
islower() |
判断字符是否为小写字母 :a ~ z |
isprint() |
判断字符是否为可打印字符,包括空白字符和图形字符 |
ispunct() |
判断字符是否为标点符号 |
isspace() |
判断字符是否为空白字符 ,如空格、换行、制表符等 |
isupper() |
判断字符是否为大写字母 ,A ~ Z |
isxdigit() |
判断字符是否为十六进制数字 ,包括所有十进制数字字符,小写字母a ~ f , 大写字母 A ~ F |
这些函数的使用都要包含头文件:<ctype.h>。
我来介绍一个函数isdigit,学习其他函数点击上述<ctype.h>头文件进行学习:
| 项目 | 内容 |
|---|---|
| 函数名 | isdigit |
| 所属头文件 | <ctype.h> |
| 函数原型 | int isdigit(int c); |
| 函数功能 | 判断字符是否为十进制数字字符 |
| 判断范围 | '0'、'1'、'2'、'3'、'4'、'5'、'6'、'7'、'8'、'9' |
| 参数说明 | c:需要被检测的字符,传入时会被转换为 int 类型 |
| 返回值 | 如果 c 是十进制数字字符,返回非零值,即真 |
| 返回值 | 如果 c 不是十进制数字字符,返回 0,即假 |
代码演示:
c
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <ctype.h>
int main()
{
int r = isdigit('8'); //是数字字符,返回非零值,表达式为真
printf("r = %d\n", r);
if (r)
printf("是数字字符\n");
else
printf("不是数字字符\n");
int r2 = isdigit('x'); //不是数字字符,返回零,表达式为假
printf("r2 = %d\n", r2);
if (r2)
printf("是数字字符\n");
else
printf("不是数字字符\n");
return 0;
}

练习:
写⼀个代码,将字符串中的⼩写字⺟转⼤写,其他字符不变。
讲解这道题之前,我们先类了解一下怎么在大小写字母之间相互转化:

代码实现:
c
#include <stdio.h>
#include <ctype.h>
int main()
{
// 定义一个字符数组,存放字符串
char str[] = "I am a student";
// 定义字符指针 p,指向字符串的首字符
char* p = str;
// 遍历字符串,直到遇到字符串结束标志 '\0'
while (*p != '\0')
{
// 判断当前字符是否为小写字母
if (islower(*p))
{
// 如果是小写字母,则减去 32 转换为大写字母
*p -= 32;
}
// 指针向后移动,继续处理下一个字符
p++;
}
// 输出转换后的字符串
printf("%s\n", str);
return 0;
}
2.字符转换函数
C语⾔提供了2个字符转换函数:
| 函数名 | 所属头文件 | 函数原型 | 函数功能 | 参数说明 | 返回值 |
|---|---|---|---|---|---|
tolower() |
<ctype.h> |
int tolower(int c); |
将大写字母转换为小写字母 | c:需要被转换的字符,会被转换为 int 类型进行处理 |
如果 c 是大写字母,则返回对应的小写字母;如果不能转换,则原样返回 |
toupper() |
<ctype.h> |
int toupper(int c); |
将小写字母转换为大写字母 | c:需要被转换的字符,会被转换为 int 类型进行处理 |
如果 c 是小写字母,则返回对应的大写字母;如果不能转换,则原样返回 |
上⾯的代码,我们将⼩写转⼤写,是-32完成的效果,有了转换函数,就可以直接使⽤ toupper 函数。
c
int main()
{
char str[] = "I am a student";
char* p = str;
while (*p != '\0')
{
*p = toupper(*p); // 将小写字母转换为大写字母,并重新赋值给当前位置
p++;
}
printf("%s\n", str);
return 0;
}
注意:toupper() 会把小写字母转换成大写字母,但是它的结果需要接收回来。如果只调用了函数,并没有把转换后的字符重新赋值给 *p,字符串内容是不会改变的。
3. strlen
| 项目 | 内容 |
|---|---|
| 函数名 | strlen() |
| 所属头文件 | <string.h> |
| 函数原型 | size_t strlen(const char *str); |
| 函数功能 | 统计字符串的长度 |
| 参数说明 | str:指向要统计长度的字符串 |
| 统计规则 | 统计字符串中 '\0' 之前的字符个数 |
是否统计 '\0' |
不统计字符串结束标志 '\0' |
| 返回值 | 返回字符串的长度,返回类型为 size_t |
| 注意事项 | strlen() 只计算字符串有效字符个数,不包括末尾的 '\0' |
3.1 代码演示
c
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = "abcdef";
size_t len = strlen(arr);
printf("%zu\n", len); //6
return 0;
}
strlen函数的注意事项:
| 注意事项 | 说明 |
|---|---|
字符串以 '\0' 作为结束标志 |
strlen() 统计的是 '\0' 前面的字符个数 |
不包含 '\0' |
strlen() 返回的长度不包括字符串末尾的结束符 '\0' |
| 参数必须是有效字符串 | 传入的字符串必须以 '\0' 结尾,否则可能会继续向后查找,导致结果错误 |
返回值类型是 size_t |
strlen() 的返回值是无符号整数类型(易错),表示字符串长度 |
| 需要包含头文件 | 使用 strlen() 需要包含头文件 <string.h> |
3.2 strlen函数的返回值
下面代码的结果是什么:
c
int main()
{
if (strlen("abc") - strlen("abcdef") > 0)
printf(">");
else if (strlen("abc") - strlen("abcdef") == 0)
printf("==");
else
printf("<");
return 0;
}


结论:比较字符串长度的时候,要直接比较两个strlen()的结果,不要先相减。
正确写法如下:
c
int main()
{
if (strlen("abc") > strlen("abcdef")) //直接比较大小
{
printf(">\n");
}
else
{
printf("<=\n");
}
return 0;
}
3.3 strlen的模拟实现
方式一(计数器的实现):因为这种方法在C语言深入浅出2中模拟实现了。
方式二(指针-指针):在C语言深入浅出1中模拟实现了。
方式三(递归):
递归的本质思想就是将大事化小 ,递归的相关知识点:C语言函数递归。
代码展示:
c
#include <stdio.h>
#include <assert.h>
size_t my_strlen(const char* str)
{
// 断言 str 不是空指针
assert(str);
if (*str != '\0')
{
// 当前字符计数 1 个,然后递归统计后面的字符
return 1 + my_strlen(str + 1);
}
else
{
// 遇到 '\0',说明字符串结束,返回 0
return 0;
}
}
int main()
{
char str[] = "abcdef";
size_t r = my_strlen(str);
printf("%zu\n", r);
return 0;
}
画图演示:

4. strcpy
| 项目 | 内容 |
|---|---|
| 函数名 | strcpy() |
| 所属头文件 | <string.h> |
| 函数原型 | char *strcpy(char *destination, const char *source); |
| 函数功能 | 将 source 指向的字符串复制到 destination 指向的字符数组中 |
| 复制内容 | 会复制整个字符串,包括字符串结束标志 '\0' |
参数 destination |
目标字符数组,用来存放复制后的字符串 |
参数 source |
源字符串,要被复制的字符串 |
| 返回值 | 返回目标空间 destination 的起始地址 |
4.1 代码演示
c
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[20] = "";
char arr2[] = "world";
strcpy(arr1, arr2);
printf("%s\n", arr1); //world
return 0;
}
strcpy函数的注意事项:
| 注意事项 | 说明 |
|---|---|
源字符串必须以 '\0' 结尾 |
strcpy() 会一直复制,直到遇到字符串结束标志 '\0' |
会复制 '\0' |
strcpy() 不仅复制普通字符,也会把末尾的 '\0' 一起复制到目标空间 |
| 目标空间必须足够大 | 目标数组要能存下源字符串的所有字符和最后的 '\0' |
| 目标空间必须可修改 | 目标空间不能是字符串常量,必须是可以被写入的字符数组 |
| 防止越界访问 | 如果目标空间太小,可能会造成数组越界,程序出错 |
4.2 模拟实现
c
#include <stdio.h>
#include <assert.h>
char* my_strcpy(char* dest, const char* src) // src 指向源字符串,const 保证不能通过 src 修改源字符串内容
{
char* p = dest; // 保存目标空间的起始地址,方便最后返回
assert(dest && src); //断言dest和src不为空指针
while (*src != '\0') //while(*dest++ = *src++)
{ //{
*dest = *src; // ;
dest++; //}
src++;
}
*dest = *src; // 将字符串结束标志 '\0' 复制到目标空间
// 返回目标空间的起始地址
return p;
}
int main()
{
char arr1[20] = "";
char arr2[] = "abcdef";
my_strcpy(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
画图推演:

5. strcat
| 项目 | 内容 |
|---|---|
| 函数名 | strcat() |
| 所属头文件 | <string.h> |
| 函数原型 | char *strcat(char *destination, const char *source); |
| 函数功能 | 将 source 指向的字符串追加到 destination 字符串的末尾 |
| 拼接规则 | destination 原本末尾的 '\0' 会被 source 的第一个字符覆盖 |
| 结束标志 | 拼接完成后,会在新字符串末尾自动添加 '\0' |
参数 destination |
目标字符串,用来接收拼接后的结果 |
参数 source |
源字符串,需要被追加到目标字符串后面 |
| 返回值 | 返回目标字符串 destination 的起始地址 |
5.1 代码演示
c
int main()
{
char arr1[20] = "hello ";
char arr2[] = "world";
strcat(arr1, arr2);
printf("%s\n", arr1); //hello world
return 0;
}
strcat相关函数的注意事项:
| 注意事项 | 说明 |
|---|---|
源字符串必须以 '\0' 结尾 |
strcat() 会从源字符串 source 中逐个追加字符,直到遇到 '\0' 才停止 |
目标字符串也必须包含 '\0' |
strcat() 需要先找到目标字符串 destination 末尾的 '\0',然后从这个位置开始追加 |
| 目标空间必须足够大 | destination 要能容纳原字符串、追加的源字符串以及最后的 '\0' |
| 目标空间必须可修改 | destination 不能是字符串常量,必须是可以被写入的字符数组 |
会覆盖目标字符串原来的 '\0' |
追加时,source 的第一个字符会覆盖 destination 原来的结束标志 '\0' |
最后会重新添加 '\0' |
拼接完成后,会在新字符串末尾放入新的字符串结束标志 |
5.2 模拟实现
c
#include <stdio.h>
#include <assert.h>
char* my_strcat(char* dest, const char* src)
{
assert(dest && src);
char* p = dest;
// 先找到目标字符串末尾的 '\0'
// strcat 要从 '\0' 的位置开始追加源字符串
while (*dest != '\0')
{
dest++;
}
// 将源字符串 src 中的字符逐个追加到 dest 后面
while ((*dest++ = *src++) != '\0')
{
;
}
return p;
}
int main()
{
char str1[20] = "hello ";
char str2[] = "world";
my_strcat(str1, str2);
printf("%s\n", str1);
return 0;
}
画图演示:

6. strcmp
| 项目 | 内容 |
|---|---|
| 函数名 | strcmp() |
| 所属头文件 | <string.h> |
| 函数原型 | int strcmp(const char *str1, const char *str2); |
| 函数功能 | 比较两个字符串的大小 |
| 比较方式 | 从两个字符串的第一个字符开始 ,逐个比较字符的 ASCII 码值 |
| 比较规则 | 如果当前字符相等,则继续比较下一个字符 |
| 停止条件 | 遇到两个不相等的字符,或者其中一个字符串结束时停止比较 |
参数 str1 |
指向第一个要比较的字符串 |
参数 str2 |
指向第二个要比较的字符串 |
返回值 > 0 |
第一个字符串大于第二个字符串 |
返回值 = 0 |
第一个字符串等于第二个字符串 |
返回值 < 0 |
第一个字符串小于第二个字符串 |
| 注意事项 | strcmp() 比较的是字符的 ASCII 码值,不是字符串长度 |
6.1 代码演示
c
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abq";
int ret = strcmp(arr1, arr2); // <0
printf("%d\n", ret); // -1
if(ret > 0)
printf("arr1 > arr2\n");
else if(ret == 0)
printf("arr1 == arr2\n");
else
printf("arr1 < arr2\n"); //arr1 < arr2
return 0;
}
6.2 模拟实现
c
#include <stdio.h>
#include <assert.h>
int my_strcmp(const char* s1, const char* s2)
{
// 断言 s1 和 s2 都不是空指针
assert(s1 && s2);
// 当两个字符串当前字符相等时,继续向后比较
while (*s1 == *s2)
{
// 如果两个字符都为 '\0'
// 说明两个字符串已经同时结束,内容完全相同
if (*s1 == '\0')
{
return 0;
}
// 两个指针同时向后移动,比较下一个字符
s1++;
s2++;
}
// 遇到不同字符时,返回两个字符 ASCII 码值的差
// 返回值 > 0:s1 大于 s2
// 返回值 = 0:s1 等于 s2
// 返回值 < 0:s1 小于 s2
return *s1 - *s2;
}
int main()
{
char str1[] = "abcdef";
char str2[] = "abc";
int r = my_strcmp(str1, str2);
if (r > 0)
printf("str1 > str2\n");
else if (r == 0)
printf("str1 == str2\n");
else
printf("str1 < str2\n");
return 0;
}
画图演示:

7.strncpy
| 项目 | 内容 |
|---|---|
| 函数名 | strncpy() |
| 所属头文件 | <string.h> |
| 函数原型 | char *strncpy(char *destination, const char *source, size_t num); |
| 函数功能 | 将 source 指向的字符串拷贝到 destination 指向的空间中,最多拷贝 num 个字符 |
参数 destination |
目标空间,用来存放拷贝后的内容 |
参数 source |
源字符串,即要被拷贝的数据 |
参数 num |
最多从 source 中拷贝的字符个数 |
| 返回值 | 返回目标空间 destination 的起始地址 |
7.1 代码演示
c
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[20] = { 0 };
char arr2[] = "abcdef";
char* str = strncpy(arr1, arr2, 5);
printf("%s\n", arr1); //abcde
return 0;
}
7.2 strcpy 与 strncpy 函数的对比
| 对比项 | strcpy |
strncpy |
|---|---|---|
| 函数原型 | char* strcpy(char* dest, const char* src); |
char* strncpy(char* dest, const char* src, size_t n); |
| 拷贝方式 | 从 src 开始一直拷贝,直到遇到 \0 |
最多拷贝 n 个字符 |
是否拷贝 \0 |
一定会拷贝源字符串末尾的 \0 |
不一定会拷贝 \0 |
| 结束条件 | 遇到 \0 停止 |
拷贝满 n 个字符或提前遇到 \0 |
| 目标空间要求 | dest 必须足够大 |
dest 至少能放下 n 个字符 |
| 越界风险 | 如果 dest 空间不足,容易越界 |
限制了最多拷贝 n 个字符,但使用不当仍有风险 |
补 \0 情况 |
自动补,因为会拷贝源字符串的 \0 |
如果 src 长度小于 n,后面会补 \0;如果 src 长度大于等于 n,不会自动补 \0 |
| 安全性 | 较低,完全依赖目标空间足够大 | 相对可控,但不等于绝对安全 |
| 返回值 | 返回 dest 的起始地址 |
返回 dest 的起始地址 |
8. strncat
| 项目 | 内容 |
|---|---|
| 函数原型 | char* strncat(char* destination, const char* source, size_t num); |
| 所属头文件 | <string.h> |
| 功能 | 将 source 指向的字符串追加到 destination 指向的字符串末尾,最多追加 num 个字符 |
| 参数 1 | destination:目标字符串空间 |
| 参数 2 | source:源字符串 |
| 参数 3 | num:最多追加的字符个数 |
| 返回值 | 返回 destination 的起始地址 |
是否自动补 \0 |
会 |
8.1 代码演示
c
#include <stdio.h>
#include <string.h>
int main()
{
char str1[20] = "hello\0----- ";
char str2[] = "worldxxxxxx";
strncat(str1, str2, 5);
printf("%s\n", str1); //helloworld
return 0;
}
画图演示:

8.2 strcat 与 strncat 函数的对比
| 对比项 | strcat |
strncat |
|---|---|---|
| 函数原型 | char* strcat(char* dest, const char* src); |
char* strncat(char* dest, const char* src, size_t num); |
| 参数数量 | 2 个参数 | 3 个参数,多了一个 num |
| 功能 | 将 src 字符串全部追加到 dest 末尾 |
将 src 中最多 num 个字符追加到 dest 末尾 |
| 追加长度 | 不限制,直到遇到 src 的 \0 |
最多追加 num 个字符 |
是否追加 \0 |
会把 src 的 \0 一起追加过去 |
追加完成后会自动补一个 \0 |
| 源字符串要求 | src 必须是以 \0 结尾的字符串 |
src 在前 num 个字符内可以没有 \0 |
| 目标字符串要求 | dest 原本必须有 \0 |
dest 原本也必须有 \0 |
| 目标空间要求 | 必须能容纳 dest 原内容 + src 全部内容 + \0 |
必须能容纳 dest 原内容 + 最多 num 个字符 + \0 |
| 安全性 | 较低,容易因空间不足越界 | 相对更安全,但仍需保证目标空间足够 |
| 返回值 | 返回 dest 起始地址 |
返回 dest 起始地址 |
9.strncmp
| 项目 | strncmp 函数说明 |
|---|---|
| 函数名称 | strncmp |
| 所属头文件 | <string.h> |
| 函数原型 | int strncmp(const char* str1, const char* str2, size_t num); |
| 函数功能 | 比较 str1 和 str2 指向的两个字符串,最多比较 num 个字符。 |
| 比较方式 | 从两个字符串的第一个字符开始逐个比较 ASCII 值。 |
| 停止条件 | 遇到不同字符、遇到字符串结束符 \0,或已经比较完 num 个字符。 |
参数 str1 |
指向第一个参与比较的字符串。 |
参数 str2 |
指向第二个参与比较的字符串。 |
参数 num |
最多比较的字符个数,类型为 size_t,是无符号整数类型。 |
返回值 < 0 |
第一个不同字符中,str1 的字符 ASCII 值小于 str2。 |
返回值 = 0 |
两个字符串在前 num 个字符内相同。 |
返回值 > 0 |
第一个不同字符中,str1 的字符 ASCII 值大于 str2。 |
9.1 代码演示
c
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcqw";
int ret1 = strncmp(arr1, arr2, 3);
printf("%d\n", ret1); //0
int ret2 = strncmp(arr1, arr2, 4);
printf("%d\n", ret2); //-1
return 0;
}
画图演示:

9.2 strcmp 与 strncmp 函数的对比
| 对比项 | strcmp |
strncmp |
|---|---|---|
| 函数原型 | int strcmp(const char* str1, const char* str2); |
int strncmp(const char* str1, const char* str2, size_t num); |
| 参数数量 | 2 个参数 | 3 个参数,多了 num |
| 比较范围 | 比较整个字符串 | 最多比较 num 个字符 |
| 结束条件 | 遇到不同字符或 \0 结束 |
遇到不同字符、\0 或比较满 num 个字符结束 |
返回值 < 0 |
str1 小于 str2 |
str1 在前 num 个字符内小于 str2 |
返回值 = 0 |
两个字符串完全相同 | 前 num 个字符相同 |
返回值 > 0 |
str1 大于 str2 |
str1 在前 num 个字符内大于 str2 |
| 安全性 | 不限制比较长度,可能访问更多字符 | 限制比较长度,相对更安全 |
| 使用场景 | 比较完整字符串 | 比较字符串前几个字符 |
10. strstr
| 项目 | strstr 函数说明 |
|---|---|
| 函数名称 | strstr |
| 所属头文件 | <string.h> |
| 函数原型 | char* strstr(const char* str1, const char* str2); |
| 函数功能 | 在 str1 指向的字符串中查找 str2 指向的子字符串 |
| 简单理解 | 在一个字符串中查找另一个字符串第一次出现的位置 |
参数 str1 |
被查找的字符串,也叫主字符串 |
参数 str2 |
要查找的字符串,也叫子字符串 |
| 返回值 | 如果找到 str2,返回 str2 在 str1 中第一次出现位置的地址 |
返回 NULL |
如果 str1 中不存在 str2,返回 NULL |
| 查找方式 | 从 str1 的第一个字符开始,依次匹配 str2 |
| 注意事项 | strstr 区分大小写,例如 "abc" 和 "ABC" 不相同 |
| 特殊情况 | 如果 str2 是空字符串 "",则返回 str1 的起始地址 |
10.1 代码演示
c
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[] = "abcdefabcdef";
char arr2[] = "deq";
char* ps = strstr(arr1, arr2);
if (ps != NULL)
{
printf("%s\n", ps);
}
else
{
printf("没有找到\n"); //打印
}
return 0;
}
画图演示:

10.2 strstr的模拟实现
c
#include <stdio.h>
#include <assert.h>
char* my_strstr(const char* str1, const char* str2)
{
// 断言:两个指针都不能为空
assert(str1 && str2);
if (*str2 == '\0') // 如果要查找的是空字符串,直接返回主字符串起始地址
return (char*)str1;
const char* s1 = NULL; // s1 用来从 str1 的某个位置开始尝试匹配
const char* s2 = NULL; // s2 用来遍历待查找字符串 str2
const char* p = str1; // p 用来遍历主字符串 str1,依次作为每次匹配的起始位置
while (*p)// 只要主字符串还没有走到 '\0',就继续查找
{
s1 = p; // 每一轮都从 p 指向的位置开始匹配
s2 = str2; // 每一轮都从 str2 的起始位置开始匹配
while (*s1 == *s2) // 当前字符相等时,继续比较下一个字符
{
s1++;
s2++;
if (*s2 == '\0') // 如果 s2 走到了 '\0',说明 str2 已经全部匹配成功
return (char*)p; // 返回匹配开始的位置
}
p++;// 如果本轮匹配失败,就让 p 后移一位,重新尝试匹配
}
return NULL;// 遍历完整个 str1 仍未找到 str2,返回 NULL
}
int main()
{
char str1[] = "abcdefabcdef";
char str2[] = "bce";
char* p = my_strstr(str1, str2);
if (p != NULL)
printf("%s\n", p);
else
printf("没有找到\n");
return 0;
}
注意下面这两种写法的不同的效果。
| 对比项 | 原写法:先比较,后移动 | 简写:边比较,边移动 |
|---|---|---|
| 代码形式 | while (*s1 == *s2) { s1++; s2++; } |
while (*s1++ == *s2++) |
| 是否语法正确 | 正确 | 语法也正确 |
| 是否等价 | 与当前代码逻辑匹配 | 不等价,不能直接替换 |
| 指针移动时机 | 只有当前字符相等时,才进入循环并移动 | 每次比较后,s1 和 s2 都会自动后移 |
| 比较失败时 | 指针停在第一个不相等的位置 | 即使比较失败,指针也已经后移 |
| 匹配成功时 | s2 正好停在 '\0' 位置 |
s2 可能已经越过 '\0' |
| 对后续判断的影响 | 后面 if (*s2 == '\0') 可以正确判断是否匹配成功 |
可能破坏 if (*s2 == '\0') 的判断 |
画图演示:

11.strtok
| 项目 | 内容 |
|---|---|
| 函数名 | strtok |
| 头文件 | #include <string.h> |
| 函数原型 | char *strtok(char *str, const char *delim); |
| 主要功能 | 根据 delim 指定的分隔符,将字符串 str 分割成多个子字符串。 |
| 工作原理 | strtok 会在原字符串中找到分隔符,并将分隔符替换 为字符串结束符 '\0',从而把原字符串切分成多个部分。 |
| 是否修改原字符串 | 会修改原字符串,因此传入的字符串不能是字符串常量。 |
参数 str |
首次调用时传入待分割的字符串;后续调用传入 NULL,表示继续分割同一个字符串。 |
参数 delim |
指定分隔符字符串,其中每个字符都可以作为独立的分隔符。 |
| 返回值 | 成功时返回当前分割出的子字符串的首地址。 |
| 结束条件 | 当没有更多子字符串可分割时,返回 NULL。 |
| 首次调用 | strtok(str, delim),用于开始分割字符串。 |
| 后续调用 | strtok(NULL, delim),继续从上一次结束的位置向后分割。 |
| 概念 | 说明 |
|---|---|
| 分隔符 | 用来分割字符串的字符 |
| 常见分隔符 | 空格、逗号、分号、冒号、换行符等 |
在 strtok 中的作用 |
告诉函数遇到哪些字符时进行切割 |
| 注意点 | delim 中的每个字符都会被单独看作分隔符 |
11.1 代码演示
c
#include <stdio.h>
#include <string.h>
int main()
{
char str[] = "byte-wizard@byte";// 原始字符串,里面包含 '-' 和 '@' 作为分隔符
// 定义分隔符字符串
const char* sep = "@-";// 注意:这里的 '@' 和 '-' 都会被当作分隔符
char buf[30] = { 0 };
strncpy(buf, str, 30);
/*
for 循环结构说明:
1. char* p = strtok(buf, sep)
第一次调用 strtok,传入要分割的字符串 buf
2. p != NULL
判断是否还分割出了有效的子字符串
3. p = strtok(NULL, sep)
后续调用 strtok,传入 NULL,表示继续分割上一次的字符串
*/
for (char* p = strtok(buf, sep); p != NULL; p = strtok(NULL, sep))
{
// 打印每次分割得到的子字符串
printf("%s\n", p);
}
return 0;
}
画图演示:

11.2 注意事项
| 注意事项 | 说明 | 示例/结果 |
|---|---|---|
| 会修改原字符串 | strtok 会把分隔符位置替换成 '\0',因此原字符串会被改变 |
"a-b-c" 会被改成 "a\0b\0c" |
| 不能直接处理字符串常量 | 字符串常量通常存放在只读区,strtok 会修改字符串,可能导致程序崩溃 |
错误:strtok("a-b-c", "-"); |
| 建议使用字符数组 | 如果要使用 strtok,应把字符串放到可修改的字符数组中 |
正确:char str[] = "a-b-c"; |
| 连续分隔符会被当作一个分隔符处理 | 多个连续分隔符不会产生空字符串 | "a--b" 使用 "-" 分割,结果是 "a"、"b" |
后续调用第一个参数传 NULL |
第一次传入字符串地址,之后继续分割时传入 NULL |
strtok(NULL, "-"); |
返回 NULL 表示分割结束 |
当没有更多子字符串时,strtok 返回 NULL |
可作为循环结束条件 |
str 不能随便传 NULL |
第一次调用时如果 str 为 NULL,且之前没有保存分割状态,行为未定义 |
首次调用必须传入有效字符串 |
| 分隔符字符串中每个字符都单独生效 | delim 中的每个字符都可以作为分隔符 |
",;" 表示逗号和分号都是分隔符 |
12. strerror
| 项目 | 内容 |
|---|---|
| 函数名 | strerror |
| 头文件 | #include <string.h> |
| 函数原型 | char *strerror(int errnum); |
| 参数 | errnum:错误码,一般传入 errno 的值 |
| 返回值 | 返回错误码对应的错误信息字符串的地址 |
| 主要功能 | 根据错误码 errnum,获取对应的错误描述信息 |
| 常见用途 | 当标准库函数调用失败时,通过 strerror(errno) 查看具体错误原因 |
| 是否修改原数据 | 不修改传入的错误码 |
| 返回内容 | 一个描述错误原因的字符串,例如 "No such file or directory" |
12.1 代码演示
c
#include <errno.h>
#include <string.h>
#include <stdio.h>
int main()
{
int i = 0;
for (i = 0; i <= 10; i++) {
printf("%d: %s\n", i, strerror(i));
}
return 0;
}
输出结果如下:

c
#include <stdio.h>
#include <string.h> // 使用 strerror 函数
#include <errno.h> // 使用 errno 变量,保存错误码
int main()
{
/*
fopen 用于打开文件。
参数说明:
1. "data.txt":要打开的文件名
2. "r":以只读方式打开文件
如果文件打开成功,fopen 返回文件指针;
如果文件打开失败,fopen 返回 NULL,并将错误码保存到 errno 中。
*/
FILE* pf = fopen("data.txt", "r");
// 判断文件是否打开失败
if (pf == NULL)
{
printf("%s\n", strerror(errno));
// 文件打开失败,程序异常结束,返回 1
return 1;
}
// 文件打开成功后,可以在这里进行读文件操作
//...
/*
文件使用完之后,需要关闭文件。
fclose 用于关闭文件,释放相关资源。
*/
fclose(pf);
/*
文件关闭后,将文件指针置为 NULL,
避免 pf 成为野指针。
*/
pf = NULL;
return 0;
}

12.2 perror
| 项目 | 内容 |
|---|---|
| 函数名 | perror |
| 头文件 | #include <stdio.h> |
| 函数原型 | void perror(const char *s); |
| 参数 | s:用户自定义的提示信息字符串 |
| 返回值 | 无返回值,返回类型是 void |
| 主要功能 | 根据当前 errno 中保存的错误码,打印对应的错误信息 |
| 输出位置 | 默认输出到标准错误流 stderr |
perror 会按照下面的格式输出:
c
自定义提示信息: 系统错误信息
例如:
c
perror("fopen");
如果文件不存在,可能输出:
c
fopen: No such file or directory
其中:
| 部分 | 含义 |
|---|---|
fopen |
传给 perror 的参数字符串 |
: |
perror 自动添加的冒号 |
| 空格 | perror 自动添加 |
No such file or directory |
根据 errno 得到的错误信息 |
代码演示:
c
#include <stdio.h>
#include <errno.h>
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
// perror 会自动根据 errno 打印错误原因
perror("fopen");
return 1;
}
fclose(pf);
pf = NULL;
return 0;
}
如果 data.txt 文件不存在,输出结果可能是:
c
fopen: No such file or directory
完