笔者在学习C语言后,对C语言还没有深入理解,紧接着就学习了C++。这导致对C语言的理解,长期存在偏差,比如C和C++都有&
运算符,C++中&
除了取变量地址,还表示引用,导致我一直以为C中也有引用。
今天将另一个关于指针的问题,很长时间以来,笔者都认为数组和指针在很多地方是等价的,比如int *a
和int a[]
;int **a
和int a[][]
。
那么这个理解有错么?错在哪里呢?
一维数组和指针
我们直接看下面代码:
1 | int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; |
由于“数组的名字等价于数组首元素的地址”,所以上面代码中a、b、c都是指向数组a[10]的首元素(a[0])的地址,三者在用法上完全相同:
1 |
|
但其实a
和b
的类型都不相同,a
是int指针,而b
是int指针的指针。
如果第2行改成int *b = a;
,两者类型上就相同了,那么这种情况下可以认为a
、b
等价了吗?还是不行: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 | b[i] == a[0][i]; |
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 | f(int a[2][3]) { ... } |
上面3中声明方式,在函数内都可以使用a[i][j]
的方式访问数组元素。
ps:
f(int *a[13]) { ... }
这种声明方式,无法使用a[i][j]
的方式访问数组元素
那么f(int *a) { ... }
这种声明方式正确吗?理论上是正确的,只不过不能使用a[i][j]
的方式访问元素,而应该使用*(a + 3 * i + j)
的方式访问元素。
多重指针作为函数参数:
用数组做函数参数的方式,数组列数是固定的,如果想要实现动态分配内存,需要使用多重数组作为函数参数:
1 | f(int **a) { ... } |
上面代码中,arr1
可以正常打印,arr2
却会报错。