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)。

那该用哪个?

去练

相关:数据类型(为什么 char 是个小整数)、cin 与 getline、vector 与 array。