C 语言的字符串处理函数 全部定义在 <string.h> 头文件中,专门用于处理以 \0 结尾的字符数组(C 语言字符串)。
1、字符串长度
1.1 strlen
c
size_t strlen(const char *str);
- 作用 :计算字符串有效长度 (不含末尾的
\0) - 参数:目标字符串
- 返回值:无符号整数(长度)
- 示例
c
char str1[] = "Hello";
char str2[] = "Hello\0World";
char *str3 = "Hello";
printf("str1: %s\n", str1); // prints "Hello"
printf("str2: %s\n", str2); // prints "Hello"
// sizeof()返回的是数组的总大小,包括结尾的'\0'
printf("str1: %llu\n", sizeof(str1)); // prints 6
printf("str2: %llu\n", sizeof(str2)); // prints 12
// strlen() 返回的是字符串的长度,不包括结尾的'\0', 遇到'\0'会停止计算
printf("str1: %llu\n", strlen(str1)); // prints 5
printf("str2: %llu\n", strlen(str2)); // prints 5
// 字符串常量str3是只读的,不能修改
printf("str1: %llu\n", strlen(str1));
! NOTE
注意 :
strlen遇到\0就停止,不计入结束符 。如果是数组,可以使用sizeof计算数组的长度,包含尾部的'\0'
2、字符串复制
2.1 strcpy
c
char *strcpy(char *dest, const char *src);
- 作用 :不安全 字符串复制(把 src 复制到 dest,包含
\0) - 参数:目标、源
- 返回值:指向 dest 的指针
- 示例
c
char src[] = "Hello, world!";
char dest[20];
strcpy(dest, src);
printf("%s\n", dest); // prints "Hello, world!"
!NOTE
- 如果 dest 空间不足,会内存越界。因此需要确保dest空间能够容纳src字符串长度。
- 遇到'\0'字符结束拷贝。
2.2 strncpy
c
char *strncpy(char *dest, const char *src, size_t n);
- 作用 :安全复制,最多复制 n 个字符
- 注意 :
- 不会自动补
\0!必须手动加 - 源长度 < n:剩余空间填
\0 - 遇到\0会结束;
- 不会自动补
- 示例
c
// 遇到\0会结束
char src[] = "Hello\0, world!";
char dest[20];
strncpy(dest, src, 10);
printf("%s\n", dest); // 应该输出 "Hello, world!" 但实际上会输出 "Hello"
c
// 不会自动加\0
char src[] = "Hello, world!";
char dest[20] = "aaaaaaaaaa";
strncpy(dest, src, 5);
printf("%s\n", dest); // prints "Helloaaaaa"
// 需要手动加\0
dest[5] = '\0';
printf("%s\n", dest); // prints "Hello"
c
// 最安全的做法
char src[] = "Hello, world!";
char dest[20] = "aaaaaaaaaaaaaaa";
// 最安全的拷贝方法,防止移除,并且最后补了0
strncpy(dest, src, sizeof(src));
printf("%s\n", dest);
3、字符串拼接
3.1 strcat
c
char *strcat(char *dest, const char *src);
- 作用:把 src 拼接到 dest 末尾
- 危险:不检查空间,容易越界
- 推荐使用:[[字符串处理函数#3.2 strncat|strncat]]
3.2 strncat
c
char *strncat(char *dest, const char *src, size_t n);
- 作用 :最多拼接 n 个字符,自动补 \0
- 推荐使用
c
char src[10] = "World";
char dst[12] = "Hello";
strncat(dst, src, 5);
printf("%s\n", dst); // HelloWorld
4、字符串比较
4.1 strcmp
c
int strcmp(const char *s1, const char *s2);
- 作用 :按 ASCII 码逐字符比较
- 返回值 :
- 0:相等
- <0:s1 < s2
-
0:s1 > s2
- 示例
c
char str1[] = "aaa";
char str2[] = "bbb";
char str3[] = "aaa";
// 比较两个字符串的大小, 若str1 < str2, 则返回负值;
// 若str1 > str2, 则返回正值;
// 若str1 == str2, 则返回0.
// 注意: 比较时, 若两个字符串相等, 则返回0;
// 若不相等, 则返回第一个不相同字符的ASCII码之差.
int ret = strcmp(str1, str2);
if (ret < 0) {
printf("str1 < str2\n");
}
else if (ret > 0) {
printf("str1 > str2\n");
}
else {
printf("str1 == str2\n");
}
printf("str2 == str1 ? %d\n", ret); // 返回第一个不相同字符的ASCII码之差 -1
ret = strcmp(str1, str3);
printf("str1 == str3? %d\n", ret); // 返回0, 因为两个字符串相等
4.2 strncmp
c
int strncmp(const char *s1, const char *s2, size_t n);
作用:只比较前 n 个字符
5、字符串查找
5.1 strchr
c
char *strchr(const char *str, int c);
- 作用 :查找字符 c 第一次出现的位置
- 返回值 :找到返回地址,没找到返回
NULL - 示例
c
char str[20] = "hello world";
char *p = strchr(str, 'l');
// 找不到会返回NULL,否则返回指向第一个匹配字符的指针
// 先判断是否为NULL,再使用指针指向的字符串
if (p != NULL) {
printf("%s\n", p); // prints "llo world"
}
5.2 strrchr
c
char *strrchr(const char *str, int c);
作用 :查找字符 c 最后一次出现的位置相当于反向查找一个
c
char str[20] = "hello world";
char *p = strrchr(str, 'l');
// 找不到会返回NULL,否则返回指向第一个匹配字符的指针
// 先判断是否为NULL,再使用指针指向的字符串
if (p != NULL) {
printf("%s\n", p); // prints "ld";
}
5.3 strstr
c
char *strstr(const char *haystack, const char *needle);
作用 :查找子串(比较常用)
c
char str[20] = "hello world";
// strstr 查找子串,返回子串的起始地址,找不到返回NULL
char* p = strstr(str, "ll");
if (p != NULL) {
printf("%s\n", p); // ll world
}
6、字符串分割
6.1 strtok
c
char *strtok(char *str, const char *delim);
- 作用 :按分隔符切割字符串
- 特点 :
- 会修改原字符串(多线程不安全)
- 第一次传字符串,后续传
NULL
- 示例
c
char str[20] = "He,llo;world!";
// strtok() 函数用于分割字符串,第一个参数是要处理的字符串,第二个参数是分隔符
// 分割符可以是多个字符,例如 ",;" 表示以逗号或分号分割字符串
char *p = strtok(str, ",;");
while (p != NULL) {
printf("%s\n", p);
// 继续分割字符串,直到没有更多的分隔符为止,传入NULL作为第一个参数
p = strtok(NULL, ",;");
}
!NOTE
strtok 函数内部使用了静态全局变量保存切割上下文,所有线程共享该变量,多线程并发调用时会发生数据竞争,现场被互相覆盖,导致结果不可预期。
使用线程安全的可重入版本 strtok_r(Linux)/strtok_s(Windows)。
7、内存操作(通用,不限于字符串)
内存操作函数不关心\0,都是按照字节操作内存。
7.1 memset
c
void *memset(void *ptr, int value, size_t num);
作用:批量赋值(常用于清空数组、或内存区域清0)
c
char str[10] = "Hello";
// 这里没有初始化,数组的值是不确定的
int arr[10];
memset(str, '\0', 10);
printf("%s\n", str);
// 初始化数组为0
memset(arr, 0, sizeof(int) * 10);
for (int i = 0; i < 10; i++) {
printf("%d\n", arr[i]); // 输出0
}
7.2 memcpy
c
void *memcpy(void *dest, const void *src, size_t n);
作用:内存拷贝(比 strncpy 更通用)
- 按字节 拷贝,不关心
\0,不限于字符串 - 拷贝
n字节 - 返回
dest
注意: - 不保证处理内存重叠
- 当
src和dest指向同一块数组,且区间有重叠:
- 当
- 行为未定义(可能乱、可能正常、可能崩溃)
- 通常实现是从低地址往高地址逐字节拷贝
c
char src[] = "Hello, world!";
char dest[20] = {0};
memcpy(dest, src, strlen(src));
printf("%s\n", dest);
memcpy(src, src, strlen(src)); // 错误的使用, 会导致未定义行为
printf("%s\n", src);
! NOTE
memcpy 可能覆盖还没读的数据 → 结果错乱
memmove 会从后往前拷贝,保证正确
7.3 memmove
c
void *memmove(void *dest, const void *src, size_t n);
作用 :可处理内存重叠的拷贝(更安全)
- 保证安全处理内存重叠
- 内部会自动判断地址高低,选择正向 / 反向拷贝
- 结果永远可预期
c
char src[] = "Hello, world!";
char dest[20] = {0};
memmove(dest, src, strlen(src));
printf("%s\n", dest);
// 这种情况建议使用memmove,因为src和dest可能重叠
memmove(src, src, strlen(src));
printf("%s\n", src);
7.4 memcmp
c
int memcmp(const void *s1, const void *s2, size_t n);
内存比较,比较的是每一个字节的二进制数据。
7.5 memchr
c
void *memchr(const void *str, int c, size_t n);
内存中查找字节
8、字符串转换
这些函数都在 <stdlib.h> 头文件里。
8.1 atoi \ atol \ atoll
把数字字符串 转成 -> 整数 \ long \ long long
使用注意:
atoi和atof两个函数规则完全一样:
- 跳过开头的空白符(空格、换行、制表符)
- 识别正负号
- 连续读取数字 ,直到遇到非数字字符停止
- 无法转换 → 返回 0
c
char str1[10] = "123";
char str2[10] = "a456";
int ret = atoi(str1);
printf("%d\n", ret);
// 转换失败,返回0
ret = atoi(str2);
printf("%d\n", ret);
8.2 atof
字符串到浮点数
c
char str1[10] = "0.123";
float ret = atof(str1);
printf("%f\n", ret); // 0.123000
9、字符大小写转换
需要包含#include <ctype.h>
c
printf("%c\n", tolower('A')); // a
printf("%c\n", toupper('a')); // A
10、扩展
10.1 strtok_s和strtok_r使用
- strtok_r:Linux、Unix、macOS 用(POSIX 标准)
- strtok_s:Windows 用(C11 标准)
- 功能完全一样 :自己传入指针保存切割现场,多线程安全、可重入、可嵌套
c
char *strtok_r( char *str, const char *delim, char **saveptr );
saveptr:自己定义的指针,用来保存切割位置- 每个切割任务独立一个 saveptr → 完全安全
c
char *strtok_s( char *str, const char *delim, char **context );
- 和 strtok_r 一模一样,只是第三个参数名字叫 context
- 功能、用法、原理 100% 相同
! 说明
strtok 内部用 静态全局变量 保存位置 → 所有线程共享
strtok_r / strtok_s 由你自己传入指针保存位置 → 每个线程独立一份,互不干扰
c
char str[20] = "He,llo;Wor,ld";
char *savepoint = str; // 保存下一次调用strtok_s的起始位置
// 第一次传入字符串
char *p = strtok_s(savepoint, ",;", &savepoint);
while (p != NULL) {
printf("%s\n", p);
// 第二次传入NULL,继续分割字符串
p = strtok_s(NULL, ",;", &savepoint);
}