文章目录
- 多维数组
- 数组名
- 下标
- 指向数组的指针
- 作为函数参数的多维数组
- 指针数组
- 小结
多维数组
如果某个数组的维数超过1,它就被称为多维数组,例如,下面这个声明:
int matrix[6][10]
创建了一个包含60个元素的矩阵。但是,它是6行每行10个元素,还是10行每行6个元素?
为了回答这个问题,你需要从一个不同的视点观察多维数组。考虑下列这些维数不断增加的声明:
int a;
int b[10];
int c[6][10];
int d[3][6][10];
a是个简单的整数,接下来的那个声明增加一个维数,所以b就是一个向量,它包含10个整形元素。
c只是在b的基础上再增加一维,所以我们可以把c看做是一个包含6个元素的向量,只不过它的每个元素本身是一个包含10个整形元素的向量。换句话说,c是个一维数组的一维数组。d也是如此,它是一个包含三个元素的数组,每个元素都是包含6个元素的数组,而这6个元素中的每一个都是包含10个整形元素的数组,间接地说,d是一个3排6行10列的整形三维数组。
·
数组名
一维数组名的值是一个指针常量,它指向数组的第一个元素,它的类型是“指向元素类型的指针” 。多维数组也差不多简单,唯一的区别是多维数组第1维的元素实际上是另一个数组。例如,下面这个声明:
int matrix[3][10];
创建了matrix,它可以看做是一个一维数组,包含3个元素,只是每个元素恰好是包含10个整形元素的数组。
matrix这个名字的值是一个指向它第一个元素的指针,所以matrix是一个指向一个包含10个整型元素的数组的指针。
下标
如果要标识一个多维数组的某个元素,必须按照与数组声明时相同的顺序为每一维都提供一个下标,而且每个下标都单独位于一对方括号内。在下面的声明中:
int matrix[3][10];
表达式matrix[1][5]
访问下面这个元素
但是,下标引用实际上只是间接访问表达式的一种伪装形式,即使在多维数组中也是如此,考虑下面这个表达式:
matrix
它的类型是“指向包含10个整型元素数组的指针”,它的值是
表达式 matrix+1也是一个“指向10个整型元素数组的指针”,但它指向matrix的另一行
为什么?因为1这个数值根据包含10个整型元素的数组的长度进行调制,所以它指向matrix下一行,如果对其执行间接访问,就如下图随箭头选择中间的这个子数组
所以表达式*(matrix+1)
事实上标识了一个10个整型元素的子数组。数组名的值是个常量指针,它指向数组的第一个元素,在这个表达式中也是如此。它的类型是“指向整型的指针”,我们现在可以在下一维的上下文环境中显示它的值。
现在请拿稳你的帽子,猜猜下面这个表达式的结果是什么?
*(*(matrix+1)+5)
它所访问的正是那个整型元素。如果它作为右值使用,你取得存储于那个位置的值,如果它作为左值使用,这个位置将存储一个新值。
这个看上去吓人的表达式实际上正是我们的老朋友–下标,我们可以把表达式*(matrix+1)改写成matrix[1],把这个下标表达式带入原先的表达式,我们将得到:
*(matrix[1]+1);
这个表达式完全合法的,matrix[1]选定一个子数组,所以它的类型是一个指向整型的指针,我们对这个指针加上5,然后执行间接访问操作。
但是,我们可以再次用下标代替间接访问,所以这个表达式还可以写出:
matrix[1][5]
指向数组的指针
下面这些声明合法吗?
int vector[10],*vp = vector;
int matrix[3][10],*mp = matrix;
第一个声明是合法的。它为一个整型数组分配内存,并把vp声明一个指向整型的指针,并把它初始化为指向vector数组的第一个元素。vector和vp具有相同的类型:指向整型的指针。但是第2个是非法的。它正确地创建了matrix数组,并把mp声明为一个指向整型的指针。但是mp的初始化是不正确的,因为matrix并不是一个指向整型的指针,而是一个指向整型数组的指针。我们应该怎样声明一个指向整型数组的指针呢?
int (*p)[10];
下标引用的优先级高于间接访问,但由于括号的存在,首先执行的还是间接访问。所以p是个指针,但它指向什么呢?
接下来执行的是下标引用,所以p指向某种类型的数组。这个声明表达式中并没有更多的操作符,所以数组的每个元素都是整数。
声明并没有直接告诉你p是什么,但推断它的类型并不困难,当我们对它执行间接访问操作时,我们得到的是个数组,对该数组进行下标引用操作得到的是一个整型值。所以p是一个指向整型数组的指针。
在声明中加上初始化后是下面这个样子:
int (*p)[10] = matrix;
它使p指向matrix的第一行。
作为函数参数的多维数组
作为函数参数的多维数组名的传递方式和一位数组名相同,实际传递的是个指向数组第一个元素的指针。但是,两者之间的区别在于,多维数组的每个元素本身是另外一个数组,编译器需要知道它的维数,以便为函数形参的下标表达式进行求值。这里有两个例子,说明了它们之间的区别:
int vector[10];
...
func1(vector);
参数vector得我类型是指向整型的指针,所以func1的原型可以是下面两种的任何一种:
void funcl(int *vec);
void func1(int vec[]);
作用于vec上面的指针运算把整型的长度作为它的调整因子。
现在让我们来观察一个矩阵:
int matrix[3][10];
...
func2(matrix);
这里matrix的类型是指向包含10个整型元素数组的指针。func2的原型应该是怎样的呢?你可以使用下面两种形式中的任何一种:
void func2(int (*p)[10];
void func2(int mat[][10]);
在这个函数中,mat的第一个下标根据包含10个元素的整型数组的长度进行调整,接着第2个下标根据整型的长度进行调整,这和原先的matrix数组一样。
在编写一维数组形参的函数原型时,你既可以把它写成数组的形式,也可以把它写成指针的形式。但是,对于多维数组,只有第1维可以进行如此选择。尤其是,把func2写成下面这样的原型是不正确的:
void func2(int**mat);
这个例子把mat声明为一个指向整型指针的指针,它和指向数组的指针并不是一回事。
指针数组
除了类型之外,指针变量和其他变量很相似,正如你可以创建整型数组一样,你也可以声明指针数组。这里有一个例子:
int *api[10];
为了弄清楚这个复杂的声明,我们假设它是一个表达式,并对它进行求值。
下标引用的优先级高于间接访问,所以在这个表达式中,首先执行下标引用。因此,api是某种类型的数组,元素个数为10。在取得一个数组元素之后,随机执行的是间接访问操作,这个表达式不再有其他操作,所以它的结果是一个整型值。
那么api到底是什么东西?对数组的某个元素执行间接访问操作后,我们得到一个整型值,所以api肯定是个数组,它的元素类型是指向整型的指针。
小结
一维数组的数组名指向第一个元素,类型是指向元素类型的指针。
二维数组的数组名是也指向它第一个元素,类型是指向数组的指针。
指针的指针是指向某种类型指针的指针,它和指向数组的指针并不是一回事。