1. 引言
在 C 语言中,并没有专门的「字符串类型」,字符串是通过字符数组来存储和处理的。理解字符数组与 string.h 中的常用字符串函数,是掌握 C 语言输入输出、文本处理的基础。
本文将从字符数组讲起,逐个介绍 gets、puts、strcpy、strcat、strcmp、strlen 的用法,最后延伸到二维字符数组的应用场景。
2. 字符与字符数组
2.1 单个字符
C 语言用 char 类型表示单个字符,字符常量用单引号包裹:
char ch = 'A';
字符在内存中本质上存储的是对应的 ASCII 码值,例如 'A' 对应整数 65。
2.2 字符数组
字符数组就是存放多个字符的数组:
char chars[5] = {'h', 'e', 'l', 'l', 'o'};
这个数组有 5 个元素,分别保存了 5 个字符,但它还不是标准意义上的字符串,因为缺少字符串结束标志。
3. 字符串的本质
在 C 语言中,字符串是由字符组成、并以 '\\\\0'(空字符,ASCII 码为 0)作为结束标志的字符序列。
char str[6] = {'h', 'e', 'l', 'l', 'o', '\\\\0'};
更常见的写法是直接用双引号初始化:
char str[] = "hello";
编译器会自动在末尾补上 '\\\\0',因此数组实际长度为 6(5 个字符 + 1 个结束符)。
字符串结束标志 '\\\\0' 至关重要:所有字符串处理函数都依赖它判断字符串在哪里结束。如果字符串数组没有足够的空间存放 '\\\\0',程序可能越界访问,导致不可预期的错误。
4. gets 与 puts:字符串的输入输出
4.1 puts 输出字符串
puts 用于输出一个字符串,并在输出后自动换行:
#include <stdio.h>
int main(void) {
char str[] = "Hello, C!";
puts(str);
return 0;
}
输出结果:
Hello, C!
4.2 gets 输入字符串
gets 用于从标准输入读取一行字符串,直到遇到换行符。它会把换行符替换为 '\\\\0' 存入数组:
#include <stdio.h>
int main(void) {
char name[20];
printf("请输入你的名字:");
gets(name);
printf("你好,");
puts(name);
return 0;
}
⚠️ 重要警告:gets 不检查数组边界。如果用户输入超过数组容量的内容,会发生缓冲区溢出,这是著名的安全隐患,因此 C11 标准已废除 gets。实际开发中请使用更安全的 fgets:
#include <stdio.h>
int main(void) {
char name[20];
printf("请输入你的名字:");
fgets(name, sizeof(name), stdin);
/* 去掉 fgets 保留的换行符 */
name[strcspn(name, "\\n")] = '\\\\0';
printf("你好,%s\\n", name);
return 0;
}
注:上例中的 strcspn 需要包含 <string.h> 头文件。为了循序渐进,学习阶段仍可以用 gets 理解字符串输入原理,但动手写项目时请务必使用 fgets。
5. 常用字符串处理函数
使用以下函数前,需要包含头文件:
#include <string.h>
5.1 strlen:统计字符串长度
strlen 返回字符串中字符的个数,不包括结尾的 '\\\\0'。
#include <stdio.h>
#include <string.h>
int main(void) {
char str[] = "hello";
printf("字符串长度:%zu\\n", strlen(str));
return 0;
}
输出结果:
字符串长度:5
5.2 strcpy:拷贝字符串
strcpy 用于把源字符串(包括 '\\\\0')完整拷贝到目标数组中。
函数原型:char *strcpy(char *dest, const char *src);
#include <stdio.h>
#include <string.h>
int main(void) {
char src[] = "hello";
char dest[20];
strcpy(dest, src);
printf("拷贝结果:%s\\n", dest);
return 0;
}
使用时注意:
- 目标数组必须有足够的空间容纳源字符串及其 '\\\\0'。
- dest 不能是字符串字面量(如 char *dest = "abc";),因为字面量通常存放在只读区域。
5.3 strcat:拼接字符串
strcat 把源字符串追加到目标字符串的末尾,目标字符串原本的 '\\\\0' 会被覆盖,最终形成一个新的以 '\\\\0' 结尾的字符串。
函数原型:char *strcat(char *dest, const char *src);
#include <stdio.h>
#include <string.h>
int main(void) {
char first[30] = "Hello";
char second[] = " World";
strcat(first, second);
printf("拼接结果:%s\\n", first);
return 0;
}
输出结果:
拼接结果:Hello World
同样要保证目标数组 first 有足够空间容纳拼接后的全部内容,否则会发生越界。
5.4 strcmp:比较字符串
strcmp 按字典序逐字符比较两个字符串,比较的是字符的 ASCII 码值。
函数原型:int strcmp(const char *s1, const char *s2);
返回值含义:
- 返回 0:两个字符串相等;
- 返回正数:s1 大于 s2;
- 返回负数:s1 小于 s2。
#include <stdio.h>
#include <string.h>
int main(void) {
char a[] = "apple";
char b[] = "banana";
int result = strcmp(a, b);
if (result == 0) {
printf("两个字符串相等\\n");
} else if (result < 0) {
printf("\\"%s\\" 小于 \\"%s\\"\\n", a, b);
} else {
printf("\\"%s\\" 大于 \\"%s\\"\\n", a, b);
}
return 0;
}
输出结果:
"apple" 小于 "banana"
注意:字符串比较不能直接写 a == b,因为那比较的是两个数组的地址,而不是内容。必须使用 strcmp。
6. 二维数组与字符串数组
6.1 二维数组基础
二维数组可以理解为「数组的数组」,常用于存储表格数据:
int matrix[3][4]; /* 3 行 4 列的整型矩阵 */
初始化方式:
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
遍历二维数组需要嵌套循环:
#include <stdio.h>
int main(void) {
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", matrix[i][j]);
}
printf("\\n");
}
return 0;
}
输出结果:
1 2 3
4 5 6
6.2 二维字符数组:存储多个字符串
二维字符数组最常见的用途是存放一组字符串。每一行是一个一维字符数组,代表一个字符串:
#include <stdio.h>
int main(void) {
char fruits[3][20] = {
"apple",
"banana",
"cherry"
};
for (int i = 0; i < 3; i++) {
puts(fruits[i]);
}
return 0;
}
输出结果:
apple
banana
cherry
这里 char fruits[3][20] 表示可以存放 3 个字符串,每个字符串最多 19 个字符(留 1 位给 '\\\\0')。
6.3 结合字符串函数操作二维字符数组
我们可以把前面学到的函数配合二维字符数组一起使用,例如找出最长的字符串:
#include <stdio.h>
#include <string.h>
int main(void) {
char words[4][20] = {
"cat",
"elephant",
"dog",
"bird"
};
int max_index = 0;
for (int i = 1; i < 4; i++) {
if (strlen(words[i]) > strlen(words[max_index])) {
max_index = i;
}
}
printf("最长的单词是:%s\\n", words[max_index]);
return 0;
}
输出结果:
最长的单词是:elephant
7. 综合示例:一个简易通讯录
下面用一个完整示例,把 gets、puts、strcpy、strcat、strcmp、strlen 和二维数组串起来:
#include <stdio.h>
#include <string.h>
#define MAX_NAMES 3
#define MAX_LEN 20
int main(void) {
char names[MAX_NAMES][MAX_LEN];
char greeting[50] = "你好,";
/* 输入 3 个名字 */
for (int i = 0; i < MAX_NAMES; i++) {
printf("请输入第 %d 个名字:", i + 1);
fgets(names[i], MAX_LEN, stdin);
names[i][strcspn(names[i], "\\n")] = '\\\\0';
}
/* 查找指定的名字 */
char target[MAX_LEN];
printf("请输入要查找的名字:");
fgets(target, MAX_LEN, stdin);
target[strcspn(target, "\\n")] = '\\\\0';
int found = 0;
for (int i = 0; i < MAX_NAMES; i++) {
if (strcmp(names[i], target) == 0) {
strcat(greeting, target);
puts(greeting);
printf("名字长度:%zu\\n", strlen(target));
found = 1;
break;
}
}
if (!found) {
printf("未找到该名字。\\n");
}
return 0;
}
运行示例:
请输入第 1 个名字:Alice
请输入第 2 个名字:Bob
请输入第 3 个名字:Cindy
请输入要查找的名字:Bob
你好,Bob
名字长度:3
8. 常见错误与注意事项
9. 总结
- 字符串在 C 语言中是以 '\\\\0' 结尾的字符数组。
- puts 输出字符串,gets 输入字符串(有安全隐患,推荐用 fgets)。
- strlen 统计长度(不含 '\\\\0'),strcpy 拷贝,strcat 拼接,strcmp 比较。
- 二维字符数组适合存储一组字符串,每行代表一个字符串。
- 使用所有字符串函数前,务必保证目标空间足够,避免越界访问。
掌握这些基础后,你就可以处理 C 语言中绝大多数文本场景了。建议动手把每个示例都跑一遍,并尝试修改数组容量、观察越界风险,加深理解。



