C 字符数组与字符串
更新时间:2026-08-26。本文是
languages/c/主题入门层第 27 篇(由原《数组、字符串与结构体》拆出)。C 没有专门的"字符串类型"——字符串就是以空字符\0结尾的字符数组。这句话是整个字符串主题的地基。
本文要回答的问题
- 字符串为什么必须
\0结尾? char s[] = "hi"和char *s = "hi"有什么区别?- 怎么算字符串"长度"和"容量"?
一、字符串 = 字符数组 + \0
c
char s1[3] = {'h', 'i', '\0'}; // 显式:字符数组,结尾放空字符
char s2[] = "hi"; // 简写:字符串字面量初始化,自动加 \0s2 实际占用 3 字节:'h' 'i' '\0'。\0 是值 0 的字节,标记字符串结束。
text
s2: [ 'h' ][ 'i' ][ '\0' ]\0 不是可打印字符,也不计长度——它只是哨兵。strlen("hi") 是 2,但 sizeof("hi") 是 3(含结尾)。
二、容量 vs 长度:最容易绕晕的一对
| 概念 | 含义 | 例子 |
|---|---|---|
| 容量(capacity) | 数组总共能装多少字节 | char buf[16] 容量 16 |
| 长度(length) | 有效字符数(不含 \0) | "hello" 长度 5 |
c
char buf[16] = "hello";
// 容量 16,长度 5,还能再放 10 个字符(加上 \0 共 16)铁律:字符串要占用的空间 = 长度 + 1(\0)。strcpy(buf, "hello") 要写 6 字节,buf 至少 6 字节。容量 < 长度 + 1 就是缓冲区溢出的地雷。
三、数组 vs 指针:可修改性
c
char s1[] = "hello"; // 字符数组:可以修改内容
s1[0] = 'H'; // 合法!s1 变成 "Hello"
char *s2 = "hello"; // 指向字符串字面量
s2[0] = 'H'; // 危险!字面量在只读内存,多数平台段错误| 写法 | 存储位置 | 可修改 | sizeof |
|---|---|---|---|
char s[] = "hello" | 栈(可写) | 可以 | 6(含 \0) |
char *s = "hello" | 只读数据段 | 不行(UB) | 8(指针大小) |
这是新手高频 bug:用 char *s = "hello" 然后 s[0] = 'H',段错误。规则:字面量字符串当"只读"看,要修改就复制到数组。编译器在 C 里默认允许这种隐式转换(C++ 会直接警告),但改了就是 UB。
四、字符串数组:二维 char 数组
"存多个字符串"就是二维字符数组:
c
char names[3][16] = {"alice", "bob", "carol"};
// 3 个字符串,每个最多 15 字符(留 1 个给 \0)
for (int i = 0; i < 3; i++) {
printf("%s\n", names[i]);
}| 维度 | 含义 |
|---|---|
| 行数 3 | 最多 3 个字符串 |
| 列数 16 | 每个字符串容量 16(长度最多 15) |
注意浪费问题:字符串长短不一时,char[3][16] 固定每行 16 字节。用"指针数组"更省,见指针数组与字符串。
五、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
忘留 \0 空间 | 越界写 | 容量 = 长度 + 1 |
| 改字面量字符串 | 段错误 | 复制到数组再改 |
| 容量当长度用 | 遍历越界 | 区分两者 |
数组初始化没 \0 | 字符串没有终点 | 用 "..." 初始化自动加 |
char *s 当数组改 | UB | 明确可修改性 |
六、与本站主线衔接
- 字符串处理函数(strcpy/strcmp/strcat)的完整族谱,见字符串函数;
- 字符串在内存里的只读段布局,见专家层内存布局与 ABI;
- 字符串安全(strcpy 系列的危险),见缓冲区溢出与安全。
一句话总结
字符串 = 字符数组 + \0 结尾;容量(数组大小)和长度(有效字符)要分清,占用空间 = 长度 + 1;char s[] 可改、char *s = "..." 只读别改——地基打牢,字符串函数和安全都是在这上面长出来的。