这一章在干嘛?

把 string.h 和 ctype.h 的常用函数按「不受限制 / 长度受限 / 查找 / 内存操作」四类盘一遍。函数签名是标准接口(要背),行为细节和陷阱(更要懂)。

9.1 字符串基础与长度

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

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

9.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。