char[] 与 std::string:C 字符串到底是什么
一句话:C 风格字符串就是一个 char 数组,末尾多一个字节存 '\0'。就这一条约定,解释了 strlen 为什么存在、strcpy 为什么会溢出,以及 std::string 为什么被发明出来。
一张图讲完全部
char s[6] = "hi"; // 6 个格子,但只有 3 个有意义
下标: 0 1 2 3 4 5
+-----+-----+-----+-----+-----+-----+
值: | 'h' | 'i' | '\0'| ? | ? | ? |
+-----+-----+-----+-----+-----+-----+
^
终止符:所有 C 函数靠它知道文本到哪结束
后三个格子是未初始化的垃圾值;字符串在 '\0' 处结束。没有任何地方存着长度。
创建与打印
#include <iostream>
using namespace std;
int main() {
char s[20] = "hi";
s[0] = 'H'; // 像普通数组一样用下标改
cout << s << endl; // Hi
char empty[20]; // 未初始化:此刻还没有终止符!
empty[0] = 'o';
empty[1] = 'k';
empty[2] = '\0'; // 现在它才是一个合法字符串
cout << empty << endl;
return 0;
}
忘了终止符是 C 字符串的头号 bug:打印一个没有终止符的数组,程序会一路走出你的数据,直到碰巧撞上一个零字节。
strlen、strcpy —— 它们为什么存在
#include <iostream>
#include <cstring>
using namespace std;
int main() {
char src[20] = "copy me";
char dst[20];
strcpy(dst, src); // 拷贝,连 '\0' 一起
cout << dst << endl;
cout << "length = " << strlen(dst) << endl; // 7,不算终止符
cout << "sizeof dst = " << sizeof(dst) << endl; // 20:数组大小,不是文本长度
return 0;
}
strlen(7)和 sizeof(20)这一对是面试最爱的陷阱:一个量的是文本,一个量的是盒子。
strcpy 为什么危险
char small[5];
char big[] = "this is much longer than five";
strcpy(small, big); // 写到数组外面去了 —— 未定义行为
strcpy 一直拷到看见 '\0' 为止,它并不知道你的数组有多大。这就是缓冲区溢出 —— 大量安全漏洞的源头。带长度限制的版本是 strncpy(更好的做法:别用 C 字符串)。
std::string 改变了什么
#include <string>
std::string s = "hi";
s[0] = 'H'; // 照样能用下标
s += " there"; // 自动变长 —— 不用管容量
std::cout << s.size(); // 永远知道自己的长度
std::cout << s.substr(0, 2); // 用操作代替约定
std::string 自己管理内存、自动增长、自带长度。没有要忘的终止符,没有固定缓冲区溢出,也不用调 strlen 去找结尾。
并排对比
char[](C 字符串) | std::string | |
|---|---|---|
| 长度 | 扫描 '\0' 才知道 | 存着,.size() |
| 大小 | 声明时定死 | 可增可减 |
| 拷贝 | strcpy(可能溢出) | =(安全) |
| 拼接 | strcat(可能溢出) | += |
| 比较 | strcmp(不能用 ==) | == 直接可用 |
| 读一整行 | cin.getline(s, n) | getline(cin, s) |
最坑人的那一个
#include <iostream>
using namespace std;
int main() {
char a[10] = "abc";
char b[10] = "abc";
// 这里比的是「地址」,不是文本。结果永远是假!
cout << "a == b ? " << (a == b) << endl;
// 你要的是逐字符比较:
bool same = true;
for (int i = 0; a[i] != '\0' || b[i] != '\0'; i++) {
if (a[i] != b[i]) { same = false; break; }
}
cout << "same text? " << same << endl;
return 0;
}
换成 std::string,a == b 就做对了。用数组时,== 拿数组的地址和它自己比 —— 几乎永远是错的问题。
读带空格的输入
#include <iostream>
using namespace std;
int main() {
char line[50];
cin.getline(line, 50); // 整行,含空格
cout << "[" << line << "]" << endl;
return 0;
}
记住 cin >> s 遇到第一个空格就停;getline 拿走整行剩下的内容 —— 两者混用时小心残留的换行(见 cin 与 getline)。
那该用哪个?
- 学习或竞赛题:很多判题机和老教材用 C 字符串。要认识它 —— 也要知道它的边界。
- 任何真实 C++ 代码:用
std::string。更安全、会长、更好读。 - 调用 C 库:函数要
const char*时用std::string::c_str()。
去练
相关:数据类型(为什么 char 是个小整数)、cin 与 getline、vector 与 array。