C语言:多重指针和多重数组

笔者在学习C语言后,对C语言还没有深入理解,紧接着就学习了C++。这导致对C语言的理解,长期存在偏差,比如C和C++都有&运算符,C++中&除了取变量地址,还表示引用,导致我一直以为C中也有引用。

今天将另一个关于指针的问题,很长时间以来,笔者都认为数组和指针在很多地方是等价的,比如int *aint a[]int **aint a[][]

那么这个理解有错么?错在哪里呢?

一维数组和指针

我们直接看下面代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int *b = &a[0];
int *c = a;

for(int i = 0;i < 9;i++) {
printf("%d ", a[i]);
}
printf("\n");

for(int i = 0;i < 9;i++) {
printf("%d ", b[i]);
}
printf("\n");

for(int i = 0;i < 9;i++) {
printf("%d ", c[i]);
}
printf("\n");

由于“数组的名字等价于数组首元素的地址”,所以上面代码中a、b、c都是指向数组a[10]的首元素(a[0])的地址,三者在用法上完全相同:

1
2
3
4
5
6
7
8
9
10

从中可以看出`b + i == &b[i]`。

## 多维数组和多重指针

一维数组的这个特点让我误以为,下面代码中a和b在用法上也是等价的。

```c
int a[2][3] = {{1, 2, 3}, {4, 5, 6}};
int **b = a;

但其实ab的类型都不相同,a是int指针,而b是int指针的指针
如果第2行改成int *b = a;,两者类型上就相同了,那么这种情况下可以认为ab等价了吗?还是不行:a[i][j] != b[i][j]

原因如下:

  • 对a来说: 二维数组本质上也是一维数组,只不过这个“一维数组”的每个元素,是一个一维数组。a[i]表示二维数组的第i个元素(一维数组),a[i][j]表示a[i]这个一维数组的第j个元素。

  • 对b来说:b本质是int指针。 &b[i] = b + i,其中b + i表示指向b后第i个元素的地址,元素就是指针指向的类型,对b来说是int。b[i]表示b后第i个int的值,等价于a[0][i]

所以有下列等式成立:

1
2
3
b[i] == a[0][i];
a[i][j] == b[i * 2 + j];
a[i][j] == *(b + i * 2 + j);

ps:
下列声明方式,可以使a[i][j] == b[i][j],大家可以试着说明一下原理

1
int (*b)[3] = a; // pointer to array[13] of int 

再回到int **b = a的情况,这种情况下b[i][j]又代表什么意思呢?

b[i][j] = *(*(b + i) + j),由于b的类型是int指针的指针,所以b + i表示b后第i个int指针的地址,*(b + i)表示b后第i个指针。

进一步,*(b + i) + j就表示该指针(b后第i个指针)后第j个int的地址,*(&(b + i) + j)就表示该int地址对应的int值。

也就是说b[i][[j]对应的也是一个int。

但这不意味着a[i][j] == b[i][j],因为两者在解析式,地址的计算不同,参见本文后面关于“多重指针作为函数参数”的例子。

多维数组作为函数参数

多维数组作为函数参数时,下面3种声明方式都可以

1
2
3
4
5
f(int a[2][3]) { ... }

f(int a[][3]) { ... }

f(int (*a)[3]) { ... }

上面3中声明方式,在函数内都可以使用a[i][j]的方式访问数组元素。

ps:
f(int *a[13]) { ... }这种声明方式,无法使用a[i][j]的方式访问数组元素

那么f(int *a) { ... }这种声明方式正确吗?理论上是正确的,只不过不能使用a[i][j]的方式访问元素,而应该使用*(a + 3 * i + j)的方式访问元素。

多重指针作为函数参数:

用数组做函数参数的方式,数组列数是固定的,如果想要实现动态分配内存,需要使用多重数组作为函数参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
f(int **a) { ... }


void foo(int** array, int row_len, int col_len) {

for(int i = 0;i < row_len;i++) {

for(int j = 0;j < col_len;j++) {

printf("%d ", array[i][j]);

}

printf("\n");

}

}



int **make2DArray(int m, int n) {

int **arr = (int **)malloc(m * sizeof(int *));

for (int r = 0; r < m; r++) {

arr[r] = (int *)malloc(n * sizeof(int));

}

for(int i = 0;i < m;i++) {

for(int j = 0;j < n;j++) {

arr[i][j] = i * 2 + j;

}

}

return arr;

}



int main() {

int **arr1 = make2DArray(5,2);

int arr2[5][2] = {{1, 2}, {3, 4}, {5, 6}, {7, 8}, {9, 10}};



foo(arr1, 5, 2);

printf("\n");

foo(arr2, 5, 2);

}

上面代码中,arr1可以正常打印,arr2却会报错。