什么是 char 二维数组?
char 二维数组就是一个“数组的数组”,其中每个元素都是一个 char(字符),我们可以用它来存储多个字符串。

(图片来源网络,侵删)
想象一个表格:
- 行:代表一个完整的字符串。
- 列:代表字符串中的每一个字符。
存储三个名字 "Tom", "Jerry", "Spike":
| 列 0 | 列 1 | 列 2 | 列 3 | |
|---|---|---|---|---|
| 行 0 | 'T' | 'o' | 'm' | '\0' |
| 行 1 | 'J' | 'e' | 'r' | 'r' |
| 行 2 | 'S' | 'p' | 'i' | 'k' |
如何声明和初始化 char 二维数组?
声明
声明一个 char 二维数组需要指定行数和列数。
// 语法: char 数组名[行数][列数]; // 声明一个可以存储 5 个字符串,每个字符串最长 10 个字符的二维数组 char names[5][10];
重要提示:列数必须足够容纳最长的字符串加上一个额外的字符,用于存储字符串结束符 \0,忘记 \0 是一个非常常见的初学者错误。

(图片来源网络,侵删)
初始化
你可以在声明时直接进行初始化。
// 方法1: 逐行初始化,编译器会自动添加 '\0'
char names[][10] = {
"Tom",
"Jerry",
"Spike",
"Tyke"
};
// 方法2: 显式指定每个字符(不常用,但有助于理解)
char greeting[][6] = {
{'H', 'e', 'l', 'l', 'o', '\0'},
{'W', 'o', 'r', 'l', 'd', '\0'}
};
注意:在使用初始化列表时,如果你不指定行数,编译器会自动根据你提供的字符串数量来确定行数,但列数必须显式指定。
如何访问和修改元素?
访问单个字符
使用双重下标 [行][列] 来访问。
char names[][10] = {"Tom", "Jerry", "Spike"};
// 访问 "Jerry" 的第一个字符 'J'
char first_char = names[1][0]; // first_char 的值是 'J'
// 访问 "Spike" 的最后一个字符 'e'
char last_char = names[2][4]; // last_char 的值是 'e'
访问整个字符串
在 C 语言中,一个 char 数组(或二维数组的一行)在表达式中会“衰变”为其指向首元素的指针,你可以直接使用 printf 的 %s 格式符来打印一行。
char names[][10] = {"Tom", "Jerry", "Spike"};
// 打印整个字符串 "Jerry"
printf("%s\n", names[1]); // 输出: Jerry
修改字符串
你可以像修改普通数组一样修改其中的字符。
char name[10] = "Alice"; // 注意:这里是一维数组,但原理相同 // 修改第一个字符 name[0] = 'B'; // name 现在是 "Bob" // 尝试修改一个字符串常量(错误示例) char *ptr = "Hello"; // ptr 指向一个字符串常量 ptr[0] = 'J'; // 错误!这会导致未定义行为,可能程序崩溃
重要区别:
char name[10] = "Alice";:name是一个字符数组,存储在可写的内存区域,可以修改。char *ptr = "Hello";:ptr是一个指针,指向一个只读的字符串常量,不能通过ptr来修改字符串内容。
完整示例代码
下面是一个完整的例子,演示了声明、初始化、访问、修改和遍历。
#include <stdio.h>
#include <string.h> // 用于 strcpy 函数
int main() {
// 1. 声明并初始化
// 存储3个名字,每个名字最长9个字符 + 1个'\0'
char names[][10] = {
"Alice",
"Bob",
"Charlie"
};
// 2. 访问单个字符并打印
printf("The first character of the second name is: %c\n", names[1][0]); // 输出 'B'
// 3. 访问整个字符串并打印
printf("The third name is: %s\n", names[2]); // 输出 "Charlie"
// 4. 修改一个字符串
// 不能直接用 names[1] = "David"; 来赋值,因为 names[1] 是一个数组名,是常量指针
// 必须使用 strcpy 函数
strcpy(names[1], "David");
printf("The second name after modification is: %s\n", names[1]); // 输出 "David"
// 5. 遍历所有字符串
printf("\n--- List of all names ---\n");
for (int i = 0; i < 3; i++) {
printf("Name %d: %s\n", i + 1, names[i]);
}
return 0;
}
常见陷阱与注意事项
-
忘记字符串结束符
\0: 如果定义的列数不够,编译器可能无法自动添加\0,导致字符串操作函数(如printf("%s", ...))越界读取,引发不可预测的行为。char bad_name[3] = "Tom"; // 只有 'T', 'o', 'm',没有 '\0' printf("%s\n", bad_name); // 危险!会打印 "Tom" 并继续读取后续内存中的垃圾值 -
*混淆
char names[5][10]和 `char names[5]`**:char names[5][10]:这是一个二维数组,它存储的是5个固定长度(10字节)的字符串,内存是连续分配的,所有字符串都在栈上。char *names[5]:这是一个指针数组,它存储的是5个指向char的指针,这些指针可以指向任意长度的字符串,这些字符串可以存储在内存的任何地方(比如堆上或只读数据区),这提供了更大的灵活性,但管理起来也更复杂。
指针数组示例:
char *names_ptr[3]; // 指针数组 names_ptr[0] = "Tom"; // 指向一个字符串常量 names_ptr[1] = "Jerry"; // 指向另一个字符串常量 names_ptr[2] = "Spike"; // 可以这样修改,因为 names_ptr[1] 指向的是堆上的可写内存 // char temp[10]; // strcpy(temp, "Bull"); // names_ptr[1] = temp; // 让指针指向新的字符串 printf("%s\n", names_ptr[1]); // 输出 "Jerry"
| 特性 | char 二维数组 (如 char names[5][10]) |
|---|---|
| 本质 | 固定大小的、连续的内存块,存储多个固定长度的字符串。 |
| 声明 | char arr[行数][列数]; |
| 优点 | 简单直观,内存连续,易于管理。 |
| 缺点 | 大小固定,不够灵活,每个字符串都占用最大分配的内存,可能浪费空间。 |
| 适用场景 | 当你知道字符串的最大数量和最大长度时,例如处理一组固定配置的名称、标签等。 |
对于需要动态长度字符串的场景,更现代和推荐的方法是使用 <string.h> 中的动态内存分配函数(malloc, calloc),或者使用 C++ 的 std::vector<std::string>,但在纯 C 语言环境中,char 二维数组是一个非常基础且重要的工具。
