在C语言中数组和指针之间存在一些千丝万缕的联系,搞不清楚的情况下非常容易出错,在前一段时间我写过关于数组和指针的分析,但是还是存在很多不清楚的问题,特别是当出现一些复杂的问题时,这种情况更加的复杂。看了很多网友的博客,但是发现一些问题,做一下简要的总结吧。
多维数组的数组名并不是很多网友描述的多级指针,我仅以二维数组作为研究对象,进行一定的分析。
二维数组int A[M][N],可以认为是存在M个元素的数组,且每一个元素都是长度为N的int型数组,这样就能比较清晰的理解了数组。数组名在很多情况下转换为指针,且数组名是数组首个元素的指针,这是非常重要的概念,搞清楚了这个概念也就能够分析其他多维的数组情况啦。
对于上面的数组A[M][N],数组名A实质上指向数组的首个元素的指针,也就是说这时的数组名A变质为一个指针,指向的类型为int[N],即指向的是一个数组,而不是一个简单的值,那么对数组名进行解引用*A,就是得到这个一维数组(这是指针解引用的定义,就如同 int *p = &a, *p 实质上是指a是一个道理),相当于得到了一维数组名,即*A实质上是一个数组,(*A)可以看做这个数组的数组名,这和int a[N]是同样的道理,即:a = *A。同样根据数组名是指向数组的首个元素的指针,可以将*A也可以被看做为指针,但此时的指针指向的是数组A[0]的首个元素,也就是指向了A[0][0],也就是说*(*A)就是A[0][0]。由此可知,二维数组并不是简单的指向指针的指针,而是每一次解引用对应的都是不同的指针类型。
对于多维的数组,假设存在int B[N][M][S]这个三维的数组,那么数组名B是一个指针,其指向的类型是int[M][S]。*B也是指针,但是其指向的类型是int[S]。**B同样也是指针,但是指向的类型是int型。更多维的数组也可以采用类似方法的分析。所以说并不能说是多维数组的数组名多级指针,因为多维数组对数组名的解引用操作都得到了一个不同大小的数组空间的指针,并不是指向单一元素的指针。
由上面的分析可知,二维数组名A是一个指向一维数组的指针,指针的加法是指在当前地址上加上类型的长度,得到指向的新地址,这里的类型就是数组int[N]。因此A+i实质上是指向二维数组的第i个元素,也就是第i个一维数组。对A+i解引用*(A+i)即得到一个数组A[i],*(A+i)就是这个数组的数组名(这样说可能不合适,但是方便理解),同时依据数组名就是指向这个数组的首个元素的地址,可知*(A+i)实质上还是一个指针,但是这个指针指向的是A[i][0]这个元素。
下面总结一下二维数组中的一些结论:
a :指向一维数组的指针。a -> a[0]
*a :得到一维数组a[0],可认为(*a)是一维数组的数组名,而(*a)-> a[0][0]。
*a+j :指向a[0][j]的指针,(*a)可视为一个一维数组名。
a+i :数组a[i]的指针,也就是说a+i->a[i]。
*(a+i): 得到一维数组a[i],其中*(a+i)可以视为数组名。*(a+i)指向了a[i]的首个元素,即:*(a+i)->a[i][0]。
*(a+i)+j :得到数组*(a+i)的第j个元素的指针,即:*(a+i)+j -> a[i][j]。
*(*(a+i)+j) :就是得到a[i][j]。
我之前一直将二维数组看做指向指针的指针,现在想起来是不合适的,假设存在一个指向指针的指针int **p。如果另p = A,通常对p的操作都会导致一些错误,由于p是一个指向指针的指针,那么p++实质上只是增加了4个字节,而A+1则增加了N*sizeof(int)。两者之间并不是等价的关系,因此指向指针的指针并不能表征二维数组。
如果指向指针的指针a和二维数组名是等价的,那么下面的程序肯定能够实现数组元素的打印,但是非常不幸,下面的函数会产生段错误即访问了非法的内存空间。
- int print_array(int **a, int row, int col)
- {
- int i = 0 , j = 0;
- for(i = 0; i < row; ++ i)
- {
- for(j = 0; j < col; ++ j)
- printf("%d\t",a[i][j]);
- printf("\n");
- }
- }
简要的说明一下,为什么会产生段错误吧,由于a是一个指向指针的指针,那么a[i]则是一个指针,他指向一个数据空间,由于数组名实际上对应一个地址,这个地址和&A等都是相同的,a[i]实质上是就是数组中的一个元素,但是其中的内容也是一个指针,这时候再次解引用就很有可能导致内存非法访问,产生段错误。比如A[2][3]={{1,2,3},{4,5,6}}。调用函数print_array(A,2,3)时,由于A实质上是一个地址,由于数组的特殊性,具有&A,A,A+0,&A[0][0]等对应的值是相同的。a = A,实质上a就是一个地址。这个地址就是数组的起始位置。a中的内容实质上就是数组的元素,因此对数组进行一次解引用(*a)得到的实质上就是数组的一个元素,也就是1,但是a是指向指针的指针,内容1也是一个地址,对地址1的访问肯定发生错误,因为地址1处是属于内核,不能直接进行访问,发生了段错误。这也就是为什么说二维数组和指向指针的指针之间并不是等价的。
但是根据上面的分析,我们知道二维数组的数组名指明了数组的位置,这时候直接对位置进行访问就能够得到数组的元素。同时我们知道a++指向的地址恰好就是a+4。刚好也就是数组的下一个元素。因此我们可以采用解一次引用的方式实现数据的访问。因此上面的代码可以修改如下,这种方法是运用了指针加法的一些特性,也就是指向指针的指针的增长大小就是大小等于4,但是这种方法只能针对类型大小为4的数组问题,其他的类型不能运用。
- int print_array(int **a, int row, int col)
- {
- int i = 0 , j = 0;
- for(i = 0; i < row; ++ i)
- {
- for(j = 0; j < col; ++ j)
- printf("%d\t",*(a+i+j));
- printf("\n");
- }
- }
字符串数组是比较常用的,字符串数组定义如下:
- char *str[]={
- "One",
- "Two",
- "Three",
- "Four"
- };
字符串数组允许出现锯齿形的数组,也就是说各个字符串的长度可以是不相同的,我们知道字符串数组实质上是一个指针数组,和二维数组之间存在一定的差别,并不是同一种类型的问题,指针数组可以转换为指向指针的指针的问题。
我们常见的主函数main的两种种定义方式为:
- int main(int argc, char *argv[])
- int main(int argc, char **argv)
这两种定义方式使得很多的初学者认为二维数组和指向指针的指针有一定的联系,特别是第2种写法存在很大的误导性,实质上字符串数组和二维数组之间存在很大的差别,所以不能当做一类问题来讨论。
二维数组作为函数的形参时也是一个应该注意的问题。我们已经知道二维数组不是指向指针的指针这个结论。那么如果需要传递一个二维数组时又该如何处理呢?我们知道数组名是一个指向数组的指针,将参数设置为这种形式即可:
- int print_array(int (*p)[N],int row, int col);
- int print_array(int a[M][N],int row, int col);
上面的两种定义方式是比较常见的,当然也可以采用指向指针的指针这种方式,这种方式一般在动态分配二维数组的情况下使用。这时候的动态分配的二维数组和我们当前讨论的二维数组存在差别,更像是字符串指针数组问题。
- /*C++*/
- void array_create(int **array, int row, int col)
- {
- int i = 0, j = 0;
- array = new int *[row];
- for(i = 0; i < row; ++ i)
- {
- array[i] = new int[col];
- }
- }
其中array首先分配行,然后行中保存了指针变量,这样就实现了二维数组的动态分配,这时候的二维数组不再是线性表,这是需要注意的。在C语言中也可以直接创建线性表,即采用一维数组访问。
阅读(3096) | 评论(0) | 转发(2) |