第六章 数组(一):一维数组——把数据排好队
数组这东西,说白了就是给一堆相同类型的数据找个集体宿舍。我刚开始学C语言时,觉得数组不就是个列表嘛,有啥好学的?直到后来做嵌入式项目,要处理传感器采集的100个温度值,要是没有数组,我得定义100个变量——想想就头大。
嗯,这一章我们就来聊聊一维数组。从怎么定义、怎么初始化,到怎么访问、怎么遍历,最后再玩个排序。你跟着我走一遍,保证能搞定。
6.1 一维数组的定义
定义数组的语法很简单:
类型 数组名[元素个数];
比如:
int scores[10]; // 存放10个整型成绩
float temps[100]; // 存放100个浮点温度值
char name[50]; // 存放50个字符
这里有个坑,我当年踩过——元素个数必须是常量。你不能写成:
int n = 10;
int arr[n]; // 这在C89标准里是不允许的!
6.2 数组的初始化
定义数组时,你可以顺便给它赋初值。这就像搬进新宿舍时,先把东西摆好。
6.2.1 完全初始化
int a[5] = {1, 2, 3, 4, 5};
这样a[0]到a[4]分别被赋值为1到5。
6.2.2 部分初始化
int b[5] = {1, 2}; // 等价于 {1, 2, 0, 0, 0}
没给值的元素,系统自动补0。这个特性我经常用——初始化全0时,直接写 int arr[100] = {0}; 就行。
6.2.3 不指定大小的初始化
int c[] = {10, 20, 30}; // 编译器自动算出大小为3
我个人习惯在明确知道元素个数时,还是把大小写出来,代码更清晰。
sizeof(arr) / sizeof(arr[0]) 可以算出数组元素个数。我在项目中经常用这个宏,避免硬编码。
6.3 数组元素的访问
数组元素通过下标访问,下标从0开始。这可能是C语言最反直觉的地方——第1个元素的下标是0。
int arr[5] = {10, 20, 30, 40, 50};
printf("%d\n", arr[0]); // 输出10
printf("%d\n", arr[4]); // 输出50
我曾经在项目里犯过一个低级错误:定义了一个 int data[100],然后去访问 data[100]。你猜怎么着?程序没报错,但读到了一个垃圾值,导致整个控制逻辑跑飞了。
arr[5] 时,它只是去计算地址,然后读那个位置的值。如果那个位置存的是重要数据……嗯,后果自负。
6.4 数组的遍历
遍历数组,就是用循环把每个元素过一遍。最常用的就是for循环:
int arr[5] = {2, 4, 6, 8, 10};
for (int i = 0; i < 5; i++) {
printf("arr[%d] = %d\n", i, arr[i]);
}
输出结果:
arr[0] = 2
arr[1] = 4
arr[2] = 6
arr[3] = 8
arr[4] = 10
遍历时,循环变量i从0开始,到n-1结束。这个模式太常见了,你闭着眼睛都能写出来。
6.5 数组的排序——冒泡排序
排序是数组的经典操作。冒泡排序是最简单的一种,虽然效率不高(时间复杂度O(n²)),但胜在好理解。
它的思路很简单:相邻元素两两比较,大的往后冒。就像气泡从水底往上冒一样。
6.5.1 冒泡排序的原理
假设我们要把数组 {5, 3, 8, 1, 6} 从小到大排序。
第一轮:
- 比较5和3,5>3,交换 → {3, 5, 8, 1, 6}
- 比较5和8,5<8,不交换 → {3, 5, 8, 1, 6}
- 比较8和1,8>1,交换 → {3, 5, 1, 8, 6}
- 比较8和6,8>6,交换 → {3, 5, 1, 6, 8}
第一轮结束,最大值8冒到了最后。
第二轮:
- 比较3和5,3<5,不交换 → {3, 5, 1, 6, 8}
- 比较5和1,5>1,交换 → {3, 1, 5, 6, 8}
- 比较5和6,5<6,不交换 → {3, 1, 5, 6, 8}
第二轮结束,次大值6到了倒数第二位。
以此类推,直到全部排好。
6.5.2 代码实现
#include <stdio.h>
void bubble_sort(int arr[], int n) {
for (int i = 0; i < n - 1; i++) { // 外层循环:需要n-1轮
for (int j = 0; j < n - 1 - i; j++) { // 内层循环:每轮比较次数递减
if (arr[j] > arr[j + 1]) {
// 交换
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
int main() {
int arr[] = {5, 3, 8, 1, 6};
int n = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, n);
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
输出:1 3 5 6 8
- 外层循环控制轮数,n个元素需要n-1轮
- 内层循环控制每轮比较次数,第i轮比较n-1-i次
- 交换操作需要临时变量temp
6.5.3 冒泡排序的优化
如果某一轮没有发生任何交换,说明数组已经有序了,可以提前结束。我在项目中处理传感器数据时,数据经常已经接近有序,这个优化能省不少时间。
void bubble_sort_optimized(int arr[], int n) {
for (int i = 0; i < n - 1; i++) {
int swapped = 0; // 标记是否发生过交换
for (int j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = 1;
}
}
if (!swapped) {
break; // 没有交换,提前结束
}
}
}
6.6 本章知识体系
下面这张图帮你理清一维数组的知识脉络:
6.7 避坑总结
最后,我把这些年踩过的坑给你列一下:
| 常见错误 | 后果 | 正确做法 |
|---|---|---|
| 数组下标从1开始 | 漏掉第一个元素,或越界 | 牢记下标从0开始 |
| 访问 arr[n](越界) | 读取垃圾值,程序崩溃 | 只访问 arr[0] ~ arr[n-1] |
| 定义时用变量做大小 | 编译报错(C89) | 用常量或宏定义 |
| 冒泡排序内层循环条件写错 | 排序结果不对 | j < n-1-i,别忘了减i |
数组是C语言里最基础也最常用的数据结构。你把它玩熟了,后面学指针、字符串、二维数组都会轻松很多。嗯,这一章就到这儿,你自己多写几遍代码,比看十遍都管用。