未分类

C语言基础填坑(一)——多维数组

背景

舍友今天突然问我怎么把数组传入函数,我一开始心想这不是小儿科了吗,就说传指针。不过这时候我已经隐约感到有点知识空缺了,因为一时对直接传值传入数组好像没有概念了。然后舍友又遇到问题了,我一看才知道是要传入多维数组。我又想,多维和一维的存储是类似的啊,多维的存储仍然是线性的,只是需要切分成几段而已,所以应该也不难。但是上来我就蒙了:

1
2
int a[3][3];
int *p = a;

按照我的逻辑,反正都是int的指针,也就数组名是常量指针,但也可以被赋给其他指针吧?但Build之后……

E:\My Codes\test2\main.c|284|warning: initialization of ‘int ‘ from incompatible pointer type ‘int ()[3]’ [-Wincompatible-pointer-types]|

舍友的XCode上直接是Error。那看来我的想法是错的……连忙翻出《C Primer Plus》(第6版中文版),开始补课!

多维数组

此处以二维数组为例。

1
int zippo[4][2];

我们可以把zippo[i]理解为(子)数组,其既是zippo的元素也是int的数组。因为它是数组,所以它可以作为数组名使用,即zippo[i]指向zippo[i][0]
解引用*和数组引用[]具有相同的功能,既然zippo[i][j]才是int元素的话,那么zippo显然就是一个二重指针了。所以我之前的想法是错误的。
而且指针的类型比元素之间的更加严格,即使仅仅是维数不同也不行。我觉得应该是因为数组指针还记录了每个元素的长度,如果可以“降维”的话长度信息就彻底丢失了。所以要想达到传入首址的目的,需要写成:

1
2
3
int *p = a[0];
//误:不要写一维数组顺手了写成下面的形式
int *p = &a[0]; //这是取了子数组的首址的地址,即int **

若多维数组传参是只传入了首址的话,只需要知道各维的长度就可以用多重引用访问数组元素了。

1
p[j+3*i]=x;

我们也可以用更简单直观的方式,即与定义相同的方式p[i][j]访问多维数组。可是如此一来,需要知道下一层一直到最底层子数组的长度。所以只传首址好像不太够?当然办法也是有的,让我们往下看。

数组作参数

首先确认一点,数组是无法直接传值的,因为传值效率不如传址效率高。另外,可以使用const关键字使函数把传入的数组视作常量,保护数据。此时const写在类型之前。

一维数组

简单回忆一下,一维数组传参只能传入数组元素类型和首地址,数组长度需要另行传入。另一个知识点是,在函数签名中可以用array[]代替*array表示指针参数,但只能在函数签名中这样使用。

多维数组

除了只传入首地址外,要想简单直观地使用多维数组,需要传入首址和所有子数组的长度。传入的格式也很简单:

1
2
3
4
5
6
7
void func(int a[][3]){}
// 3即为子数组的长度
void func2(int a[][3][4]){}
// 首个方括号即为 array[],表示这是个指针
// 首个方括号中为了可读性也可写上值,但会被忽略。
// 第二个起所有方括号都必须写上长度。
void func3(int (*a)[3][4]){} //等价形式

需要注意的是,虽然看起来像是传值,但确实是传址引用,数据的值确实会被改变,这一点有点反直觉。

参考:《C Primer Plus(第6版)中文版》,第10章

分享到