在 C 语言中,字符串被表示为一个以空字符 '\0' 结尾的字符数组。string.h (或其旧称 strings.h) 头文件提供了大量用于操作这些字符串的函数。

(图片来源网络,侵删)
核心概念:'\0' (空字符)
这是理解所有字符串函数的基石,字符串的长度由函数计算,直到遇到第一个 '\0' 为止,字符串 "hello" 在内存中实际存储的是 {'h', 'e', 'l', 'l', 'o', '\0'}。
计算字符串长度
strlen
- 函数原型:
size_t strlen(const char *str); - 功能: 计算字符串的长度。
- 参数:
str- 指向字符串的指针。 - 返回值: 返回字符串的长度,不包括结尾的
'\0',如果传入的指针是NULL,会导致程序崩溃(未定义行为)。 - 特点: 它会一直向后遍历字符,直到找到
'\0'为止。
示例代码:
#include <stdio.h>
#include <string.h>
int main() {
char str1[] = "Hello, World!";
char str2[] = "C";
printf("The length of '%s' is %zu\n", str1, strlen(str1)); // 输出 13
printf("The length of '%s' is %zu\n", str2, strlen(str2)); // 输出 1
// 注意 sizeof 和 strlen 的区别
// sizeof 计算的是整个数组的大小(包括 '\0')
printf("The size of str1 is %zu\n", sizeof(str1)); // 输出 14
return 0;
}
字符串拷贝与拼接
strcpy
- 函数原型:
char *strcpy(char *dest, const char *src); - 功能: 将源字符串
src拷贝到目标字符数组dest中。 - 参数:
dest: 目标字符数组,必须有足够的空间来容纳src的内容(包括'\0')。src: 源字符串,用const修饰,表示不会修改它。
- 返回值: 返回指向
dest的指针。 - ⚠️ 重要警告:
strcpy不检查dest的大小,src比dest长,会导致缓冲区溢出,这是严重的安全漏洞。
示例代码:
#include <stdio.h>
#include <string.h>
int main() {
char src[] = "Hello";
// dest 必须足够大,至少是 strlen(src) + 1
char dest[10]; // 6个字符 + '\0' = 7,10足够
strcpy(dest, src);
printf("Copied string: %s\n", dest); // 输出 Hello
// 危险示例!
// char small_dest[5];
// strcpy(small_dest, src); // 这会导致缓冲区溢出,未定义行为
return 0;
}
strncpy
- 函数原型:
char *strncpy(char *dest, const char *src, size_t n); - 功能: 拷贝最多
n个字符从src到dest。 - 参数:
dest: 目标字符数组。src: 源字符串。n: 要拷贝的最大字符数。
- 返回值: 返回指向
dest的指针。 - 特点:
strlen(src) < n,dest的剩余部分会用'\0'填充。strlen(src) >= n,dest不会自动添加'\0',这可能导致dest不是一个合法的字符串。
- 用途: 比
strcpy安全,因为它限制了拷贝的字符数,但仍需谨慎使用。
示例代码:

(图片来源网络,侵删)
#include <stdio.h>
#include <string.h>
int main() {
char src[] = "Hello";
char dest1[10];
char dest2[5];
// 情况1: n > strlen(src)
strncpy(dest1, src, 8);
// dest1 会是 "Hello\0\0\0" (后面的'\0'由strncpy填充)
printf("strncpy with n > len: %s\n", dest1);
// 情况2: n <= strlen(src)
strncpy(dest2, src, 3);
// dest2 会是 "Hel" (注意!没有'\0')
// 为了安全,最好手动添加
dest2[3] = '\0';
printf("strncpy with n <= len: %s\n", dest2);
return 0;
}
strcat
- 函数原型:
char *strcat(char *dest, const char *src); - 功能: 将源字符串
src拼接到目标字符串dest的末尾。 - 参数:
dest: 目标字符串,必须有足够的空间来容纳拼接后的新字符串(包括'\0')。src: 要拼接的源字符串。
- 返回值: 返回指向
dest的指针。 - ⚠️ 重要警告: 和
strcpy一样,strcat不检查dest的剩余空间,容易导致缓冲区溢出。
示例代码:
#include <stdio.h>
#include <string.h>
int main() {
char dest[20] = "Hello, ";
char src[] = "World!";
strcat(dest, src);
printf("Concatenated string: %s\n", dest); // 输出 Hello, World!
return 0;
}
strncat
- 函数原型:
char *strncat(char *dest, const char *src, size_t n); - 功能: 将
src的前n个字符拼接到dest的末尾。 - 参数:
dest: 目标字符串。src: 源字符串。n: 要拼接的最大字符数。
- 返回值: 返回指向
dest的指针。 - 特点: 它会保证在拼接后的字符串末尾添加一个
'\0',这比strncpy更安全一些。 - 注意: 目标数组
dest的大小至少需要是strlen(dest) + n + 1。
示例代码:
#include <stdio.h>
#include <string.h>
int main() {
char dest[15] = "Hello, "; // 长度为 7
char src[] = "World!"; // 长度为 6
// 最多拼接 3 个字符
strncat(dest, src, 3);
printf("Concatenated string: %s\n", dest); // 输出 Hello, Wor
return 0;
}
字符串比较
strcmp
- 函数原型:
int strcmp(const char *str1, const char *str2); - 功能: 比较两个字符串的字典顺序。
- 参数:
str1,str2- 要比较的两个字符串。 - 返回值:
- 小于 0: 表示
str1小于str2。 - 等于 0: 表示
str1等于str2。 - 大于 0: 表示
str1大于str2。
- 小于 0: 表示
- 比较规则: 逐个字符比较其 ASCII 码值,直到遇到不同的字符或
'\0'。
示例代码:
#include <stdio.h>
#include <string.h>
int main() {
char str1[] = "apple";
char str2[] = "banana";
char str3[] = "apple";
printf("strcmp(str1, str2): %d\n", strcmp(str1, str2)); // 输出负数
printf("strcmp(str2, str1): %d\n", strcmp(str2, str1)); // 输出正数
printf("strcmp(str1, str3): %d\n", strcmp(str1, str3)); // 输出 0
return 0;
}
strncmp
- 函数原型:
int strncmp(const char *str1, const char *str2, size_t n); - 功能: 比较两个字符串的前
n个字符。 - 参数:
str1,str2- 要比较的字符串,n- 比较的最大字符数。 - 返回值: 与
strcmp相同。
示例代码:

(图片来源网络,侵删)
#include <stdio.h>
#include <string.h>
int main() {
char str1[] = "apple pie";
char str2[] = "apple juice";
// 只比较前 5 个字符
printf("strncmp(str1, str2, 5): %d\n", strncmp(str1, str2, 5)); // 输出 0
// 比较 6 个字符
printf("strncmp(str1, str2, 6): %d\n", strncmp(str1, str2, 6)); // 输出负数 ('p' < 'j' 是错误的,应该是 ' ' < 'j',所以是负数)
// ' ' (空格) 的 ASCII 是 32, 'j' 是 106,str1 < str2, 返回负数。
return 0;
}
字符串查找
strchr
- 函数原型:
char *strchr(const char *str, int c); - 功能: 在字符串
str中查找字符c(c会被转换为char)的第一次出现。 - 参数:
str: 要搜索的字符串。c: 要查找的字符。
- 返回值:
- 如果找到,返回指向该字符的指针。
- 如果未找到,返回
NULL。
- 注意: 查找包括
'\0'本身。
示例代码:
#include <stdio.h>
#include <string.h>
int main() {
char str[] = "Hello, World!";
char *ptr;
ptr = strchr(str, 'W');
if (ptr != NULL) {
printf("Found 'W' at position: %ld\n", ptr - str); // 输出 7
}
ptr = strchr(str, 'x');
if (ptr == NULL) {
printf("'x' not found in the string.\n");
}
return 0;
}
strstr
- 函数原型:
char *strstr(const char *haystack, const char *needle); - 功能: 在字符串
haystack(草堆)中查找子字符串needle(针)的第一次出现。 - 参数:
haystack: 要搜索的主字符串。needle: 要查找的子字符串。
- 返回值:
- 如果找到,返回指向子字符串在
haystack中起始位置的指针。 - 如果未找到,返回
NULL。
- 如果找到,返回指向子字符串在
示例代码:
#include <stdio.h>
#include <string.h>
int main() {
char haystack[] = "This is a simple string";
char *ptr;
ptr = strstr(haystack, "simple");
if (ptr != NULL) {
printf("Found 'simple' starting at: %ld\n", ptr - haystack); // 输出 10
printf("The rest of the string is: %s\n", ptr); // 输出 simple string
}
return 0;
}
其他常用函数
strtok
- 函数原型:
char *strtok(char *str, const char *delim); - 功能: 将字符串分割成一个个的“标记”(tokens)。
- 参数:
str: 要被分割的字符串。第一次调用时传入该字符串,之后调用时传入NULL。delim: 包含所有分隔符的字符串。
- 返回值: 返回指向下一个标记的指针,当没有更多标记时返回
NULL。 - ⚠️ 重要警告:
strtok会修改原始字符串,用'\0'替换分隔符,它是非线程安全的,并且会记住上次调用的状态(通过静态变量)。
示例代码:
#include <stdio.h>
#include <string.h>
int main() {
char str[] = "This,is,a,test,string";
char *token;
// 第一次调用,传入字符串
token = strtok(str, ",");
while (token != NULL) {
printf("%s\n", token);
// 后续调用,传入 NULL
token = strtok(NULL, ",");
}
// str 现在被修改了: "This\0is\0a\0test\0string"
// printf("Original str after strtok: %s\n", str);
return 0;
}
总结与现代替代方案
| 函数 | 功能 | 安全性 | 现代替代方案 (C99) |
|---|---|---|---|
strcpy |
字符串拷贝 | 不安全 (缓冲区溢出) | strncpy (需手动加 '\0') |
strcat |
字符串拼接 | 不安全 (缓冲区溢出) | strncat |
strlen |
计算长度 | 安全 | 无 |
strcmp |
字符串比较 | 安全 | 无 |
strchr |
查找字符 | 安全 | 无 |
strstr |
查找子串 | 安全 | 无 |
strtok |
分割字符串 | 不安全 (修改原串, 非线程安全) | strtok_r (POSIX, 线程安全) 或 C++ 的 std::stringstream |
对于现代 C 开发,强烈推荐使用更安全的函数,如 strncpy, strncat,并始终确保目标缓冲区足够大,在编写新代码时,考虑使用长度限制的函数或 C++ 的 std::string 等更高级的抽象可以大大提高程序的健壮性和安全性。
