[C语言进阶]字符串与字符函数全解:从 ctype.h 到 string.h,一次搞懂 strlen、strcpy、memcpy 的所有坑!
本文深入讲解了 C 语言中字符串与字符处理相关的标准库函数,包括字符分类与转换函数(isalpha、isdigit、toupper 等)、字符串长度与内存大小的区别(strlen vs sizeof)、字符串拷贝与拼接(strcpy、strcat)、比较、查找、分割(strtok)、以及内存操作函数(memcpy、memmove、memset 等)。文章以示例和内存图形化方式呈现,结合安全编程建议
字符串和字符操作是 C 语言中最容易“踩坑”的部分:
strlen和sizeof傻傻分不清,strcpy一不小心就溢出,memcpy、memmove又看似相同实则不同。本文带你系统掌握<ctype.h>与<string.h>的所有常用函数,用通俗讲解、图解和实战代码彻底理清它们的区别与安全用法。
目录
5.1 strchr(s, c) / strrchr(s, c)
5.2 strstr(haystack, needle) —— 查找子串
5.3 strpbrk(s, accept) —— 查找任意匹配字符
九、错误信息:errno、strerror、strerror_r
一、字符分类与转换(<ctype.h>)
1.1 为什么要用这些函数?
ctype.h 里提供了一系列函数,可以判断一个字符属于哪类,比如是不是字母、数字、空格、符号等。
你当然可以手动写:
if ('0' <= c && c <= '9')
但库函数更安全、可读性更高,还能处理国际化字符。
这些函数在输入检查、字符串分词、去除空白中非常常见。
1.2 常见判断函数
isalpha(c) // 是否字母
isdigit(c) // 是否数字
isalnum(c) // 是否字母或数字
isspace(c) // 是否空白符(空格、换行、制表符等)
isupper(c) // 是否大写字母
islower(c) // 是否小写字母
⚠️ 参数要写成 (unsigned char)c,否则当字符是负数(例如中文编码)时可能出错。
例子:过滤非字母数字字符
#include <ctype.h>
#include <stdio.h>
int main() {
char s[] = "abc-123,OK!";
for (char *p = s; *p; ++p) {
if (isalnum((unsigned char)*p))
putchar(*p);
}
putchar('\n'); // 输出:abc123OK
}
1.3 大小写转换
toupper(c) // 转成大写
tolower(c) // 转成小写
示例:把字符串全转大写
#include <stdio.h>
#include <ctype.h>
int main() {
char str[] = "Test String.\n";
for (int i = 0; str[i]; i++) {
char c = str[i];
if (islower(c))
c = toupper(c);
putchar(c);
}
return 0;
}
输出:
TEST STRING.
图解说明
| 原字符 | 是否小写 | 转换结果 |
|---|---|---|
| T | 否 | T |
| e | 是 | E |
| s | 是 | S |
| t | 是 | T |
| (空格) | 否 | (空格) |
| S | 否 | S |
| t | 是 | T |
| r | 是 | R |
| i | 是 | I |
| n | 是 | N |
| g | 是 | G |
二、strlen 与 sizeof 的区别
| 函数 | 功能 | 计算时机 | 示例返回值 |
|---|---|---|---|
strlen |
计算字符串实际长度(不含 '\0') |
运行时 | 3 |
sizeof |
计算变量占的内存字节数 | 编译时 | 4 |
示例:
char a[] = "abc"; // 实际占 4 字节:'a','b','c','\0'
char *p = "abc"; // p 是指针,指向字符串常量区
sizeof(a) == 4;
strlen(a) == 3;
sizeof(p) == sizeof(char*); // 4 或 8,依平台不同
strlen(p) == 3;
内存图解
a[]:
┌───┬───┬───┬────┐
│ a │ b │ c │ \0 │
└───┴───┴───┴────┘
strlen(a) = 3
sizeof(a) = 4
p:
┌───────┐ ┌───┬───┬───┬────┐
│ 指针 │ ───▶ │ a │ b │ c │ \0 │
└───────┘ └───┴───┴───┴────┘
sizeof(p) = 8 (指针大小)
三、字符串拷贝与拼接
3.1 strcpy(dest, src)(危险但常用)
它会一直复制直到遇到 '\0',不检查空间大小。
char buf[4];
strcpy(buf, "hello"); // ❌ 溢出!程序可能崩溃
3.2 strncpy(dest, src, n)
拷贝不超过 n 个字符,但不会自动加 '\0',所以要手动补上。
strncpy(dest, src, n);
dest[n-1] = '\0';
推荐更安全的:
snprintf(dest, sizeof(dest), "%s", src);
3.3 拼接函数 strcat / strncat
strncat(buf, src, sizeof(buf) - strlen(buf) - 1);
模拟实现:
char *my_strcat(char *dest, const char *src) {
char *ret = dest;
while (*dest) dest++;
while ((*dest++ = *src++));
return ret;
}
内存图解(strcat)
初始:
dest: [H][i]['\0']
src : [!]['\0']
拼接后:
dest: [H][i][!]['\0']
strcat 就是把 src 从 dest 的 '\0' 开始追加。
四、字符串比较
4.1 strcmp(a, b) —— 比较两个字符串
int my_strcmp(const char *s1, const char *s2) {
while (*s1 == *s2) {
if (*s1 == '\0') return 0;
s1++;
s2++;
}
return *s1 - *s2;
}
对比过程图
s1: H e l l o
s2: H e l p !
↑
不同 ('l' vs 'p')
返回负数(因为 'l' < 'p')
4.2 strncmp(a, b, n)
比较前 n 个字符(常用于命令识别)。
if (strncmp(cmd, "quit", 4) == 0)
printf("退出命令\n");
五、查找函数
5.1 strchr(s, c) / strrchr(s, c)
从左/右查找字符。
char *p = strchr("abc-def", '-');
printf("%s\n", p); // 输出 "-def"
演示图示:
a b c - d e f
↑
strchr 找到 '-'
5.2 strstr(haystack, needle) —— 查找子串
char *p = strstr("hello world", "wor");
printf("%s\n", p); // 输出 "world"
演示图示::
h e l l o w o r l d
↑↑↑
找到 "wor"
5.3 strpbrk(s, accept) —— 查找任意匹配字符
char *p = strpbrk("abc-123", "0123456789");
printf("%s\n", p); // 输出 "123"
六、字符串分割:strtok / strtok_r
6.1 strtok 示例
char s[] = "a,b,c";
char *tok = strtok(s, ",");
while (tok) {
printf("%s\n", tok);
tok = strtok(NULL, ",");
}
输出:
a
b
c
演示图示::
原始字符串:
a , b , c \0
调用 strtok → 把逗号改成 '\0'
结果:
[a][\0][b][\0][c][\0]
注意:
strtok会修改原字符串。
6.2 strtok_r(线程安全版本)
char s[] = "a,b,c";
char *saveptr;
char *tok = strtok_r(s, ",", &saveptr);
while (tok) {
printf("%s\n", tok);
tok = strtok_r(NULL, ",", &saveptr);
}
七、内存函数(按字节操作)
这些函数不管 '\0',直接对内存块操作。
| 函数 | 功能 |
|---|---|
memcpy(dest, src, n) |
拷贝 n 字节(不能重叠) |
memmove(dest, src, n) |
拷贝 n 字节(允许重叠) |
memset(ptr, val, n) |
把内存填充为指定值 |
memcmp(a, b, n) |
比较内存前 n 字节 |
memchr(ptr, val, n) |
查找第一个匹配字节 |
图解:memcpy vs memmove
char buf[] = "abcdef";
memmove(buf + 2, buf, 4);
printf("%s\n", buf); // 输出 "ababcd"
| 操作 | 说明 |
|---|---|
memcpy |
直接复制,重叠区域可能被覆盖,未定义行为 |
memmove |
自动处理重叠,安全可靠 |
原始:
[ a ][ b ][ c ][ d ][ e ][ f ]
↑src
↑dest
memmove 会从后往前拷贝,避免覆盖。
演示图示::memset
char buf[5];
memset(buf, 'A', 5);
结果:
[A][A][A][A][A]
常用于初始化内存或清零结构体。
八、格式化输出:sprintf / snprintf
sprintf 不检查空间大小,可能导致缓冲区溢出。
推荐使用更安全的:
char buf[64];
snprintf(buf, sizeof buf, "%s-%d", "user", 42);
演示图示::
格式字符串: "%s-%d"
参数: "user", 42
结果写入 buf → "user-42"
如果输出被截断:
if (n >= sizeof buf)
printf("输出被截断,缓冲区太小\n");
九、错误信息:errno、strerror、strerror_r
当系统调用出错时,errno 会记录错误码。strerror 可以把它转成可读的错误字符串。
#include <errno.h>
#include <string.h>
#include <stdio.h>
char buf[128];
#ifdef __GLIBC__
char *msg = strerror_r(errno, buf, sizeof buf);
printf("error: %s\n", msg);
#else
if (strerror_r(errno, buf, sizeof buf) == 0)
printf("error: %s\n", buf);
#endif
演示图示::
系统调用失败 → errno 设置 → strerror(errno) 返回错误信息字符串
十、总结
ctype.h处理单个字符string.h操作字符串或内存块strlen是长度,sizeof是内存大小- 字符串操作都要小心
'\0' strncpy、snprintf比strcpy更安全memmove可处理重叠区域- 格式化输出尽量用
snprintf
掌握这些函数,就能在 C 语言中稳健地处理字符串、内存和文本。
它们是底层编程的基本功,也是理解更复杂代码的钥匙。
更多推荐

所有评论(0)