一维数组:从零开始的数据容器
数组这东西,说白了就是一块连续的内存空间,里面存了一堆相同类型的数据。我刚开始学C语言时,总觉得数组不就是个列表嘛,有啥好讲的?直到后来做嵌入式项目,才发现数组用不好,程序崩得那叫一个惨烈。
1. 一维数组的定义
定义数组的语法很简单:
类型 数组名[元素个数];
举个例子:
int scores[10]; // 存储10个int型数据
float temps[5]; // 存储5个float型数据
char name[20]; // 存储20个char型数据
这里有几个关键点,我当年踩过坑:
- 元素个数必须是常量——C89标准不支持变量作为数组大小。虽然C99引入了变长数组(VLA),但嵌入式编译器不一定支持。我个人习惯用宏定义:
#define MAX_SIZE 100,然后int arr[MAX_SIZE]; - 数组大小在编译时就确定了——你想想看,数组占用的内存是静态分配的,运行时不能改变大小。
- 数组名是首元素的地址——这个后面会细说,先记住就行。
int a[5]的有效下标是0~4,没有a[5]这个元素。我曾经见过新手写int a[5]; a[5]=100;,结果程序跑飞了。
2. 数组的初始化
初始化数组有几种方式,我按常用程度排个序:
方式一:完全初始化
int arr[5] = {1, 2, 3, 4, 5};
方式二:部分初始化
int arr[5] = {1, 2}; // 等价于 {1, 2, 0, 0, 0}
没赋值的元素自动补0。嗯,这里要注意:只有静态初始化才会补0,局部变量不初始化的话,里面是垃圾值。
方式三:省略大小
int arr[] = {1, 2, 3, 4, 5}; // 编译器自动推断大小为5
这种方式我特别喜欢用,省得数个数。但要注意,如果数组大小由后面决定,就不能再改变用途了。
方式四:指定初始化(C99)
int arr[10] = {[0]=10, [5]=20, [9]=30};
这个在嵌入式开发中偶尔用到,比如初始化寄存器映射表时很方便。
3. 数组元素的访问
访问数组元素用下标运算符[]:
int a[5] = {10, 20, 30, 40, 50};
printf("%d\n", a[0]); // 输出10
printf("%d\n", a[2]); // 输出30
a[3] = 99; // 修改第4个元素为99
这里有个底层原理我想说一下:a[i] 实际上等价于 *(a + i)。编译器会计算:a + i * sizeof(int),然后取出那个地址的值。所以下标访问本质上就是指针运算。
有意思的是,你甚至可以写成 i[a],因为 a[i] 和 i[a] 在C语言中是等价的。不过我个人建议别这么写,代码可读性太差了。
4. 数组的越界问题
这是C语言中最常见的bug之一,也是我当年被折磨得最惨的地方。
a[10] 编译器不会报错,但运行时可能:
- 覆盖其他变量的值
- 破坏栈帧,导致函数返回地址被篡改
- 触发硬件异常(比如ARM的HardFault)
- 一切正常——这才是最可怕的,因为bug被隐藏了
我曾经在做一个数据采集项目时,定义了一个uint8_t buffer[64],结果代码里不小心写了个buffer[64] = 0。你猜怎么着?程序跑了三天才崩溃,查bug查得我怀疑人生。后来用内存检测工具才发现是越界写把相邻的变量给覆盖了。
避坑指南:
- 写循环时,注意边界条件。比如遍历
int a[10],循环条件应该是i < 10,不是i <= 10 - 使用宏定义数组大小,不要硬编码数字
- 接收用户输入时,一定要检查长度,防止缓冲区溢出
- 嵌入式开发中,可以用静态分析工具(比如PC-Lint)检查越界
5. 数组的遍历
遍历数组最常用的就是for循环:
int arr[5] = {2, 4, 6, 8, 10};
int sum = 0;
for (int i = 0; i < 5; i++) {
sum += arr[i];
}
printf("总和 = %d\n", sum);
除了for循环,也可以用while:
int i = 0;
while (i < 5) {
printf("%d ", arr[i]);
i++;
}
我个人更推荐for循环,因为循环变量初始化和更新都在一行,不容易漏掉。不过如果你要遍历到某个条件就提前退出,while可能更合适。
还有一种方式是用指针遍历,效率更高:
int *p = arr;
for (int i = 0; i < 5; i++) {
printf("%d ", *p++);
}
这种方式在嵌入式开发中很常见,因为指针运算比下标运算少一次乘法(虽然现代编译器优化后差别不大)。
知识体系结构图
下面这张图概括了一维数组的核心知识点:
总结一下
数组是C语言中最基础的数据结构,但越简单的东西越容易出错。我总结了三个要点:
- 定义时想清楚大小——用宏定义,别硬编码
- 访问时检查边界——循环条件写对,别越界
- 初始化别偷懒——局部数组不初始化就是定时炸弹
记住这三点,数组这块基本就稳了。下一节我们会讲二维数组,到时候你会发现,一维数组搞明白了,二维数组就是多了一个维度而已。
📌 核心记忆点:数组名是常量指针,不能赋值;数组下标从0开始;C语言不检查越界,全靠程序员自觉。
公众号:蓝海资料掘金营,微信deep3321