C语言入门教程(第5讲):数组详解——一次性搞懂一维数组、二维数组与内存布局
🌱 文章摘要
程序学会了判断与循环后,下一步就要学会“记忆”。
数组是C语言中最基础也最强大的数据结构,
让你的程序能批量存储、集中处理数据。
这一讲,我们将系统掌握数组的定义、初始化、内存布局与二维数组结构,
彻底搞懂数组的本质,为后续的函数与指针打下坚实基础。
💬 导读
如果说变量是一个“盒子”,那数组就是一整排整齐的“盒子”。
它能让你的程序从“操作一个值”变成“处理一组值”,
实现更高效、更优雅的数据管理。
我们将从一维数组出发,逐步走进二维数组与变长数组,
在最后的实战环节,还会带你实现炫酷的“字符汇聚动画”与基础算法“二分查找”。
准备好了吗?让我们正式进入 C 语言的数据世界!
📘 建议收藏本专栏:后续章节会持续更新,
包括控制语句、函数、指针、数组、文件操作等完整体系。如果你能坚持学完,你会发现——
C语言不仅是工具,更是理解计算机世界的第一扇门。
目录
一、从变量到数组:让程序“记得更多”
1.1 数组是什么?
1.2 列举样例
二、数组的声明语法
2.1 基本语法格式
2.2 错误示例
三、一维数组的创建与初始化
3.1 方式1:完整初始化
3.2 方式2:部分初始化
3.3 方式3:让编译器自动推导长度
3.4 方式4:全部元素为 0
3.5 错误示例
四、数组元素的访问
4.1 示例1
五、一维数组的使用
5.1 示例1:输入并输出5个整数
5.2 示例2:计算数组元素的平均值
六、一维数组的内存存储机制
6.1 原理概述
6.2 示例1:查看数组元素地址
6.3 示例2:数组名与指针的关系
6.4 注意
七、sizeof 与元素个数计算
7.1 示例1:查看数组大小
7.2 示例2:求数组元素个数
7.3 应用:自动遍历数组
八、二维数组的定义与初始化
8.1 基本定义格式
8.2 初始化方式
方式1:完整初始化
方式2:行内展开初始化
方式3:部分初始化
方式4:让编译器推导列数
九、二维数组的访问与内存布局
9.1 示例1:打印二维数组
9.2 内存布局示意
9.3 示例:输出每个元素的地址
十、C99 的变长数组(VLA)
10.1 示例
十一、实战练习
11.1 实战1:字符汇聚动画(welcome to bit)
11.2 实战2:二分查找(Binary Search)
十二、总结
十三、下一讲预告
13.1 下一讲预告
13.2 提前准备建议
一、从变量到数组:让程序“记得更多”
还记得前几讲中我们写过这样一段代码吗?
int a, b, c;
scanf("%d %d %d", &a, &b, &c);
printf("平均值为:%d\n", (a + b + c) / 3);
这当然没问题,但如果现在要输入 100个整数 呢?
是不是要写:
int a1, a2, a3, ..., a100;
太累了!
所以,C语言为我们提供了一个更好的工具——数组(Array)。
1.1 数组是什么?
数组是一组类型相同、连续存储的变量集合。
你可以把它理解为“一排连在一起的储物格子”,
每个格子都能放一个数据。
1.2 列举样例
int score[5];
含义:
声明一个数组
score
;能存放 5 个
int
类型的元素;下标(索引)从 0 到 4;
每个元素都像是一个独立变量:
score[0]、score[1]、score[2]、score[3]、score[4]
数组 = 一串相同类型的变量 + 连续的内存空间 + 下标访问。
二、数组的声明语法
2.1 基本语法格式
类型说明符 数组名[常量表达式];
例如:
int num[10]; // 存放10个整数
float score[50]; // 存放50个浮点数
char name[20]; // 存放20个字符
关键点:
下标必须是整数常量或常量表达式;
数组元素类型必须相同;
定义后每个元素初值不确定(可能是随机值)。
2.2 错误示例
int n = 5; int arr[n]; // 在C90标准中不允许
(C99标准开始支持“变长数组”,后面章节会讲)
声明数组时要告诉编译器它的“类型”和“长度”,
这样系统才能在内存中分配一整块连续的空间。
三、一维数组的创建与初始化
C语言允许在定义数组时进行初始化。
初始化方式灵活多样,我们逐个来看。
3.1 方式1:完整初始化
int num[5] = {1, 2, 3, 4, 5};
说明:
定义时立即赋值;
元素个数与初始化值个数一致;
下标范围为 0~4。
3.2 方式2:部分初始化
int num[5] = {1, 2};
说明:
前两个元素为 1 和 2;
其余元素自动初始化为 0。
3.3 方式3:让编译器自动推导长度
int num[] = {10, 20, 30, 40};
说明:
系统会根据初始化列表自动计算长度;
等价于
int num[4] = {10, 20, 30, 40};
。
3.4 方式4:全部元素为 0
int num[5] = {0};
说明:
初始化列表中只有一个
0
;编译器会将所有元素都设为 0。
3.5 错误示例
int num[5];
num = {1,2,3,4,5}; // 错误!数组不能整体赋值
数组在定义时可以整体初始化,
但定义完成后,只能逐个元素赋值,不能整体重新赋值。
四、数组元素的访问
数组的每个元素通过下标访问。下标从 0 开始,到长度 - 1 结束。
4.1 示例1
#include <stdio.h>int main() {int num[5] = {10, 20, 30, 40, 50};printf("第一个元素:%d\n", num[0]);printf("第三个元素:%d\n", num[2]);printf("最后一个元素:%d\n", num[4]);return 0;
}
输出:
第一个元素:10
第三个元素:30
最后一个元素:50
原理:
数组名 + 下标 = 对应位置元素。
num[2]
的含义是:
从数组首地址起偏移 2 个元素。
访问数组元素时务必注意下标范围,
超出范围会造成内存越界错误,属于C语言最常见Bug之一。
五、一维数组的使用
数组是循环结构的最佳拍档。
它让我们可以批量输入、输出和处理数据。
5.1 示例1:输入并输出5个整数
#include <stdio.h>int main() {int num[5];int i;// 输入阶段for (i = 0; i < 5; i++) {printf("请输入第 %d 个整数:", i + 1);scanf("%d", &num[i]);}// 输出阶段printf("你输入的数字是:\n");for (i = 0; i < 5; i++) {printf("%d ", num[i]);}printf("\n");return 0;
}
输出示例:
请输入第 1 个整数:10
请输入第 2 个整数:20
请输入第 3 个整数:30
请输入第 4 个整数:40
请输入第 5 个整数:50
你输入的数字是:
10 20 30 40 50
解读:
使用循环可以轻松遍历数组;
num[i]
表示第 i+1 个元素;下标从 0 开始,范围是
0~4
。
5.2 示例2:计算数组元素的平均值
#include <stdio.h>int main() {int score[5];int i, sum = 0;for (i = 0; i < 5; i++) {printf("请输入第 %d 个分数:", i + 1);scanf("%d", &score[i]);sum += score[i];}printf("平均分为:%.2f\n", sum / 5.0);return 0;
}
输出:
请输入第 1 个分数:90
请输入第 2 个分数:85
请输入第 3 个分数:78
请输入第 4 个分数:92
请输入第 5 个分数:88
平均分为:86.60
使用 for 循环配合数组,能批量处理数据,
极大提高程序可读性与可维护性。
六、一维数组的内存存储机制
理解数组,必须了解它在内存中的结构。
6.1 原理概述
-
数组在内存中是连续存储的;
-
元素之间紧密排列;
-
数组名代表第一个元素的地址。
6.2 示例1:查看数组元素地址
#include <stdio.h>int main() {int a[5] = {10, 20, 30, 40, 50};for (int i = 0; i < 5; i++) {printf("a[%d] 的地址是:%p\n", i, &a[i]);}return 0;
}
输出示例:
a[0] 的地址是:000000000061FE10
a[1] 的地址是:000000000061FE14
a[2] 的地址是:000000000061FE18
a[3] 的地址是:000000000061FE1C
a[4] 的地址是:000000000061FE20
解读:
每个
int
占 4 字节;地址之间差 4;
表示数组在内存中一格接一格存储。
6.3 示例2:数组名与指针的关系
#include <stdio.h>int main() {int a[3] = {10, 20, 30};printf("a 的值:%p\n", a);printf("&a[0] 的值:%p\n", &a[0]);printf("a[0] 的内容:%d\n", a[0]);return 0;
}
输出(示例):
a 的值:000000000061FE10
&a[0] 的值:000000000061FE10
a[0] 的内容:10
结论:
数组名
a
实际上等价于&a[0]
,
即第一个元素的地址。
6.4 注意
-
数组名是常量指针,不能被修改;
-
例如:
int a[5]; a = a + 1; // 错误:数组名不是变量
-
但我们可以定义一个“普通指针”指向它:
int *p = a; p++;
数组的本质是连续的内存块;
数组名其实是一个“指针常量”。
七、sizeof 与元素个数计算
sizeof
是一个非常实用的运算符,
它能告诉我们变量或数据类型占用的字节数。
7.1 示例1:查看数组大小
#include <stdio.h>int main() {int a[5] = {1, 2, 3, 4, 5};printf("数组a占用字节数:%zu\n", sizeof(a));printf("一个元素占字节数:%zu\n", sizeof(a[0]));return 0;
}
输出:
数组a占用字节数:20
一个元素占字节数:4
解读:
每个
int
占 4 字节;5 个元素总共 20 字节;
%zu
是打印size_t
类型的标准格式符。
7.2 示例2:求数组元素个数
int count = sizeof(a) / sizeof(a[0]);
printf("数组元素个数为:%d\n", count);
输出:
数组元素个数为:5
元素个数 = 数组总大小 / 单个元素大小
这在实际编程中非常常用,尤其是在循环中遍历数组时。
7.3 应用:自动遍历数组
#include <stdio.h>int main() {int a[] = {3, 6, 9, 12, 15};int count = sizeof(a) / sizeof(a[0]);for (int i = 0; i < count; i++) {printf("a[%d] = %d\n", i, a[i]);}return 0;
}
输出:
a[0] = 3
a[1] = 6
a[2] = 9
a[3] = 12
a[4] = 15
优点:
不必手动数长度;
程序更通用;
避免“越界访问”风险。
sizeof
不仅能测大小,还能让代码更安全、通用。
记住:数组遍历推荐使用 sizeof 计算元素数量。
八、二维数组的定义与初始化
一维数组能存放一列数据,
而二维数组就像是“表格”一样,可以存放行与列的数据。
8.1 基本定义格式
类型说明符 数组名[行数][列数];
例如:
int a[3][4]; // 3行4列的整型数组
理解方式:
a[0][0]
表示第1行第1列;
a[2][3]
表示第3行第4列;二维数组本质上是“数组的数组”,
即:每一行又是一个一维数组。
8.2 初始化方式
方式1:完整初始化
int a[2][3] = {{1, 2, 3},{4, 5, 6}
};
-
第一行:
{1,2,3}
-
第二行:
{4,5,6}
方式2:行内展开初始化
int a[2][3] = {1, 2, 3, 4, 5, 6};
-
编译器自动按行填充;
-
与上例效果完全相同。
方式3:部分初始化
int a[2][3] = { {1}, {4, 5} };
-
未给出的元素自动补 0。
方式4:让编译器推导列数
int a[][3] = { {1,2,3}, {4,5,6} };
-
第一维(行数)可以省略;
-
第二维(列数)必须明确。
二维数组的初始化与一维数组类似,只是多了一层“行”的概念。
九、二维数组的访问与内存布局
二维数组的访问同样使用下标:
a[行][列]
9.1 示例1:打印二维数组
#include <stdio.h>int main() {int a[2][3] = {{1, 2, 3},{4, 5, 6}};for (int i = 0; i < 2; i++) {for (int j = 0; j < 3; j++) {printf("%d ", a[i][j]);}printf("\n");}return 0;
}
输出:
1 2 3
4 5 6
-
外层循环控制行;
-
内层循环控制列;
-
访问顺序为行优先(row-major order)。
9.2 内存布局示意
在内存中,二维数组也是连续存放的:
a[0][0] a[0][1] a[0][2] a[1][0] a[1][1] a[1][2]
C语言中,二维数组按“行优先”顺序存储。
9.3 示例:输出每个元素的地址
#include <stdio.h>int main() {int a[2][3] = { {1,2,3}, {4,5,6} };for (int i = 0; i < 2; i++) {for (int j = 0; j < 3; j++) {printf("&a[%d][%d] = %p\n", i, j, &a[i][j]);}}return 0;
}
输出示例:
&a[0][0] = 000000000061FE10
&a[0][1] = 000000000061FE14
&a[0][2] = 000000000061FE18
&a[1][0] = 000000000061FE1C
...
地址之间连续;
每次偏移4字节(int类型);
表示二维数组在底层其实就是“一维线性块”。
二维数组的本质是一维数组的嵌套,所有元素仍在一块连续内存中。
十、C99 的变长数组(VLA)
C99标准引入了一个新特性——变长数组(Variable Length Array, VLA)。
它允许我们使用变量来定义数组长度,而不再局限于常量。
10.1 示例
#include <stdio.h>int main() {int n;printf("请输入数组长度:");scanf("%d", &n);int arr[n]; // 可行! 变长数组(C99支持)for (int i = 0; i < n; i++) {arr[i] = i + 1;}for (int i = 0; i < n; i++) {printf("%d ", arr[i]);}return 0;
}
输出示例:
请输入数组长度:6
1 2 3 4 5 6
变长数组只在C99及之后标准有效;
栈内存动态分配,退出函数时自动释放;
不能用初始化列表赋值;
不能使用
sizeof
计算长度。
变长数组提供了灵活性,但使用时要注意兼容性与内存开销。
十一、实战练习
下面通过两个经典案例,让你彻底掌握数组的应用。
11.1 实战1:字符汇聚动画(welcome to bit)
#include <stdio.h>
#include <string.h>
#include <windows.h>int main() {char str[] = "welcome to bit";int left = 0;int right = strlen(str) - 1;while (left <= right) {printf("\r"); // 光标回到行首for (int i = 0; i < strlen(str); i++) {if (i < left || i > right)printf("%c", str[i]);elseprintf(" ");}fflush(stdout);Sleep(200);left++;right--;}printf("\n动画结束!\n");return 0;
}
核心逻辑:
使用字符数组保存字符串;
通过下标控制字符显示;
Sleep()
产生动画效果;演示数组在字符串控制中的应用。
11.2 实战2:二分查找(Binary Search)
#include <stdio.h>int main() {int arr[] = {1, 3, 5, 7, 9, 11, 13};int key, left = 0, right = 6;printf("请输入要查找的数字:");scanf("%d", &key);while (left <= right) {int mid = (left + right) / 2;if (arr[mid] == key) {printf("找到啦!下标 = %d\n", mid);return 0;} else if (arr[mid] < key)left = mid + 1;elseright = mid - 1;}printf("未找到该数字。\n");return 0;
}
解读:
数组 + 循环 + 判断 的完美结合;
体现算法思维;
展现数组作为数据载体的核心作用。
实战是最好的理解。
通过字符动画与查找算法,你已经能在C语言中使用数组做出“有逻辑的程序”。
十二、总结
这一讲,我们走完了C语言数据存储的第一步。
你已经掌握了:
一维数组与二维数组的定义与使用;
数组的内存布局与下标访问原理;
sizeof
的应用与元素个数计算;C99 的变长数组(VLA);
数组的典型实战案例。
从这一讲开始,程序真正学会了“记忆”。
数据不再是一次性输入输出,而是可以被保存、分析与操作的。
接下来,你将学会让程序“有条理地思考”——那就是函数的世界。
十三、下一讲预告
13.1 下一讲预告
《C语言入门教程(第6讲):函数详解——让程序会“思考”与“复用”》
在下一讲中,我们将全面讲解:
函数的定义、声明与调用;
参数传递与返回值;
数组作为函数参数;
作用域与存储类型(static、extern);
函数嵌套与递归初步。
一句话总结:
数组让程序能记,函数让程序能“想”。
13.2 提前准备建议
预先练习编写一个计算平方的函数:
int square(int x) {return x * x; }
观察函数调用过程,理解输入输出的“流动”;
阅读前几讲中的数组案例,思考如何将逻辑封装为函数;
熟悉
return
与参数类型的匹配规则。
提示:
你可以把函数想象成一个“工厂”——
接收原料(参数),加工处理,输出结果(返回值)。
📘 系列结尾
本文属于《C语言入门教程》系列专栏第 5 讲
下一讲将带你进入C语言的“函数世界”,
让你的程序从“能存数据”进化为“能思考、能复用”。点赞 + 收藏 + 关注专栏,别错过更新,
你的支持,是我持续更新的最大动力 💪