文章目录
- [1. 指向多维数组的数组名](#1. 指向多维数组的数组名)
- [2. 指向多维数组的指针](#2. 指向多维数组的指针)
- [3. 作为函数参数的多维数组](#3. 作为函数参数的多维数组)
1. 指向多维数组的数组名
我们知道一维数组名的值是一个指针常量 ,它的类型是"指向元素类型的指针",它指向数组的第1个元素。那么多维数组的数组名代表什么呢?
其实也差不多简单。唯一的区别是多维数组第1维的元素实际上是另一个数组。例如,下面这个声明:
cpp
int matrix[3][10];
matrix
数组可以看作是一个一维数组,包含3个元素,只是每个元素恰好是包含10个整型元素的数组 。matrix
这个名字的值是一个指向它第1个元素的指针,所以matrix是一个指向一个包含10个整型元素的数组的指针。
那么下面各个表达式是什么意思呢?如果你能正确说出来,说明你对多维数组的数组名已经了如指掌了~
matrix + 1
这也是一个"指向包含10个整型元素的数组的指针",但它指向matrix
的另一行:
为什么?因为1这个值根据包含10个整型元素的数组的长度进行调整,所以它指向matrix的下一行。
*(matrix + 1)
如果对其执行间接访问操作,就如下图随箭头选择中间这个子数组:
事实上它标识了一个包含10个整型元素的子数组。数组名的值是个常量指针,它指向数组的第1个元素 ,在这个表达式中也是如此。它的类型是"指向整型的指针"。我们现在可以在下一维的上下文环境中显示它的值:
注意间接访问后还是一个指针,间接访问前是一个指向一维整型数组的指针,间接访问后是一个指向整型的指针。
*( matrix + 1 ) + 5
前一个表达式是个指向整型值的指针,所以5这个值根据整型的长度进行调整。整个表达式的结果是一个指针,它指向的位置比原先那个表达式所指向的位置向后移动了5个整型元素。
*( *( matrix + 1 ) + 5 )
对其执行间接访问操作,它所访问的正是图中的那个整型元素。如果它作为右值使用,你就取
得存储于那个位置的值。如果它作为左值使用,这个位置将存储一个新值。
*( matrix[1] + 5 )
这个看上去吓人的表达式实际上正是我们的老朋友------下标。我们可以把子表达式matrix[1]
改写为*(matrix + 1)
。
这个表达式是完全合法的。matrix[1]
选定一个子数组,所以它的类型是一个指向整型的指针。我们对这个指针加上5,然后执行间接访问操作。
matrix[1][5]
这个就是我们最常见的表达形式了。用下标代替间接访问,其含义和4、5一样。
2. 指向多维数组的指针
下面这些声明合法吗?
cpp
int vector[10], *vp = vector;
int matrix[3][10], *mp = matrix;
1个声明是合法的。它为一个整型数组分配内存,并把vp声明为一个指向整型的指针,并把它初始化为指向vector数组的第1个元素。vector和vp具有相同的类型:指向整型的指针。但是,第2个声明是非法的。它正确地创建了matrix数组,并把mp声明为一个指向整型的指针。但是,
mp的初始化是不正确的,因为matrix并不是一个指向整型的指针,而是一个指向整型数组的指针。我们应该怎样声明一个指向整型数组的指针的呢?
cpp
int (*p)[10];
下标引用的优先级高于间接访问,但由于括号的存在,首先执行的还是间接访问。所以,p是个指针,但它指向什么呢?接下来执行的是下标引用,所以p指向某种类型的数组。这个声明表达式中并没有更多的操作符,所以数组的每个元素都是整数。
我们对它执行间接访问操作时,我们得到的是个数组,对该数组进行下标引用操作得到的是一个整型值。所以p是一个指向整型数组的指针。
c
int (*p)[10] = matrix;
它使p指向matrix的第1行。p是一个指向拥有10个整型元素的数组的指针。当你把p与一个整数
相加时,该整数值首先根据10个整型值的长度进行调整,然后再执行加法。所以我们可以使用这个指针一行一行地在matrix中移动。
不要想当然地认为一维数组的数组名是
int *
,二维数组是int **
,两者还是有明显差别的。
如果需要一个指针逐个访问整型元素而不是逐行在数组中移动,应该怎么办呢?下面两个声明都创建了一个简单的整型指针,并以两种不同的方式进行初始化,指向matrix的第1个整型元素。
cpp
int *pi = &matrix[0][0];
cpp
int *pi = matrix[0];
增加这个指针的值使它指向下一个整型元素。
如果你打算在指针上执行任何指针运算,应该避免这种类型的声明:
cppint (*p)[] = matrix;
p仍然是一个指向整型数组的指针,但数组的长度却不见了 。当某个整数与这种类型的指针执行指针运算时,它的值将根据空数组的长度进行调整(也就是说,与零相乘),这很可能不是你所设想的 。有些编译器可以捕捉到这类错误,但有些编译器却不能。所以不要在一个指向未指定长度的数组的指针上执行指针运算。
3. 作为函数参数的多维数组
作为函数参数的多维数组名的传递方式和一维数组名相同------实际传递的是个指向数组第1个元素的指针。但是,两者之间的区别在于,多维数组的每个元素本身是另外一个数组,编译器需要知道它的维数,以便为函数形参的下标表达式进行求值。
- 一维数组:
c
int vector[10];
...
func1(vector);
参数vector的类型是指向整型的指针,所以func1的原型可以是下面两种中的任何一种:
cpp
void func1( int *vec );
void func1(int vec[] );
作用于vec上面的指针运算把整型的长度作为它的调整因子。
- 多维数组:
c
int matrix[3][10];
...
func2( matrix );
参数matrix的类型是指向包含10个整型元素的数组的指针。func2的原型应该是怎样的呢?
c
void func2( int (*mat)[10] );
void func2( int mat[][10] );
在这个函数中,mat的第1个下标根据包含10个元素的整型数组的长度进行调整,接着第2个下标根据整型的长度进行调整,这和原先的matrix数组一样。
这里的关键在于编译器必须知道第2个及以后各维的长度才能对各下标进行求值,因此在原型中必须声明这些维的长度。第1维的长度并不需要,因为在计算下标值时用不到它。
在编写一维数组形参的函数原型时,你既可以把它写成数组的形式,也可以把它写成指针的形式。但是,对于多维数组,只有第1维可以进行如此选择。尤其是,把func2写成下面这样的原型是不正确的:
cpp
void func2( int **mat );
这个例子把mat声明为一个指向整型指针的指针,它和指向整型数组的指针并不是一回事。
参考
- 《C和指针》