📖 第 9 章:字符串、字符和字节(Strings, Characters, and Bytes)

🔤 Kenneth Reek《Pointers on C》(C和指针)· Chapter 9
🗨️ 本章金句:「C 的字符串不是类型,是「以 \0 结尾的 char 数组」+ 一套 str 函数的约定。」
这一章在干嘛? 把 string.h 和 ctype.h 的常用函数按「不受限制 / 长度受限 / 查找 / 内存操作」四类盘一遍。函数签名是标准接口(要背),行为细节和陷阱(更要懂)。
9.1 字符串基础与长度9.2 复制、连接与比较:str 家族主干9.3 查找与标记:strchr / strstr / strtok9.4 字符分类与内存操作:ctype.h 与 mem 家族

9.1 字符串基础与长度

字符串字面量 "hello" 存在静态区,类型是 char[6]——5 个字符 + 隐式结尾 '\0'。一切 str 函数都靠找 '\0' 判断结束,所以 '\0' 丢了就一路读到天边:

size_t my_strlen(const char *s)     /* strlen 的典型实现思路 */
{
    const char *start = s;
    while (*s != '\0')
        s++;
    return (size_t)(s - start);     /* 指针相减 = 元素个数 */
}
/* 返回类型 size_t:无符号!strlen(a) - strlen(b) 结果永不为负 */
size_t 无符号陷阱: if (strlen(a) - strlen(b) > 0) 在 a 更短时并不为假——无符号下减出巨大正数。比较长度请用 strlen(a) > strlen(b)。

9.2 复制、连接与比较:str 家族主干

函数签名要点行为 / 陷阱
strcpy(dst, src)返回 dst连 '\0' 一起拷;不检查 dst 空间,src 过长即溢出
strncpy(dst, src, n)最多拷 n 字节src 短则补 0 凑满 n;src 长则不写 '\0'——必须手工补
strcat(dst, src)返回 dst把 src 接到 dst 尾部;dst 空间不足即溢出
strncat(dst, src, n)最多追加 n 字符好消息:它总会补 '\0',比 strncpy 规矩
strcmp(a, b)按字典序逐字符比返回 <0 / 0 / >0;判断相等必须 == 0,strcmp 返回的不是布尔
strncmp(a, b, n)最多比 n 字符前缀比较用
char buf[16];
strncpy(buf, src, sizeof buf - 1);  /* 留 1 字节给 '\0' */
buf[sizeof buf - 1] = '\0';         /* 手工保险:strncpy 可能不补 */

if (strcmp(cmd, "quit") == 0)       /* 正确的相等判断 */
    quit();

9.3 查找与标记:strchr / strstr / strtok

函数找什么返回
strchr(s, c)单个字符(含 '\0')首次出现处的指针,找不到 NULL
strrchr(s, c)单个字符,从右找最后一次出现处
strstr(s, sub)子串首次出现处,找不到 NULL
strpbrk(s, set)set 中任意一个字符首个命中处
strspn / strcspn(s, set)前缀连续匹配/不匹配 set长度(size_t)
strtok(s, delim)按分隔符切 token逐次调用返回各 token,完了 NULL
/* strtok 的状态机用法:第一次传串,之后传 NULL */
char line[] = "a,b,,c";
for (char *t = strtok(line, ","); t; t = strtok(NULL, ","))
    printf("[%s]\n", t);      /* [a] [b] [c] —— 空字段被跳过 */
strtok 会修改原串(把分隔符改写为 '\0')且有静态内部状态——不可重入、不能用于多线程。要保留原串先拷贝一份,线程场景用可重入版本(strtok_r)。

9.4 字符分类与内存操作:ctype.h 与 mem 家族

ctype.h:isalpha / isdigit / isspace / isupper / islower / iscntrl...,转换 tolower / toupper——非字母原样返回,参数必须是 unsigned char 值或 EOF(传负的 char 值是未定义行为)。

mem 家族:不认 '\0'、按字节数干活,因此能处理任意二进制数据:

memcpy(dst, src, n);    /* 快速拷贝 n 字节——两块内存不得重叠! */
memmove(dst, src, n);   /* 允许重叠:先当 src 整体读出再写,慢一点但安全 */
memset(buf, 0, n);      /* 清零 n 字节:结构体/缓冲区初始化标配 */
memcmp(a, b, n);        /* 按字节比较,返回 <0/0/>0 */
选择口诀: 处理字符串(遇 '\0' 止)用 str 家族;处理「指定长度的一坨字节」(图片、结构体、帧缓冲)用 mem 家族。重叠内存一律 memmove。
🧠 小测验
1. strncpy 的两个坑是什么?
① 源串不足 n 时用 \0 补满剩余空间,效率浪费;② 源串达到或超过 n 时目标串没有结尾 \0,后续 strlen/printf 会越界读。惯用法:strncpy 后手工 buf[n-1]='\0',或用 strncat(空串) 技巧。
2. strcmp 怎么判断两串相等?
strcmp(a,b) 返回负/零/正表示字典序先后,相等返回 0。必须写 strcmp(a,b)==0;直接把返回值当布尔用(如 if (strcmp(a,b)))会在「相等」时判假、不等时判真,方向完全反了。
3. memcpy 和 memmove 的区别?为什么 mem 家族适合二进制数据?
memcpy 不允许源/目标重叠(重叠时未定义),memmove 内部按重叠方向调整拷贝顺序、允许重叠。mem 家族按显式字节数工作、不依赖 \0 结尾,因此能安全处理任意二进制数据(字符串 str 家族遇到 \0 提前停)。
← 上一篇🏠 顶层目录下一篇 →