字符数组是 C 语言处理字符串的方式,本质是 char 类型的一维数组,但有一个特殊规定——末尾必须有结束标记。
6.3
字符数组是 C 语言处理字符串的方式,本质是 char 类型的一维数组,但有一个特殊规定:末尾必须有 '\0'(空字符,ASCII = 0)作为终止符,告诉程序"字符串到这里结束了"。
\0 就是用来标记"有效内容到这里为止"的哨兵,几乎所有处理字符串的函数(包括 cout)都是靠遇到 \0 才知道该在哪里停下来。| 1 | char name[8] = "abcdefg"; // 7 字符 + \0,长度必须 ≥ 8 |
| 2 | char str[] = "Hello"; // 自动推断长度为 6(5 + \0) |
| 3 | |
| 4 | // 读取单词(遇空格停止) |
| 5 | char word[20]; |
| 6 | cin >> word; // 遇空格停止 |
| 7 | |
| 8 | // 读取含空格的整行 |
| 9 | char sentence[100]; |
| 10 | cin.getline(sentence, 100); // 读取整行,包括空格 |
| 11 | |
| 12 | // 混用 cin 和 getline 时需先清空缓冲区 |
| 13 | int a; cin >> a; |
| 14 | cin.ignore(); // 忽略缓冲区里残留的换行符 |
| 15 | cin.getline(sentence, 100); |
cin 读取整数后,缓冲区里会残留一个换行符,更稳妥的做法是用 cin.ignore(); 忽略掉它(默认忽略 1 个字符,正好是这个残留的换行符),否则 getline 会直接读到这个换行符,得到一个空字符串。有些资料会用 cin.sync() 来做这件事,但 sync() 本意是"同步输入流",具体行为在不同编译器下并不保证一致,不如 cin.ignore() 可靠,不建议依赖它清空缓冲区。字符串里有些符号没办法直接打出来(比如换行、Tab键),还有些符号本身就是字符串语法的一部分(比如引号),直接写会和字符串的边界搞混。C++ 用反斜杠 \ 开头的组合来表示这些特殊字符,称为转义字符——"转义"的意思是让紧跟在 \ 后面的字符不再表示它本来的含义,而是代表一个特殊符号。
| 转义字符 | 含义 | ASCII | 使用示例 |
|---|---|---|---|
| 排版控制 | |||
| \n | 换行 | 10 | cout << "Hello\nWorld"; |
| \t | 水平制表符(Tab) | 9 | cout << "A\tB\tC"; |
| 符号本身 | |||
| \\ | 一个反斜杠 \ | 92 | cout << "C:\\Users"; |
| \" | 一个双引号 " | 34 | cout << "\"Hello\""; |
| \' | 一个单引号 ' | 39 | char c = '\''; |
| 字符串结束 | |||
| \0 | 字符串终止符(空字符) | 0 | char s[] = {'H','i','\0'}; |
" 本身是字符串的边界符号,如果字符串内容里直接出现一个 ",编译器会误以为字符串在这里就结束了。反斜杠 \ 同理——它是转义序列的开头,直接写一个 \ 会被当成某个转义字符的开始,必须写两个 \\ 才能表示"就是一个普通反斜杠"。#include <cstring>)字符数组本质上只是普通数组,并不会自带"求长度""拼接""比较"这些能力——毕竟编译器只把它当成一串字符,不知道你把它当"字符串"在用。<cstring> 头文件提供了一整套配套函数,专门用来完成这些字符串操作,使用前记得先 #include <cstring>。
| 函数 | 功能 | 示例 |
|---|---|---|
| 查询类 | ||
| strlen(s) | 返回字符串有效长度(不含 \0) | strlen("Hello") → 5 |
| strcmp(s1, s2) | 比较字符串大小(按 ASCII),相等返回 0 | strcmp("abc","abc") → 0 |
| 修改类 | ||
| strcpy(dest, src) | 将 src 复制到 dest | strcpy(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 |
strcpy、strcat 在写入时完全不会检查目标数组是否装得下,多出来的内容会直接溢出到相邻内存(详见 6.3.4 节的陷阱说明)。使用前先确认目标数组留够了空间。strlwr(s)、strupr(s) 这两个"转大小写"的函数,但它们并不是 C/C++ 标准库的一部分,只是 Borland C++、旧版 Visual C++ 等个别编译器提供的非标准扩展——在 GCC、Clang 等主流编译器(包括大多数 OJ 和竞赛评测系统)下根本无法使用,写了会直接编译报错,属于不可移植的写法,不建议使用。正确的做法是用 <cctype> 提供的 toupper、tolower,对字符串里的每一个字符逐个转换(已在 2.3 节 cctype 函数库部分详细介绍过):| 1 | char s[] = "Hello"; |
| 2 | for (int i = 0; s[i] != '\0'; i++) |
| 3 | s[i] = tolower(s[i]); // 逐字符转小写 |
| 4 | // 结果:s = "hello" |
把本节内容汇总成几条最容易踩坑的规则:
\0。char s[5] = "hello" 会因为 5 个字符正好占满数组、没有位置放 \0 而出错,正确写法是 char s[6] 或直接省略大小让编译器自动推断。为了避免这类错误,一个安全的原则是:定义字符数组时把大小定为"最大预期字符数 + 1",或者干脆直接写 char s[] = "hello";,让编译器自动算出刚好够用(且留了 \0 位置)的大小。strcpy 不会检查目标数组够不够大,如果 dest 的空间比 src 小,多出来的内容会溢出到相邻内存,是非常危险的隐藏 bug。使用前一定确认目标数组的大小足够容纳源字符串(含 \0)。s1 == s2 比较的是两个数组的地址,而不是字符串的内容,即使两个数组内容完全一样,结果通常也是 false。判断内容是否相同必须用 strcmp(s1, s2) == 0。s[0]='H'; s[1]='i'; 这样一个个字符手动填充,最后必须手动加上 s[2]='\0';,否则数组内容"看起来对",但用 strlen、cout 等函数处理时会因为找不到结束标记而读到后面无关的垃圾数据。