#include <stdio.h>
int main(void) {
int a[2][3] = {{1, 2, 3}, {4, 5, 6}}; // braced by row - clearest
int b[2][3] = {1, 2, 3, 4, 5, 6}; // flat - same result
int c[2][3] = {{1, 2}, {4}}; // partial rows: rest are zero
int d[2][3] = {0}; // every element zero
int e[][3] = {{1, 2, 3}, {4, 5, 6}}; // rows deduced, columns required
printf("a: "); for (int i = 0; i < 2; i++) for (int j = 0; j < 3; j++) printf("%d ", a[i][j]);
printf("\nb: "); for (int i = 0; i < 2; i++) for (int j = 0; j < 3; j++) printf("%d ", b[i][j]);
printf("\nc: "); for (int i = 0; i < 2; i++) for (int j = 0; j < 3; j++) printf("%d ", c[i][j]);
printf("\nd: "); for (int i = 0; i < 2; i++) for (int j = 0; j < 3; j++) printf("%d ", d[i][j]);
printf("\ne: "); for (int i = 0; i < 2; i++) for (int j = 0; j < 3; j++) printf("%d ", e[i][j]);
printf("\n");
return 0;
}
int e[][3] 这种形式很重要:你可以不写行数,让初始化列表来决定,但列数永远不能省略。下一节会解释原因。
行优先布局
C 按行优先顺序存储二维数组:先是完整的第 0 行,然后是完整的第 1 行,依此类推,放在一整块连续的内存中。背后并没有一个由行指针组成的数组。
int grid[3][4] = {{ 1, 2, 3, 4},
{ 5, 6, 7, 8},
{ 9,10,11,12}};
how you picture it how it actually sits in memory
+----+----+----+----+
| 1 | 2 | 3 | 4 | row 0 +--+--+--+--+--+--+--+--+--+--+--+--+
+----+----+----+----+ | 1| 2| 3| 4| 5| 6| 7| 8| 9|10|11|12|
| 5 | 6 | 7 | 8 | row 1 +--+--+--+--+--+--+--+--+--+--+--+--+
+----+----+----+----+ \__ row 0 __/\__ row 1 __/\_ row 2 _/
| 9 | 10 | 11 | 12 | row 2
+----+----+----+----+ grid[i][j] is at element index i*4 + j
i * columns + j 这个公式就是全部的机制,这也是为什么编译器必须知道列数才能访问任何元素。行数从不参与这个计算。
打印地址就能直接看到这种布局:
#include <stdio.h>
int main(void) {
int grid[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
printf("element addresses, in memory order:\n");
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
printf("grid[%d][%d] = %2d at %p\n", i, j, grid[i][j], (void *)&grid[i][j]);
}
}
// the same block read as one flat run of 12 ints
printf("\nflattened: ");
int *flat = &grid[0][0];
for (int k = 0; k < 12; k++) {
printf("%d ", flat[k]);
}
printf("\n");
return 0;
}
#include <stdio.h>
#define ROWS 3
#define COLS 4
int main(void) {
int grid[ROWS][COLS];
// fill with a multiplication table
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
grid[i][j] = (i + 1) * (j + 1);
}
}
// print it as a table
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
printf("%4d", grid[i][j]);
}
printf("\n");
}
// row totals and a grand total
int grand = 0;
printf("\nrow totals: ");
for (int i = 0; i < ROWS; i++) {
int rowSum = 0;
for (int j = 0; j < COLS; j++) {
rowSum += grid[i][j];
}
grand += rowSum;
printf("%d ", rowSum);
}
printf("\ngrand total: %d\n", grand);
return 0;
}
#include <stdio.h>
#define COLS 4
// the column size is required; the row count comes as a separate argument
void print_grid(int grid[][COLS], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < COLS; j++) {
printf("%4d", grid[i][j]);
}
printf("\n");
}
}
// exactly the same parameter, written as the pointer it really is
void scale_grid(int (*grid)[COLS], int rows, int factor) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < COLS; j++) {
grid[i][j] *= factor;
}
}
}
int main(void) {
int grid[3][COLS] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
print_grid(grid, 3);
scale_grid(grid, 3, 10);
printf("\nafter scaling:\n");
print_grid(grid, 3);
return 0;
}
原因在于退化。传递 grid 时,它会被转换成指向第一个元素的指针,而它的元素是行,所以类型是 int (*)[4]:指向包含 4 个 int 的数组的指针。为了让 grid[i][j] 有意义,编译器必须知道一行有多长,也就是这个 4。行数确实不在类型中,所以它要作为单独的参数传递。
注意,int (*grid)[COLS] 和 int grid[][COLS] 是同一个参数的两种写法。圆括号是必需的,因为 int *grid[COLS] 表示的是指针数组。这个区别在“指针与数组”一节中有介绍。
如果列数只有在运行时才知道,C99 的可变修改参数允许你先传入它:
void print_any(int rows, int cols, int grid[rows][cols]);