一维数组:从定义到实战

数组这东西,说白了就是一块连续的内存空间,里面存了一堆相同类型的数据。我刚开始学C语言时,总觉得数组不就是个“大箱子”嘛,有啥好讲的?后来做项目才发现,用不好数组,代码能写得又臭又长。

举个例子,你要存100个学生的成绩。没有数组?那就得定义100个变量:score1, score2, score3……写到第50个我就想摔键盘了。数组就是来解决这个问题的。

一维数组的定义与初始化

定义数组的语法很简单:

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

比如:

int scores[100];      // 100个int,未初始化
float prices[50];     // 50个float,未初始化
char name[20];        // 20个char,未初始化

嗯,这里要注意:元素个数必须是常量。C99之前不支持变量做数组长度,虽然C99引入了变长数组(VLA),但我个人建议——别用。为啥?移植性差,而且栈上分配大数组容易崩。

初始化有几种玩法:

// 完全初始化
int a[5] = {1, 2, 3, 4, 5};

// 部分初始化,剩下的补0
int b[5] = {1, 2};  // 等价于 {1, 2, 0, 0, 0}

// 不指定长度,编译器自动算
int c[] = {1, 2, 3, 4, 5};  // 长度就是5

// 全部初始化为0
int d[100] = {0};
我的习惯:定义数组时一定初始化。哪怕全赋0也行。不然里面是随机值,调试时能把你坑哭。

数组的访问与遍历

数组下标从0开始,这个坑我见过太多人踩了。你定义 int a[5],能用的下标是0到4,没有a[5]这回事。

int arr[5] = {10, 20, 30, 40, 50};

// 访问单个元素
printf("%d\n", arr[0]);  // 输出10
printf("%d\n", arr[4]);  // 输出50

// 遍历——最常用的写法
for (int i = 0; i < 5; i++) {
    printf("arr[%d] = %d\n", i, arr[i]);
}
我曾经踩过的坑:有一次写排序算法,循环条件写成了 i <= 5,结果数组越界。程序没立刻崩,但排序结果总是莫名其妙。查了两小时才发现是下标越界把栈上别的变量给改了。从此以后,我写循环都默念三遍:小于号,小于号,小于号。

数组名在大多数情况下会退化为指向首元素的指针。所以 arr&arr[0] 是等价的。但要注意,sizeof(arr) 返回的是整个数组的大小,不是指针的大小。

int arr[5];
printf("%zu\n", sizeof(arr));   // 20(假设int占4字节)
printf("%zu\n", sizeof(&arr[0])); // 8(64位系统,指针大小)

二维数组:表格思维

二维数组,说白了就是“数组的数组”。你想想看,一维数组是一条线,二维数组就是一个表格——有行有列。

定义与初始化

// 定义一个3行4列的二维数组
int matrix[3][4];

// 初始化
int grid[2][3] = {
    {1, 2, 3},
    {4, 5, 6}
};

// 也可以写成一串,编译器会自动按行填充
int grid2[2][3] = {1, 2, 3, 4, 5, 6};

内存里怎么存的?行优先存储。也就是说,先存完第一行所有列,再存第二行。这一点很重要,遍历时按行访问效率最高。

// 高效遍历:外层行,内层列
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 4; j++) {
        printf("%d ", matrix[i][j]);
    }
    printf("\n");
}
性能小贴士:如果你把内外层循环反过来(外层列,内层行),CPU缓存命中率会大幅下降。我在做图像处理项目时,就因为这个顺序问题,性能差了3倍多。

数组作为函数参数

数组传参时,实际上传的是指针。这一点很多新手会懵——明明我传的是整个数组,怎么函数里 sizeof 就变成指针大小了?

// 这三种写法完全等价
void func1(int arr[], int size);
void func2(int *arr, int size);
void func3(int arr[10], int size);  // 这里的10被忽略

所以,必须额外传数组长度。函数内部没法通过 sizeof 知道数组大小。

void print_array(int arr[], int size) {
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main() {
    int data[] = {1, 2, 3, 4, 5};
    int len = sizeof(data) / sizeof(data[0]);  // 计算元素个数
    print_array(data, len);
    return 0;
}

二维数组传参时,必须指定第二维的大小

void print_matrix(int mat[][4], int rows) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < 4; j++) {
            printf("%d ", mat[i][j]);
        }
        printf("\n");
    }
}

为啥必须指定第二维?因为编译器需要知道每行有多少个元素,才能计算 mat[i][j] 的内存地址。

核心总结:
  • 数组下标从0开始,遍历时用 i < n,不是 i <= n
  • 数组名是常量指针,不能自增自减
  • 传数组时一定要带长度参数
  • 二维数组是行优先存储,遍历时外层行内层列效率最高
数组知识体系 数组 一维数组 二维数组 数组作为函数参数 定义与初始化 访问与遍历 行优先存储 嵌套遍历 指针传递 需传长度 ⚠ 常见陷阱 下标从0开始 · 数组名是常量指针 · 二维数组传参需指定第二维 图:数组知识体系结构图
公众号:蓝海资料掘金营,微信deep3321