Appearance
字符函数与字符串函数
预计学习时长:约 3 小时(仅 L1 主线,建议分两次完成)· 运行环境:VS2026 / gcc / clang 均可
🎯 学习目标
学完本讲,你将能够:
- 用
<ctype.h>的分类函数(islower等)和转换函数(toupper/tolower)处理字符 - 说清
strlen返回size_t(无符号)带来的减法陷阱 - 熟练使用
strcpy/strcat/strcmp/strstr/strtok五大字符串函数,并记住各自的使用前提 - 知道
strncpy/strncat/strncmp为什么"更安全",以及strncpy仍可能留下的坑 - 亲手模拟实现
my_strlen、my_strcpy、my_strcmp、my_strstr - 用
strerror/perror把错误码翻译成人话
🔗 先修知识
- 第 7 讲:数组:字符串就是
char数组 - 第 8 讲:函数:这些全是库函数,本讲还要模拟实现它们
- 第 3 讲:数据类型和变量:
size_t是无符号类型
先修自检(三问)
- 字符串
"hi"在内存里占几个字节?分别存了什么? strcpy的两个参数,哪个是目的地、哪个是来源?- 数组传进函数后为什么
sizeof会失灵?
一、字符串回顾:一切围绕 \0
C 语言没有专门的"字符串类型",字符串就是一个以 '\0' 结尾的 char 数组。这个 '\0'(ASCII 值 0,叫"空字符")是字符串的结束标志——本讲几乎所有函数的行为、几乎所有 bug,都跟它有关。
c
char arr[] = "hi";内存里其实是 3 个字节:'h'、'i'、'\0'。写 "hi" 时编译器自动补上 '\0'。
处理字符和字符串太常用了,C 标准库在两个头文件里准备了一整套函数:
| 头文件 | 管什么 |
|---|---|
<ctype.h> | 单个字符的分类与转换 |
<string.h> | 字符串的长度、拷贝、拼接、比较、查找、分割 |
二、字符分类函数(<ctype.h>)
这套函数判断"一个字符属于哪一类",用法高度一致:参数是一个字符,返回非 0 表示"是",返回 0 表示"不是"。
| 函数 | 判断内容 |
|---|---|
isdigit(c) | 是否数字 0-9 |
isupper(c) | 是否大写字母 A-Z |
islower(c) | 是否小写字母 a-z |
isalpha(c) | 是否字母 |
isalnum(c) | 是否字母或数字 |
isspace(c) | 是否空白(空格、\t、\n 等) |
📄 classify.c · ✅ 完整程序(可直接复制编译)
c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <ctype.h>
int main()
{
char str[] = "Test String.\n";
for (int i = 0; str[i]; i++)
{
if (islower(str[i])) // 是小写就转大写
str[i] -= 32; // ASCII 里 小写 = 大写 + 32
putchar(str[i]);
}
return 0;
}text
TEST STRING.三、字符转换函数(<ctype.h>)
与其手动 -32,不如用现成的转换函数,更安全也更直观:
c
int tolower(int c); // 大写转小写;非大写字母原样返回
int toupper(int c); // 小写转大写;非小写字母原样返回把上面的例子改成用 toupper:
📄 convert.c · ✅ 完整程序(可直接复制编译)
c
#include <stdio.h>
#include <ctype.h>
int main()
{
char str[] = "Test String.\n";
for (int i = 0; str[i]; i++)
{
if (islower(str[i]))
str[i] = toupper(str[i]);
putchar(str[i]);
}
return 0;
}💡
-32这种写法依赖"ASCII 里大小写正好差 32",虽然几乎所有平台都成立,但用toupper才是标准做法,可读性也好得多。
四、strlen——求字符串长度
🧩 函数原型(示意片段) · 需 #include <string.h>
c
size_t strlen(const char *str);- 功能:统计
str中'\0'之前的字符个数(不含'\0') - 返回类型是
size_t,一个无符号整数,用%zd打印 - 参数指向的字符串必须以
'\0'结尾,否则函数会一直往后数,越界
📄 strlen_demo.c · ✅ 完整程序(可直接复制编译)
c
#include <stdio.h>
#include <string.h>
int main()
{
const char *str = "abcdef";
printf("%zd\n", strlen(str)); // 6,不含结尾 \0
printf("%zu\n", sizeof(str)); // 8(这是指针本身的大小,不是字符串长度!)
return 0;
}⚠️ 别把
strlen和sizeof搞混:sizeof(str)这里str是个指针,得到的是指针大小(64 位下 8);要对字符数组用sizeof(arr)-1才近似长度,而strlen永远数到'\0'为止。
4.1 经典陷阱:无符号数相减
📄 strlen_trap.c · ✅ 完整程序(可直接复制编译)
c
#include <stdio.h>
#include <string.h>
int main()
{
const char *str1 = "abcdef"; // 长度 6
const char *str2 = "bbb"; // 长度 3
if (strlen(str2) - strlen(str1) > 0)
printf("str2 > str1\n"); // 竟然走进来!
else
printf("str1 > str2\n");
return 0;
}输出 str2 > str1。明明 3 < 6,为什么?因为 strlen 返回的是无符号数,3 - 6 不会变成 -3,而是"绕"成一个巨大的正数,于是 > 0 成立。
教训:不要用两个 size_t 相减再判断正负。 要比长度就直接 strlen(a) > strlen(b)。
4.2 模拟实现 strlen(三种写法)
📘 提高(L2):my_strlen 的三种实现
方式 1:计数器
c
#include <assert.h>
size_t my_strlen(const char *str)
{
assert(str != NULL);
size_t count = 0;
while (*str) // 不是 '\0' 就继续
{
count++;
str++;
}
return count;
}方式 2:递归(不借助临时变量计数)
c
size_t my_strlen(const char *str)
{
assert(str != NULL);
if (*str == '\0')
return 0;
else
return 1 + my_strlen(str + 1); // 见第 9 讲递归
}方式 3:指针减指针
c
size_t my_strlen(const char *str)
{
assert(str != NULL);
const char *start = str;
while (*str != '\0')
str++;
return str - start; // 两个指针相减 = 中间的元素个数
}三种都数到 '\0' 停下。方式 3 的"指针减指针"到第 12 讲会正式解释。
五、strcpy——字符串拷贝
🧩 函数原型(示意片段) · 需 #include <string.h>
c
char *strcpy(char *dest, const char *src);把 src 指向的字符串(连同结尾的 '\0')拷进 dest,返回 dest 的起始地址。
📄 strcpy_demo.c · ✅ 完整程序(可直接复制编译)
c
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[10] = {0};
char arr2[] = "hello";
strcpy(arr1, arr2); // hello 连同 \0 一起拷进 arr1
printf("%s\n", arr1);
return 0;
}四条使用前提(缺一不可):
- 源字符串必须以
'\0'结束——否则不知道拷到哪 '\0'也会被拷过去——目标里得到的是一个完整字符串- 目标空间必须足够大——装得下源串 +
'\0',否则越界写 - 目标空间必须可修改——不能是字符串常量
📘 提高(L2):my_strcpy 模拟实现
c
#include <assert.h>
char *my_strcpy(char *dest, const char *src)
{
char *ret = dest;
assert(dest != NULL);
assert(src != NULL);
while ((*dest++ = *src++)) // 先赋值再判断;拷到 '\0'(值为0)时循环停
{
;
}
return ret;
}while ((*dest++ = *src++)) 是本讲最经典的一行:把 *src 赋给 *dest,两个指针各自后移,整个赋值表达式的值就是刚拷过去的那个字符。拷到 '\0' 时表达式值为 0,循环结束——'\0' 恰好也被拷了过去。
六、strcat——字符串追加
🧩 函数原型(示意片段) · 需 #include <string.h>
c
char *strcat(char *dest, const char *src);把 src 接到 dest 的末尾(覆盖 dest 原来的 '\0',接完再补一个新 '\0'),返回 dest 起始地址。
📄 strcat_demo.c · ✅ 完整程序(可直接复制编译)
c
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[20] = "hello "; // 注意留足空间
char arr2[] = "world";
strcat(arr1, arr2);
printf("%s\n", arr1); // hello world
return 0;
}使用前提与 strcpy 类似,多一条:目标字符串自己必须已经以 '\0' 结尾,否则 strcat 找不到"往哪接"。
📘 提高(L2):my_strcat 模拟实现
c
#include <assert.h>
char *my_strcat(char *dest, const char *src)
{
char *ret = dest;
assert(dest != NULL && src != NULL);
while (*dest) // 先走到 dest 的末尾 '\0' 处
dest++;
while ((*dest++ = *src++)) // 从那里开始拷贝,逻辑同 my_strcpy
;
return ret;
}七、strcmp——字符串比较
🧩 函数原型(示意片段) · 需 #include <string.h>
c
int strcmp(const char *str1, const char *str2);从第一个字符开始逐个比较 ASCII 值,遇到不等或到 '\0' 结束。返回值(标准只保证符号,不保证具体数值):
| 情况 | 返回值 |
|---|---|
| str1 > str2 | 大于 0 的数 |
| str1 == str2 | 0 |
| str1 < str2 | 小于 0 的数 |
📄 strcmp_demo.c · ✅ 完整程序(可直接复制编译)
c
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abq";
int ret = strcmp(arr1, arr2);
if (ret > 0)
printf("arr1 > arr2\n");
else if (ret == 0)
printf("arr1 == arr2\n");
else
printf("arr1 < arr2\n"); // 走这里:第三个字符 'c'(99) < 'q'(113)
return 0;
}⚠️ 字符串不能用
==比较内容:arr1 == arr2比的是两个地址是否相同,永远为假。比较内容必须用strcmp。
📘 提高(L2):my_strcmp 模拟实现
c
#include <assert.h>
int my_strcmp(const char *str1, const char *str2)
{
assert(str1 != NULL && str2 != NULL);
while (*str1 == *str2)
{
if (*str1 == '\0') // 一起走到结尾,相等
return 0;
str1++;
str2++;
}
return (unsigned char)*str1 - (unsigned char)*str2;
}最后一行为什么要转 unsigned char?因为 char 在不同平台可能有符号可能无符号,遇到高位字符时差值符号会不确定。转成 unsigned char 再相减,才能保证"按字节大小比较"的语义正确。
八、带长度限制的安全版本:strncpy / strncat / strncmp
strcpy / strcat 有个通病:只要源串太长就越界。标准库提供了带 num 参数的版本,限制最多操作多少个字符:
c
char *strncpy(char *dest, const char *src, size_t num); // 最多拷 num 个
char *strncat(char *dest, const char *src, size_t num); // 最多接 num 个
int strncmp(const char *str1, const char *str2, size_t num); // 最多比 num 个📄 strn_demo.c · ✅ 完整程序(可直接复制编译)
c
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[20] = {0};
char arr2[] = "abcdefghi";
strncpy(arr1, arr2, 5); // 只拷前 5 个
printf("%s\n", arr1); // abcde
printf("%d\n", strncmp("abcdef", "abcqw", 3)); // 前3个相同 -> 0
printf("%d\n", strncmp("abcdef", "abcqw", 4)); // 'd' < 'q' -> 负数
return 0;
}它们更安全,是因为在写代码时就被迫思考"目标空间够不够"。
⚠️ strncpy 仍有一个坑
strncpy 拷满 num 个字符后,不保证自动补 '\0'——如果源串长度 ≥ num,目标数组就没有结尾,之后用 %s 打印会越界读。正确姿势是手动补一个 0:
c
char dest[6];
strncpy(dest, src, 5);
dest[5] = '\0'; // 自己补结尾,才安全正因为这些坑,很多团队干脆用各编译器的"边界检查版"(如 MSVC 的
strcpy_s)或第三方安全字符串库。但理解标准版的行为,是读懂一切 C 代码的基础。
九、strstr——查找子串
🧩 函数原型(示意片段) · 需 #include <string.h>
c
char *strstr(const char *haystack, const char *needle);在"大字符串 haystack"里找"子串 needle"第一次出现的位置:找到返回该位置指针,找不到返回 NULL。
📄 strstr_demo.c · ✅ 完整程序(可直接复制编译)
c
#include <stdio.h>
#include <string.h>
int main()
{
char str[] = "This is a simple string";
char *p = strstr(str, "simple");
if (p != NULL)
printf("%s\n", p); // 从 simple 开始打印到结尾:simple string
else
printf("查找的字符串不存在\n");
return 0;
}📘 提高(L2):my_strstr 模拟实现(暴力查找)
c
char *my_strstr(const char *haystack, const char *needle)
{
if (*needle == '\0') // 空串,按标准返回 haystack
return (char *)haystack;
while (*haystack)
{
const char *s1 = haystack;
const char *s2 = needle;
while (*s1 && *s2 && *s1 == *s2) // 逐字符比对
{
s1++;
s2++;
}
if (*s2 == '\0') // needle 走完 = 完全匹配
return (char *)haystack;
haystack++; // 起点后移一位,再试
}
return NULL;
}这叫"暴力匹配"。还有一种高效得多的算法叫 KMP,属于数据结构课程的内容,这里知道有它就行。
十、strtok——分割字符串
🧩 函数原型(示意片段) · 需 #include <string.h>
c
char *strtok(char *str, const char *delim);按 delim 里的分隔符,把 str 切成一段段子串。用法很特别:
- 第一次调用传入待切字符串;之后每次传
NULL,表示"接着切上一个" - 返回当前段指针;没有更多段时返回
NULL delim里每个字符都算独立分隔符;连续分隔符当成一个处理
📄 strtok_demo.c · ✅ 完整程序(可直接复制编译)
c
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = "192.168.6.111";
char buf[30];
strcpy(buf, arr); // 先拷贝一份,保护原串(见下)
const char *sep = ".";
for (char *p = strtok(buf, sep); p != NULL; p = strtok(NULL, sep))
{
printf("%s\n", p);
}
return 0;
}text
192
168
6
111⚠️ strtok 三条注意事项
- 破坏性:它把分隔符原地改写成
'\0',原字符串会被改掉。要保留原串,先strcpy拷贝一份再切(如上例切buf不切arr)。 - 连续分隔符:
"a..b"用.切,两个连续的点当成一个,不会切出空串。 - 它内部用全局状态记住"切到哪了",所以不能对两个字符串同时切、也不能在多线程里裸用(多线程有
strtok_r,属进阶)。
十一、strerror 和 perror——把错误码翻译成人话
库函数出错时,会把一个错误码写进全局变量 errno(需 #include <errno.h>)。但错误码是个整数(如 2),人看不懂,于是有:
c
char *strerror(int errnum); // 把错误码转成描述字符串的地址
void perror(const char *str); // 直接打印 "str: 错误描述"📄 strerror_demo.c · ✅ 完整程序(可直接复制编译)
c
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
FILE *pFile = fopen("unexist.ent", "r"); // 故意打开不存在的文件
if (pFile == NULL)
{
printf("错误信息:%s\n", strerror(errno)); // 用 errno 换描述
return 1;
}
return 0;
}perror 一步到位,等价于把上面的 printf + strerror 合起来:
📄 perror_demo.c · ✅ 完整程序(可直接复制编译)
c
#include <stdio.h>
#include <errno.h>
int main()
{
FILE *pFile = fopen("unexist.ent", "r");
if (pFile == NULL)
{
perror("错误信息"); // 自动打印 "错误信息: No such file or directory"
return 1;
}
return 0;
}💡 打印 0~10 号错误码:
for (int i = 0; i <= 10; i++) printf("%d: %s\n", i, strerror(i));——能看到2: No such file or directory等。不同平台描述文字略有差异。
⚠️ 常见坑与报错(本讲汇总)
| 你看到的现象 | 原因 | 修法 |
|---|---|---|
strlen 相减判断大小,结果反了 | 返回的是无符号 size_t | 别相减,直接 strlen(a) > strlen(b) |
| 拷贝/拼接后程序崩溃或数据乱 | 目标数组太小,越界写 | 目标空间要 ≥ 源串 + 1;或用带 num 的安全版 |
strcpy(常量串, ...) 崩溃 | 目标不可修改 | 目标是可写数组,不能是 "abc" 这种常量 |
strncpy 后打印 %s 出乱码 | 拷满 num 时没补 '\0' | 手动 dest[num] = '\0' |
用 == 比两个字符串永远不等 | == 比的是地址 | 用 strcmp |
strtok 把原字符串改花了 | 它是破坏性操作 | 先 strcpy 拷贝再切 |
忘了 #include <string.h> | 隐式声明,返回值按 int 处理出错 | 用字符串函数必带 <string.h>,字符函数带 <ctype.h> |
strlen 把 \0 也算进去了? | 误解 | strlen 不含 \0;sizeof 才含 |
🛠️ 动手练习
规则:先独立做,卡住了再依次展开提示,不要一上来就看答案。
📊 本讲网页练习进度0 / 5(0%)
进度自动保存在本浏览器;编程题不计数,请在编辑器中完成。
练习 1 · ⭐ 基础 · 读输出
知识点:strlen 无符号减法陷阱
题干:写出输出:
📄 ex1.c · ✅ 完整程序
c
#include <stdio.h>
#include <string.h>
int main()
{
const char *a = "hello"; // 5
const char *b = "hi"; // 2
if (strlen(b) - strlen(a) > 0)
printf("A\n");
else
printf("B\n");
return 0;
}请写出输出(一个字母):
空①
练习 2 · ⭐ 基础 · 选择判断
知识点:字符串函数前提
题干:下列说法正确的是?(多选)
练习 3 · ⭐ 基础 · 读输出
知识点:strcmp 返回值符号
题干:写出输出(strcmp 结果只判断正/零/负):
📄 ex3.c · ✅ 完整程序
c
#include <stdio.h>
#include <string.h>
int main()
{
printf("%d\n", strcmp("abc", "abc"));
printf("%s\n", strcmp("abc", "abd") < 0 ? "less" : "notless");
printf("%s\n", strcmp("abd", "abc") > 0 ? "greater" : "notgreater");
return 0;
}请按三行顺序写出输出(第一行是数字,用空格分隔三行结果即可):
空①
练习 4 · ⭐ 基础 · 找错改错
知识点:strncpy 补结尾
题干:下面代码打印 arr1 可能出乱码,请填出应补的那一行(在 strncpy 之后、printf 之前):
🧩 待改错片段
c
char arr1[6] = {0};
strncpy(arr1, "abcdef", 5);
printf("%s\n", arr1);请写出补上的那一行语句:
空①
练习 5 · ⭐ 基础 · 程序填空
知识点:my_strlen 计数器实现
题干:补全用计数器求字符串长度的函数(两个空):
🧩 待补全片段
c
size_t my_strlen(const char *str)
{
size_t count = 0;
while (______) // 第一空:循环条件
{
count++;
______; // 第二空:指针后移
}
return count;
}空①空②
练习 6 · ⭐⭐ 提高 · 写小程序
知识点:字符分类 + 转换函数
🛠 动手编程题 · 请在 VS2026(或你的编辑器)中完成
写一个程序,把用户输入的一行字符串中的大写字母转小写、小写字母转大写(大小写互换),其他字符保持不变。
✅ 过关标准:输入 Hello 输出 hELLO;输入 Ab9! 输出 aB9!。
网页内无法练习写代码,亲手敲、亲手编译才能真正学会。下方按顺序展开 思路 → 步骤 → 答案。
💡 提示 1(思路)
用 isupper / islower 判断,再用 tolower / toupper 转换。读一行用 fgets。
🧭 提示 2(步骤)
char str[100]; fgets(str, sizeof(str), stdin);- 遍历每个字符:大写转小写,小写转大写,其余不动
printf("%s", str);
🔑 参考答案与解释
c
#include <stdio.h>
#include <ctype.h>
int main()
{
char str[100];
fgets(str, sizeof(str), stdin);
for (int i = 0; str[i]; i++)
{
if (isupper(str[i]))
str[i] = tolower(str[i]);
else if (islower(str[i]))
str[i] = toupper(str[i]);
}
printf("%s", str);
return 0;
}要点:fgets 会把行尾换行符也读进来,但它不是字母,两个分支都不匹配,原样保留,不影响结果。判断顺序无所谓,因为一个字符不可能既大写又小写。
练习 7 · ⭐⭐ 提高 · 写小程序
知识点:strlen + strcmp 组合
🛠 动手编程题 · 请在 VS2026(或你的编辑器)中完成
写一个函数 int strend(const char *s, const char *t),判断字符串 t 是否是 s 的结尾部分。是返回 1,否返回 0。
✅ 过关标准:strend("hello world", "world") 返回 1;strend("hello world", "word") 返回 0;strend("hello world", "world!") 返回 0。
网页内无法练习写代码,亲手敲、亲手编译才能真正学会。下方按顺序展开 思路 → 步骤 → 答案。
💡 提示 1(思路)
如果 t 比 s 还长,肯定不是结尾。否则比较 s 的最后 strlen(t) 个字符和 t 是否相等。
🧭 提示 2(步骤)
int ls = strlen(s), lt = strlen(t);if (lt > ls) return 0;return strcmp(s + ls - lt, t) == 0;(s + ls - lt就是 s 里"倒数第 lt 个字符"的位置)
🔑 参考答案与解释
c
#include <stdio.h>
#include <string.h>
int strend(const char *s, const char *t)
{
int ls = (int)strlen(s);
int lt = (int)strlen(t);
if (lt > ls)
return 0;
return strcmp(s + ls - lt, t) == 0 ? 1 : 0;
}
int main()
{
printf("%d\n", strend("hello world", "world")); // 1
printf("%d\n", strend("hello world", "word")); // 0
printf("%d\n", strend("hello world", "world!")); // 0
return 0;
}关键:s + ls - lt 是指针运算,指向 s 中倒数第 lt 个字符,从这里到结尾正好 lt 个字符,用 strcmp 一比即可。指针运算到第 12 讲正式展开。
练习 8 · ⭐⭐⭐ 挑战 · 写小程序
知识点:strtok 分割 + 数组逆序
🛠 动手编程题 · 请在 VS2026(或你的编辑器)中完成
给定以空格分隔的句子 "hello world C language",用 strtok 把每个单词提取出来,倒序打印(结果应为 language C world hello)。
✅ 过关标准:输出 language C world hello。
网页内无法练习写代码,亲手敲、亲手编译才能真正学会。下方按顺序展开 思路 → 步骤 → 答案。
💡 提示 1(思路)
先用 strtok 把每个单词的指针存进一个"字符指针数组",再从后往前打印。
🧭 提示 2(步骤)
strcpy(buf, arr);拷贝一份(strtok 会破坏原串)char *words[20]; int n = 0;for (p = strtok(buf, " "); p; p = strtok(NULL, " ")) words[n++] = p;- 倒序
for (i = n-1; i >= 0; i--) printf("%s ", words[i]);
🔑 参考答案与解释
c
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = "hello world C language";
char buf[100];
strcpy(buf, arr); // 拷贝,保护原串
char *words[20];
int count = 0;
for (char *p = strtok(buf, " "); p != NULL; p = strtok(NULL, " "))
{
words[count++] = p; // 每个单词的起始指针存起来
}
for (int i = count - 1; i >= 0; i--)
{
printf("%s ", words[i]); // 倒序打印
}
printf("\n");
return 0;
}要点:strtok 返回的是指向 buf 内部的指针,所以 buf 必须活得比 words[] 久(不能是临时量)。这个"切词 + 存指针 + 逆序"的套路,是后面做单词统计、简易命令解析的基础。
📝 小结与自测
本讲主线:字符串是带 '\0' 的 char 数组,一切函数都围绕这个结尾标志。<ctype.h> 管单字符(isxxx 判断、toxxx 转换),<string.h> 管字符串:strlen(数到 \0、返回无符号)、strcpy(连 \0 一起拷)、strcat(接在 \0 处)、strcmp(逐字符比 ASCII)、strstr(找子串)、strtok(原地切分、破坏原串)、strerror/perror(错误码转文字)。带 n 的版本更安全但 strncpy 要自己补 \0。
自测四问:
strlen("hi")和sizeof((char[]){"hi"})分别是多少?为什么?- 为什么
strncpy拷满长度后还要手动补'\0'? strcmp的返回值标准规定一定是 -1/0/1 吗?strtok第一次调用和后续调用,第一个参数有什么不同?
🔬 选学拓展(L3)
以下内容超出主线要求
适合学有余力的同学,不影响后续学习。
L3-1:为什么字符串函数参数都带 const
你会注意到 strlen(const char *str)、strcmp(const char *a, const char *b) 的参数都带 const,而 strcpy(char *dest, const char *src) 里 dest 不带、src 带。规律是:
- 只读不写的参数 →
const(strlen 不改串、strcmp 两个串都不改、strcpy 的源不改) - 要写入的参数 → 不带 const(strcpy/strcat 的目标)
const 是一种"承诺":编译器会帮你挡住"手滑修改不该改的东西"。这是第 8 讲提过的接口设计思想,也是 C 语言 const 正确性(const-correctness)的起点。
L3-2:strtok 的"接着切"到底怎么实现的
strtok(NULL, sep) 不传字符串却能继续切上一个——因为 strtok 内部用了一个静态局部变量记住了"上次切到的位置"(正是第 8 讲 static 修饰局部变量的经典用途)。这也解释了它的两个限制:
- 同一时刻只能切一个字符串(静态变量只有一个);
- 多线程不安全(多个线程共享这个状态会互相踩)。
需要可重入版本时,POSIX 提供 strtok_r,把"上次位置"作为额外参数由调用者自己保管。
L3-3:自己实现一个 memcpy(预告第 11 讲)
字符串函数处理的是"以 \0 结尾的文本"。如果是不含 \0 的任意内存(比如一个 int 数组),就得用内存函数 memcpy。它的模拟实现和 my_strcpy 神似,只是要额外传"字节数":
📄 my_memcpy.c · ✅ 完整程序(可直接复制编译)
c
#include <stdio.h>
#include <assert.h>
#include <stddef.h>
void *my_memcpy(void *dest, const void *src, size_t count)
{
assert(dest != NULL && src != NULL);
char *d = (char *)dest;
const char *s = (const char *)src;
while (count--)
*d++ = *s++;
return dest;
}
int main()
{
int a[5] = {1, 2, 3, 4, 5};
int b[5] = {0};
my_memcpy(b, a, sizeof(a));
for (int i = 0; i < 5; i++)
printf("%d ", b[i]);
printf("\n");
return 0;
}text
1 2 3 4 5为什么这里用 char* 逐字节拷、void* 作参数?为什么 count 是字节数而不是元素个数?这些正是第 11 讲:内存函数要回答的。