C 语言数组进阶指南:二维数组、字符数组与字符串实战
上一篇我们详解了一维数组的基础用法,本篇将承接前文,深入数组进阶知识 ------ 涵盖二维数组的定义与应用、字符数组与字符串的本质区别,以及字符串常用函数的使用与手动实现,结合实战案例与避坑技巧,帮你彻底掌握数组的全场景用法。
一、二维数组:多维数据的存储与操作
二维数组本质是 "数组的数组",适用于存储表格化数据(如矩阵、学生成绩表),核心特点是 "行 + 列" 的二维索引结构。
1. 二维数组的定义与初始化
(1)定义格式
c
运行
ini
类型 标识符[行长度][列长度];
- 必须指定列长度,行长度可省略(由初始化内容自动推导);
- 示例:
int matrix[3][3];(3 行 3 列的整型矩阵)、int matrix[][3] = {1,2,3,4,5,6};(自动推导为 2 行 3 列)。
(2)初始化方式
二维数组支持多种初始化形式,核心规则与一维数组一致(未赋值元素默认补 0):
c
运行
less
// 1. 连续赋值(按行优先存储)
int num1[3][3] = {1,2,3,4,5,6,7,8,9};
// 2. 分段赋值(按行分组,更直观)
int num2[3][3] = {{1,2,3}, {4,5,6}, {7,8,9}};
// 3. 部分初始化(未赋值元素补0)
int num3[3][3] = {{1,2}, {4}, {7,8,9}}; // 等价于{{1,2,0}, {4,0,0}, {7,8,9}}
// 4. 全0初始化(简洁高效)
int num4[3][3] = {0};
// 5. 省略行长度(由初始化内容推导)
int num5[][3] = {1,2,3,4,5,6}; // 推导为2行3列
2. 二维数组的实战应用:输入与输出
二维数组需通过 "外层循环控制行、内层循环控制列" 的双层循环操作,示例如下:
c
运行
arduino
#include <stdio.h>
int main() {
int num[3][3]; // 3行3列数组
// 循环输入:行i从0~2,列j从0~2
for(int i = 0; i < 3; i++) {
for(int j = 0; j < 3; j++) {
printf("请输入第%d行第%d列数据:", i+1, j+1);
scanf("%d", &num[i][j]); // 取第i行第j列元素的地址
}
}
// 循环输出:按行打印
printf("\n输入的3×3矩阵为:\n");
for(int i = 0; i < 3; i++) {
for(int j = 0; j < 3; j++) {
printf("%d\t", num[i][j]); // 制表符分隔,格式整洁
}
printf("\n"); // 每行结束换行
}
return 0;
}
3. 二维数组的空间计算
与一维数组一致,总空间大小 = 单个元素类型大小 × 行长度 × 列长度:
c
运行
perl
int num[3][3];
// 总空间:4字节(int)×3×3=36字节
printf("总空间:%zu\n", sizeof(num));
// 一行的空间:4×3=12字节
printf("一行空间:%zu\n", sizeof(num[0]));
// 单个元素空间:4字节
printf("单个元素空间:%zu\n", sizeof(num[0][0]));
二、字符数组与字符串:本质与区别
字符数组是存储字符的一维数组,而字符串是 "以\0(字符串结束符)结尾的字符数组"------ 这是两者的核心区别。
1. 字符数组的定义与初始化
c
运行
arduino
// 1. 单个字符赋值
char ch1[5] = {'h', 'e', 'l', 'l', 'o'};
// 2. 字符串简化赋值(自动添加'\0')
char ch2[6] = "hello"; // 需预留'\0'的空间(5个字符+1个结束符)
// 3. 省略长度(自动推导为6,包含'\0')
char ch3[] = "hello";
- 关键:用
""赋值时,编译器会自动在末尾添加\0,数组长度需比字符数多 1,否则会导致字符串溢出。
2. 字符串的核心:\0结束符
\0是 ASCII 码为 0 的特殊字符,用于标记字符串结束,无实际显示效果:
- 示例:
char ch[6] = "hello"的内存存储为h e l l o \0; - 若缺少
\0:char ch[5] = "hello"(无\0),使用%s输出时会超出数组范围,打印垃圾值。
3. 字符串的两种操作方式
(1)数组下标操作(逐字符处理)
c
运行
perl
char ch[] = "hello";
// 循环遍历每个字符(直到'\0'结束)
for(int i = 0; ch[i] != '\0'; i++) {
printf("%c", ch[i]); // 输出:hello
}
(2)字符串格式操作(%s直接处理)
c
运行
perl
char ch[] = "hello";
// %s从数组首地址开始,直到'\0'结束
printf("%s\n", ch); // 输出:hello
三、字符串常用函数(string.h)
C 语言提供string.h头文件,包含字符串长度计算、复制、拼接、比较等常用函数,以下是核心函数的用法与手动实现(加深理解)。
1. 计算字符串长度:strlen
- 功能:返回字符串的实际长度(不包含
\0); - 用法:
size_t strlen(const char *str);; - 示例与手动实现:
c
运行
arduino
#include <stdio.h>
#include <string.h> // strlen需要包含该头文件
// 手动实现strlen
int my_strlen(char *str) {
int len = 0;
// 遍历到'\0'结束
while(str[len] != '\0') {
len++;
}
return len;
}
int main() {
char ch[] = "hello";
// 库函数实现:返回5
printf("库函数长度:%zu\n", strlen(ch));
// 手动实现:返回5
printf("手动实现长度:%d\n", my_strlen(ch));
return 0;
}
2. 字符串复制:strcpy
- 功能:将源字符串复制到目标字符串(覆盖目标字符串);
- 用法:
char *strcpy(char *dest, const char *src);; - 注意:目标字符串需有足够空间,否则会溢出;
- 手动实现:
c
运行
scss
// 手动实现strcpy:dest接收方,src提供方
void my_strcpy(char *dest, char *src) {
int i = 0;
// 复制字符直到src的'\0'
while(src[i] != '\0') {
dest[i] = src[i];
i++;
}
dest[i] = '\0'; // 手动添加结束符
}
int main() {
char src[] = "hello";
char dest[10] = {0};
my_strcpy(dest, src);
printf("复制结果:%s\n", dest); // 输出:hello
return 0;
}
3. 字符串拼接:strcat
- 功能:将源字符串拼接在目标字符串末尾;
- 用法:
char *strcat(char *dest, const char *src);; - 手动实现:
c
运行
scss
// 手动实现strcat
void my_strcat(char *dest, char *src) {
int i = 0, j = 0;
// 找到dest的'\0'位置
while(dest[i] != '\0') {
i++;
}
// 拼接src的字符
while(src[j] != '\0') {
dest[i] = src[j];
i++;
j++;
}
dest[i] = '\0'; // 补结束符
}
int main() {
char dest[20] = "hello";
char src[] = " world!";
my_strcat(dest, src);
printf("拼接结果:%s\n", dest); // 输出:hello world!
return 0;
}
4. 字符串比较:strcmp
- 功能:按 ASCII 码比较两个字符串,返回差值;
- 用法:
int strcmp(const char *str1, const char *str2);; - 返回值:
0(相等)、>0(str1>str2)、<0(str1<str2); - 手动实现:
c
运行
arduino
// 手动实现strcmp
int my_strcmp(char *str1, char *str2) {
int i = 0, res = 0;
while(str1[i] != '\0' || str2[i] != '\0') {
res = str1[i] - str2[i];
// 差值不为0,直接返回
if(res != 0) {
break;
}
i++;
}
return res;
}
int main() {
char str1[] = "hello";
char str2[] = "hellow";
printf("比较结果:%d\n", my_strcmp(str1, str2)); // 输出:-119('o'-'w'的ASCII差值)
return 0;
}
5. 字符串输入:scanf与fgets
scanf("%s", str):遇到空格或回车结束,无法输入含空格的字符串;fgets(str, size, stdin):支持输入含空格的字符串,size为最大读取长度(含\0),推荐使用:
c
运行
scss
char str[1024] = {0};
// 读取最多1023个字符(预留'\0')
fgets(str, 1024, stdin);
printf("输入内容:%s", str); // 可输出含空格的字符串
四、数组进阶避坑指南
- 二维数组行 / 列缺失错误 :定义时必须指定列长度,如
int num[][3]合法,int num[3][]非法; - 字符串结束符遗漏 :手动赋值字符数组时,需手动添加
\0,否则%s输出异常; - 字符串函数溢出风险 :
strcpy、strcat需确保目标字符串空间足够,建议提前计算长度; gets函数禁用 :gets无长度限制,易导致溢出,已被淘汰,改用fgets;- 字符数组与字符串混淆 :
char ch[5] = "hello"(无\0)不是合法字符串,需定义为char ch[6]。
五、数组应用场景总结
- 一维数组:批量存储同类型数据(如成绩、编号),配合循环实现排序、查找;
- 二维数组:存储表格化数据(如矩阵、二维坐标),适用于数学运算、数据统计;
- 字符数组 / 字符串:处理文本数据(如用户名、描述信息),依赖
string.h函数实现高效操作。
总结
数组是 C 语言处理批量数据的核心工具,从一维到二维、从普通数组到字符数组,其核心逻辑始终是 "连续内存 + 索引访问 + 循环操作"。掌握二维数组的行名列操作、字符串的\0结束符规则,以及常用字符串函数的原理,能解决大部分数据存储与处理场景。新手需重点规避 "下标越界""结束符遗漏""空间溢出" 三大坑,通过多练习排序、字符串拼接等实战案例,可快速夯实数组进阶基础。