c语言9:从内存到实践深入浅出理解数组
数组是编程中最基础也最常用的数据结构之一,几乎所有编程语言都支持数组。今天我们就来全面了解数组的方方面面,包括它在内存中的存储方式、二维数组的特性、初始化方法、常见问题以及数组名的特殊含义。
什么是数组?
简单来说,数组是相同类型元素的有序集合。它能让我们用一个名称管理一组相关的数据,通过索引来访问各个元素。
例如,我们可以用一个数组存储班级所有学生的成绩,而不用为每个学生单独创建一个变量
数组在内存中的存储
数组最显著的特点是连续存储,这意味着数组元素在内存中占据连续的存储空间。
想象一下内存就像一排储物柜,数组就像其中连续的几个柜子,每个柜子放一个元素,它们之间没有其他数据。
#include<stdio.h>
int main()
{int arr[7] = { 0,1,2,3,4,5,6 };printf("arr[%d]->%p\n", 0, &arr[0]);printf("arr[%d]->%p\n", 1, &arr[1]);printf("arr[%d]->%p\n", 2, &arr[2]);printf("arr[%d]->%p\n", 3, &arr[3]);printf("arr[%d]->%p\n", 4, &arr[4]);printf("arr[%d]->%p\n", 5, &arr[5]);printf("arr[%d]->%p\n", 6, &arr[6]);return 0;
}
结果为:
arr[0]->0000008D599EF5D8
arr[1]->0000008D599EF5DC
arr[2]->0000008D599EF5E0
arr[3]->0000008D599EF5E4
arr[4]->0000008D599EF5E8
arr[5]->0000008D599EF5EC
arr[6]->0000008D599EF5F0
内存地址图示:
这种连续存储的特性让数组访问速度非常快,因为知道了第一个元素的地址和元素类型,就能直接计算出任意元素的地址。
一维数组的初始化
在 C 语言中,数组有多种初始化方式:
// 方法1:指定长度并初始化
int scores[5] = {90, 85, 95, 88, 92};// 方法2:不指定长度,编译器自动计算
int numbers[] = {1, 2, 3, 4, 5};// 方法3:部分初始化,未初始化的元素默认为0
int values[5] = {10, 20}; // 相当于 {10, 20, 0, 0, 0}// 方法4:初始化所有元素为0
int zeros[5] = {0};
数组名是首元素的地址
在 C 语言中,数组名代表了数组首元素的地址,这是一个非常重要的特性,除了两个特例(sizeof(数组名)与 &数组名)
1:sizeof(数组名)这里的数组表示整个数组,计算的是整个数组的大小,单位是字节
2:&数组名 ,这里是整个数组为单位的地址
#include <stdio.h>int main()
{int arr[5] = {1, 2, 3, 4, 5};printf("数组名 arr 的值: %p\n", arr);printf("首元素 &arr[0] 的地址: %p\n", &arr[0]);// 数组名 + 1 表示下一个元素的地址printf("arr + 1 的值: %p\n", arr + 1);printf("第二个元素 &arr[1] 的地址: %p\n", &arr[1]);// 通过指针方式访问数组元素printf("*(arr + 2) = %d\n", *(arr + 2)); // 相当于 arr[2]printf("arr[2] = %d\n", arr[2]);return 0;
}
运行这段代码,你会发现数组名arr
和首元素地址&arr[0]
是一样的。这也是为什么我们可以通过指针来操作数组。
数组名 arr 的值: 000000EB6C8FFBE8
首元素 &arr[0] 的地址: 000000EB6C8FFBE8arr + 1 的值: 000000EB6C8FFBEC
第二个元素 &arr[1] 的地址: 000000EB6C8FFBEC*(arr + 2) = 3arr[2] = 3
二维数组
二维数组可以理解为 "数组的数组",它有行和列的概念。比如int matrix[3][4]
表示一个 3 行 4 列的二维数组。
二维数组在内存中的存储
和一维数组一样,二维数组在内存中也是连续存储的,按行排列。
matrix[0][0] matrix[0][1] matrix[0][2] matrix[0][3]
matrix[1][0] matrix[1][1] matrix[1][2] matrix[1][3]
matrix[2][0] matrix[2][1] matrix[2][2] matrix[2][3]
在内存中实际上是这样排列的:
matrix[0][0], matrix[0][1], matrix[0][2], matrix[0][3], matrix[1][0], matrix[1][1], ...
二维数组的初始化
// 方法1:完整初始化
int matrix[3][4] =
{{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12}
};// 方法2:简化写法,按顺序初始化
int matrix[3][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};// 方法3:部分初始化,其余元素为0
int matrix[3][4] =
{{1},{5, 6},{9, 10, 11}
};// 方法4:可以省略第一维的大小,编译器会自动计算
int matrix[][] =
{{1, 2, 3},{4, 5, 6},{7, 8, 9}
};
数组越界问题
数组越界是指访问了超出数组定义范围的索引。这是编程中常见的错误,而且在 C 语言中不会被编译器检查出来。
#include <stdio.h>int main()
{int arr[5] = {1, 2, 3, 4, 5};// 合法访问:索引 0-4printf("arr[0] = %d\n", arr[0]);printf("arr[4] = %d\n", arr[4]);// 数组越界:索引 5 超出了定义的范围printf("arr[5] = %d\n", arr[5]); // 未定义行为return 0;
}
运行结果:
arr[0] = 1
arr[4] = 5
arr[5] = -858993460
这里的arr[5]直接访问了内存值转为int值
数组越界的危害:
- 访问到无关数据,导致结果错误
- 修改到其他变量的值,引发难以排查的 bug
- 可能导致程序崩溃
- 严重时可能被利用为安全漏洞
数组的遍历
遍历数组就是依次访问数组的每个元素,通常使用循环来实现
#include <stdio.h>int main()
{int numbers[] = {10, 20, 30, 40, 50};int length = sizeof(numbers) / sizeof(numbers[0]); // 计算数组长度// 遍历一维数组printf("一维数组元素: ");for (int i = 0; i < length; i++) {printf("%d ", numbers[i]);}printf("\n");// 遍历二维数组int matrix[3][4] = {{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12}};int rows = sizeof(matrix) / sizeof(matrix[0]);int cols = sizeof(matrix[0]) / sizeof(matrix[0][0]);printf("二维数组元素:\n");for (int i = 0; i < rows; i++) {for (int j = 0; j < cols; j++) {printf("%d ", matrix[i][j]);}printf("\n");}return 0;
}
上面代码中
1:sizeof(numbers) / sizeof(numbers[0])
是一种计算数组行数的常用方法
2:sizeof(matrix[0]) / sizeof(matrix[0][0]) 是一种计算数组列数的常用方法
总结
数组是一种简单而强大的数据结构,理解它的工作原理对编程非常重要:
- 数组在内存中连续存储,这使得访问速度很快
- 数组名代表首元素的地址,可以通过指针操作数组
- 二维数组本质上是 "数组的数组",在内存中按行连续存储
- 数组越界是常见错误,需要特别注意避免
- 可以通过循环遍历数组元素,使用
sizeof
可以计算数组长度
掌握数组的使用是编程的基础,后续学习更复杂的数据结构(如链表、栈、队列等)时,很多概念都与数组有关联。
希望这篇文章能帮助你更好地理解数组!如果你有任何疑问或想了解更多细节,欢迎在评论区留言。