C语言中的数组存储遵循行优先的规则,并非列优先。所谓行优先,指的是数组元素在内存中是按照行顺序依次连续存放的,同一行的元素先全部存储完成后,才会存储下一行的元素。
C语言数组行优先存储的具体表现
以二维数组为例,假设我们定义一个3行4列的整型数组,其内存布局会先存放第一行的4个元素,再存放第二行的4个元素,最后存放第三行的4个元素。我们可以通过打印每个元素的地址来验证这个规则。
#include <stdio.h>
int main() {
// 定义3行4列的二维数组
int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// 遍历打印每个元素的地址和值
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
printf("arr[%d][%d] = %d, 地址: %pn", i, j, arr[i][j], &arr[i][j]);
}
}
return 0;
}
运行上述代码后,你会发现地址是依次递增的,且同一行的元素地址连续,第一行最后一个元素的地址加一个int类型的大小,就是第二行第一个元素的地址,完全符合行优先的存储特征。
列优先存储的特点
列优先存储和行优先相反,会先存储第一列的所有元素,再存储第二列的所有元素,以此类推。Fortran语言中的数组默认就是列优先存储的。我们同样可以用地址验证列优先的逻辑,假设是列优先存储,那么arr[0][0]的下一个地址应该是arr[1][0],而不是arr[0][1],这和上面C语言代码的运行结果不符。
存储顺序对程序的影响
了解数组的存储顺序对程序性能有很大影响。因为CPU访问内存时会有缓存机制,连续访问内存地址的元素时缓存命中率更高。如果我们在C语言中遍历二维数组时按行遍历,也就是先固定行号,遍历列元素,那么访问的元素地址是连续的,效率会更高;如果按照列优先的方式遍历,也就是先固定列号,遍历行元素,那么访问的地址是不连续的,缓存命中率低,程序运行速度会更慢。
#include <stdio.h>
#include <time.h>
#define ROW 1000
#define COL 1000
int main() {
int arr[ROW][COL];
// 初始化数组
for (int i = 0; i < ROW; i++) {
for (int j = 0; j < COL; j++) {
arr[i][j] = i + j;
}
}
clock_t start, end;
long sum = 0;
// 行优先遍历
start = clock();
for (int i = 0; i < ROW; i++) {
for (int j = 0; j < COL; j++) {
sum += arr[i][j];
}
}
end = clock();
printf("行优先遍历耗时: %f 秒n", (double)(end - start) / CLOCKS_PER_SEC);
sum = 0;
// 列优先遍历
start = clock();
for (int j = 0; j < COL; j++) {
for (int i = 0; i < ROW; i++) {
sum += arr[i][j];
}
}
end = clock();
printf("列优先遍历耗时: %f 秒n", (double)(end - start) / CLOCKS_PER_SEC);
return 0;
}
运行上述代码可以明显看到,行优先遍历的耗时远小于列优先遍历的耗时,这就是存储顺序带来的实际影响。
常见误区说明
有些开发者会把数组的下标访问顺序和存储顺序混淆,认为先写列下标就是列优先,这是错误的。C语言中arr[i][j]的写法只是语法层面的访问方式,和底层内存存储顺序无关,底层始终是行优先存储。另外,如果是多维数组,C语言同样遵循行优先规则,也就是最右边的下标变化最快,最左边的下标变化最慢。
| 存储方式 | 元素存放顺序 | 典型语言 |
|---|---|---|
| 行优先 | 同一行元素连续存放,行号递增存放 | C、C++、Java |
| 列优先 | 同一列元素连续存放,列号递增存放 | Fortran、MATLAB |
总结来说,C语言数组的存储方式是行优先,并非列优先,在编写涉及数组遍历的代码时,尽量按照行优先的顺序访问元素,能够提升程序的运行效率。