字符串和字符操作是 C 语言中最容易“踩坑”的部分:strlen sizeof 傻傻分不清,strcpy 一不小心就溢出memcpymemmove 又看似相同实则不同。本文带你系统掌握 <ctype.h><string.h> 的所有常用函数,用通俗讲解、图解和实战代码彻底理清它们的区别与安全用法。


目录

一、字符分类与转换( )

1.1 为什么要用这些函数?

1.2 常见判断函数

1.3 大小写转换

图解说明

二、strlen 与 sizeof 的区别

 内存图解

三、字符串拷贝与拼接

3.1 strcpy(dest, src)(危险但常用)

3.2 strncpy(dest, src, n)

3.3 拼接函数 strcat / strncat

 内存图解(strcat)

四、字符串比较

4.1 strcmp(a, b) —— 比较两个字符串

 对比过程图

4.2 strncmp(a, b, n)

五、查找函数

5.1 strchr(s, c) / strrchr(s, c)

5.2 strstr(haystack, needle) —— 查找子串

5.3 strpbrk(s, accept) —— 查找任意匹配字符

六、字符串分割:strtok / strtok_r

6.1 strtok 示例

6.2 strtok_r(线程安全版本)

七、内存函数(按字节操作)

图解:memcpy vs memmove

演示图示::memset

八、格式化输出:sprintf / snprintf

九、错误信息: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

二、strlensizeof 的区别

函数 功能 计算时机 示例返回值
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 就是把 srcdest'\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");

九、错误信息:errnostrerrorstrerror_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'
  • strncpysnprintfstrcpy 更安全
  • memmove 可处理重叠区域
  • 格式化输出尽量用 snprintf

掌握这些函数,就能在 C 语言中稳健地处理字符串、内存和文本。
它们是底层编程的基本功,也是理解更复杂代码的钥匙。


Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐