C 语言12:字符串函数全解析
一、引言
在 C 语言中,字符串就是以 '\0'
(空字符)结尾的 char 数组。
C 标准库 <string.h> 提供了大量字符串处理函数,掌握它们能极大提升代码效率。
但如果不了解原理,很容易写出有 bug 或不安全的代码(比如缓冲区溢出)。
二、常用字符串函数分类
我将它们分为几类:
- 求长度
strlen
- 比较
strcmp
、strncmp
- 拷贝
strcpy
、strncpy
- 拼接
strcat
、strncat
- 查找
strchr
、strrchr
、strstr
、strpbrk
、strspn
、strcspn
- 分割
strtok
- 内存操作(二进制安全)
memcpy
、memmove
、memset
三、函数详解
1. strlen
—— 获取字符串长度
size_t strlen(const char *s);
功能:返回字符串 s
的长度,不包括结尾的 '\0'
。
源码的实现:
size_t my_strlen(const char *s)
{size_t len = 0;while (s[len] != '\0') {len++;}return len;
}
解析
- 遍历字符串直到遇到
'\0'
。 - 时间复杂度 O (n)。
实例:
#include <stdio.h>
#include <string.h>int main()
{char str[] = "Hello";printf("%zu\n", strlen(str)); // 输出 5
}
使用场景
- 分配内存时计算所需空间。
- 循环遍历字符串。
常见问题
- 忘记
'\0'
不算在长度里。 - 参数必须是合法的以
'\0'
结尾的字符串,否则会越界访问。
2. strcmp
—— 比较字符串
int strcmp(const char *s1, const char *s2);
功能:按字典序比较两个字符串。
返回值
- 等于 0:s1 == s2
- 小于 0:s1 < s2
- 大于 0:s1 > s2
源码实现:
int my_strcmp(const char *s1, const char *s2)
{while (*s1 && (*s1 == *s2)) {s1++;s2++;}return *(unsigned char *)s1 - *(unsigned char *)s2;
}
解析
- 逐个字符比较,直到不同或遇到
'\0'
。 - 返回的是字符 ASCII 码的差值。
实例:
char* str1 ="hello world";
char* str2 ="hello world";if (strcmp(str1, str2) == 0)
{printf("相等\n");
}
使用场景
- 排序字符串数组。
- 判断用户输入是否匹配某个关键字。
常见问题
- 用
==
比较字符串是错误的,应使用strcmp
3. strcpy
—— 拷贝字符串
char *strcpy(char *dest, const char *src);
功能:将 src
拷贝到 dest
,包括结尾的 '\0'
。
源码实现
char *my_strcpy(char *dest, const char *src)
{char *ret = dest;while ((*dest++ = *src++));return ret;
}
解析
- 循环赋值,直到
src
的'\0'
被复制。 - 必须保证
dest
足够大,否则会缓冲区溢出。
示例:
char dest[20];
strcpy(dest, "Hello");
使用场景
- 字符串赋值。
常见问题
- 缓冲区溢出(安全版本:
strncpy
)。
4. strncpy
—— 拷贝指定长度
原型:
char *strncpy(char *dest, const char *src, size_t n);
功能:拷贝最多 n
个字符,不一定会自动加 '\0'
。
源码实现:
char *my_strncpy(char *dest, const char *src, size_t n)
{char *ret = dest;while (n && (*dest++ = *src++)){n--;}while (n--) {*dest++ = '\0';}return ret;
}
解析
- 如果
src
长度 >= n,则不会自动加'\0'
。 - 如果
src
长度 < n,则剩余部分填充'\0'
。
常见问题
- 忘记手动加
'\0'
会导致字符串没有结束符。
5. strcat
—— 拼接字符串
char *strcat(char *dest, const char *src);
功能:将 src
追加到 dest
的末尾。
源码实现
char *my_strcat(char *dest, const char *src)
{char *ret = dest;while (*dest) dest++;while ((*dest++ = *src++));return ret;
}
解析
- 先找到
dest
的末尾'\0'
,再追加src
。 - 必须保证
dest
足够大。
常见问题
- 缓冲区溢出。
6. strncat
—— 拼接指定长度
原型:
char *strncat(char *dest, const char *src, size_t n);
功能追加最多 n
个字符,并自动加 '\0'
。
示例:
char dest[20] = "Hello";
strncat(dest, "World", 3); // dest = "HelloWor"
7. strchr
/ strrchr
—— 查找字符
char *strchr(const char *s, int c);
char *strrchr(const char *s, int c);
功能
strchr
:从左到右查找第一个匹配字符。strrchr
:从右到左查找。
示例
char *p = strchr("Hello", 'l');
printf("%s\n", p); // 输出 "llo"
8. strstr
—— 查找子串
char *strstr(const char *haystack, const char *needle);
功能:查找子串 needle
在 haystack
中首次出现的位置。
源码实现(KMP 简化版)
char *my_strstr(const char *haystack, const char *needle)
{if (*needle == '\0') return (char *)haystack;for (; *haystack; haystack++) {const char *h = haystack;const char *n = needle;while (*h && *n && *h == *n) {h++;n++;}if (*n == '\0') return (char *)haystack;}return NULL;
}
使用场景
- 判断一个字符串是否包含另一个子串。
9. strcspn
/ strspn
—— 计算连续字符跨度
size_t strcspn(const char *s, const char *reject);
size_t strspn(const char *s, const char *accept);
strspn
:从s
的开头开始,统计连续出现的、且都属于accept
集合的字符个数。
s: H e l l o W o r l d
accept: H e l
过程: H✓ → e✓ → l✓ → l✓ → o✗ → 停止,返回 4
strcspn
:从s
的开头开始,统计连续出现的、且都不属于reject
集合的字符个数。
s: H e l l o W o r l d
reject: (空格)
过程: H✓ → e✓ → l✓ → l✓ → o✓ → (空格)✗ → 停止,返回 5
示例:
size_t len = strcspn("Hello World", " ");
printf("%zu\n", len); // 输出 5
使用场景:
strspn
- 前缀合法性检查:比如判断一个字符串是否以某些允许的字符开头。
- 协议解析:从数据流中读取固定字符集的字段。
- 提取固定字符集组成的子串。
strcspn
- 查找分隔符位置:找到字符串中第一个出现的分隔符位置。
- 跳过不需要的字符:例如读取一行时,跳过行首的非法字符。
- 判断字符串中是否包含非法字符(通过返回值判断)。
10. strtok
—— 分割字符串
char *strtok(char *str, const char *delim);
功能:按分隔符 delim
分割字符串(会修改原字符串)。
实例:
char str[] = "a,b;c";char *p = strtok(str, ",;");while (p)
{printf("%s\n", p); //输出:a b cp = strtok(NULL, ",;");
}
常见问题
- 线程不安全(内部有静态变量)。
- 会破坏原字符串。
四、安全使用建议
- 避免缓冲区溢出:尽量使用
strncpy
、strncat
、snprintf
。 - 检查返回值:如
strstr
、strchr
找不到会返回NULL
。 - 初始化缓冲区:避免未定义行为。
- 处理重叠内存:用
memmove
而不是memcpy
。