← 目录 / 第六章 · 数组 / 6.3 字符数组

6.3 字符数组(C 风格字符串)

字符数组是 C 语言处理字符串的方式,本质是 char 类型的一维数组,但有一个特殊规定——末尾必须有结束标记。

字符数组(C 风格字符串)

字符数组是 C 语言处理字符串的方式,本质是 char 类型的一维数组,但有一个特殊规定:末尾必须有 '\0'(空字符,ASCII = 0)作为终止符,告诉程序"字符串到这里结束了"。

6.3.1 结束符 \0 与输入方式

char name[8] = "abcdefg"; // 7个字符 + 1个 \0
字符
a
b
c
d
e
f
g
\0
索引
0
1
2
3
4
5
6
7
🏁
为什么需要 \0?普通数组只要知道首地址和元素个数,就能确定数据范围。但字符数组常常"预留的空间"比"实际用到的字符数"更大(比如上面的例子里定义了 8 个字符的空间,刚好用满)。\0 就是用来标记"有效内容到这里为止"的哨兵,几乎所有处理字符串的函数(包括 cout)都是靠遇到 \0 才知道该在哪里停下来。
C++ · 字符数组 & 输入
1char name[8] = "abcdefg"; // 7 字符 + \0,长度必须 ≥ 8
2char str[] = "Hello"; // 自动推断长度为 6(5 + \0)
3
4// 读取单词(遇空格停止)
5char word[20];
6cin >> word; // 遇空格停止
7
8// 读取含空格的整行
9char sentence[100];
10cin.getline(sentence, 100); // 读取整行,包括空格
11
12// 混用 cin 和 getline 时需先清空缓冲区
13int a; cin >> a;
14cin.ignore(); // 忽略缓冲区里残留的换行符
15cin.getline(sentence, 100);
📌
getline 注意事项:长度参数不能超过数组长度;只能读取字符类型;默认以换行符作为结束符。使用 cin 读取整数后,缓冲区里会残留一个换行符,更稳妥的做法是用 cin.ignore(); 忽略掉它(默认忽略 1 个字符,正好是这个残留的换行符),否则 getline 会直接读到这个换行符,得到一个空字符串。有些资料会用 cin.sync() 来做这件事,但 sync() 本意是"同步输入流",具体行为在不同编译器下并不保证一致,不如 cin.ignore() 可靠,不建议依赖它清空缓冲区。

6.3.2 常用转义字符

字符串里有些符号没办法直接打出来(比如换行、Tab键),还有些符号本身就是字符串语法的一部分(比如引号),直接写会和字符串的边界搞混。C++ 用反斜杠 \ 开头的组合来表示这些特殊字符,称为转义字符——"转义"的意思是让紧跟在 \ 后面的字符不再表示它本来的含义,而是代表一个特殊符号。

转义字符含义ASCII使用示例
排版控制
\n换行10cout << "Hello\nWorld";
\t水平制表符(Tab)9cout << "A\tB\tC";
符号本身
\\一个反斜杠 \92cout << "C:\\Users";
\"一个双引号 "34cout << "\"Hello\"";
\'一个单引号 '39char c = '\'';
字符串结束
\0字符串终止符(空字符)0char s[] = {'H','i','\0'};
📖
为什么 \" 和 \\ 需要转义?双引号 " 本身是字符串的边界符号,如果字符串内容里直接出现一个 ",编译器会误以为字符串在这里就结束了。反斜杠 \ 同理——它是转义序列的开头,直接写一个 \ 会被当成某个转义字符的开始,必须写两个 \\ 才能表示"就是一个普通反斜杠"。

6.3.3 常用字符串函数(#include <cstring>

字符数组本质上只是普通数组,并不会自带"求长度""拼接""比较"这些能力——毕竟编译器只把它当成一串字符,不知道你把它当"字符串"在用。<cstring> 头文件提供了一整套配套函数,专门用来完成这些字符串操作,使用前记得先 #include <cstring>

函数功能示例
查询类
strlen(s)返回字符串有效长度(不含 \0)strlen("Hello") → 5
strcmp(s1, s2)比较字符串大小(按 ASCII),相等返回 0strcmp("abc","abc") → 0
修改类
strcpy(dest, src)将 src 复制到 deststrcpy(b, a)
strcat(s1, s2)将 s2 拼接到 s1 末尾strcat(s, " World")
转换类
atoi(s)字符串转整数(非数字返回 0)atoi("123") → 123
atof(s)字符串转 double(非数字返回 0.0)atof("3.14") → 3.14
⚠️
这些函数都不检查目标空间够不够:strcpystrcat 在写入时完全不会检查目标数组是否装得下,多出来的内容会直接溢出到相邻内存(详见 6.3.4 节的陷阱说明)。使用前先确认目标数组留够了空间。
🔤
字符串的大小写转换,不能靠 strlwr/strupr:有些资料里会提到 strlwr(s)strupr(s) 这两个"转大小写"的函数,但它们并不是 C/C++ 标准库的一部分,只是 Borland C++、旧版 Visual C++ 等个别编译器提供的非标准扩展——在 GCC、Clang 等主流编译器(包括大多数 OJ 和竞赛评测系统)下根本无法使用,写了会直接编译报错,属于不可移植的写法,不建议使用。正确的做法是用 <cctype> 提供的 touppertolower,对字符串里的每一个字符逐个转换(已在 2.3 节 cctype 函数库部分详细介绍过):
C++ · 逐字符转换大小写(标准、可移植)
1char s[] = "Hello";
2for (int i = 0; s[i] != '\0'; i++)
3 s[i] = tolower(s[i]); // 逐字符转小写
4// 结果:s = "hello"

6.3.4 常见陷阱与易错点

把本节内容汇总成几条最容易踩坑的规则:

1
忘记为 \0 预留空间:定义字符数组时,大小必须至少是"字符个数 + 1",多出来的这一个位置留给 \0char s[5] = "hello" 会因为 5 个字符正好占满数组、没有位置放 \0 而出错,正确写法是 char s[6] 或直接省略大小让编译器自动推断。为了避免这类错误,一个安全的原则是:定义字符数组时把大小定为"最大预期字符数 + 1",或者干脆直接写 char s[] = "hello";,让编译器自动算出刚好够用(且留了 \0 位置)的大小。
2
strcpy 目标数组太小导致溢出:strcpy 不会检查目标数组够不够大,如果 dest 的空间比 src 小,多出来的内容会溢出到相邻内存,是非常危险的隐藏 bug。使用前一定确认目标数组的大小足够容纳源字符串(含 \0)。
3
用 == 比较字符数组内容:字符数组本质是数组,s1 == s2 比较的是两个数组的地址,而不是字符串的内容,即使两个数组内容完全一样,结果通常也是 false。判断内容是否相同必须用 strcmp(s1, s2) == 0
4
手动逐字符构造字符串却忘了补 \0:如果不是用字符串字面量赋值,而是像 s[0]='H'; s[1]='i'; 这样一个个字符手动填充,最后必须手动加上 s[2]='\0';,否则数组内容"看起来对",但用 strlencout 等函数处理时会因为找不到结束标记而读到后面无关的垃圾数据。