第六章 数组(一):一维数组——把数据排好队

数组这东西,说白了就是给一堆相同类型的数据找个集体宿舍。我刚开始学C语言时,觉得数组不就是个列表嘛,有啥好学的?直到后来做嵌入式项目,要处理传感器采集的100个温度值,要是没有数组,我得定义100个变量——想想就头大。

嗯,这一章我们就来聊聊一维数组。从怎么定义、怎么初始化,到怎么访问、怎么遍历,最后再玩个排序。你跟着我走一遍,保证能搞定。

6.1 一维数组的定义

定义数组的语法很简单:

类型 数组名[元素个数];

比如:

int scores[10];      // 存放10个整型成绩
float temps[100];    // 存放100个浮点温度值
char name[50];       // 存放50个字符

这里有个坑,我当年踩过——元素个数必须是常量。你不能写成:

int n = 10;
int arr[n];  // 这在C89标准里是不允许的!
注意: 数组大小在定义时必须是一个确定的常量表达式。C99支持变长数组(VLA),但嵌入式开发中很多编译器不支持,我建议你老老实实用常量。

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]。你猜怎么着?程序没报错,但读到了一个垃圾值,导致整个控制逻辑跑飞了。

避坑指南: C语言不会检查数组下标越界!访问 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 本章知识体系

下面这张图帮你理清一维数组的知识脉络:

一维数组 定义 类型 数组名[常量]; 例: int a[10]; 初始化 完全: int a[3]={1,2,3}; 部分: int b[3]={1}; //→{1,0,0} 不定长: int c[]={1,2}; 访问 下标从0开始 a[0] 是第一个元素 ⚠ 注意越界! 遍历 for(i=0; i<n; i++) 访问 arr[i] 排序(冒泡排序) 相邻元素两两比较 大的往后冒 优化:无交换则提前结束

6.7 避坑总结

最后,我把这些年踩过的坑给你列一下:

常见错误 后果 正确做法
数组下标从1开始 漏掉第一个元素,或越界 牢记下标从0开始
访问 arr[n](越界) 读取垃圾值,程序崩溃 只访问 arr[0] ~ arr[n-1]
定义时用变量做大小 编译报错(C89) 用常量或宏定义
冒泡排序内层循环条件写错 排序结果不对 j < n-1-i,别忘了减i

数组是C语言里最基础也最常用的数据结构。你把它玩熟了,后面学指针、字符串、二维数组都会轻松很多。嗯,这一章就到这儿,你自己多写几遍代码,比看十遍都管用。


公众号:蓝海资料掘金营,微信deep3321