指针和多维数组有什么关系?为何咱们须要知道它们之间的关系?函数是经过指针来处理多维数组的,所以在使用这样的函数以前,您须要更多的了解指针。假设有以下的声明:c++
int zippo[4][2] ; /*整数数组的数组*/数组
数组名zippo同时也是数组首元素的地址。在本例中,zippo的首元素自己又是包含两个int的数组,所以zippo也是包含两个int的数组的地址。下面从指针的属性进一步分析:安全
**由于zippo是数组首元素的地址,因此zippo的值和&zippo[0]相同。另外一方面,zippo[0]自己是包含两个整数的数组,所以zippo[0]的值同其首元素的地址&zippo[0][0]相同。简单地说,zippo[0]是一个整数大小对象的地址,而zippo是两个整数大小对象的地址。由于整数和两个整数组成的数组开始于同一个地址,所以zippo和zippo[0]具备相同的值 。函数
**对一个指针加1会对原来的数值加上一个对应类型大小的数值。在这方面zippo和zippo[0]是不同的,zippo所指向对象的大小是两个int,而zippo[0]所指向对象的大小是一个int。所以zippo+1和zippo[0]+1的结果不一样。spa
**对一个指针取值获得的是该指针所指向对象的数值。由于zippo[0]是其首元素zippo[0][0]的地址,因此*(zippo[0])表明存储在zippo[0][0]中的数值,即一个int数值。一样*zippo表明其首元素zippo[0]的值,可是zippo[0]自己就是一个int数的地址,即&zippo[0][0],所以*zippo是&zippo[0][0]。对这个表达式同时应用取值运算符将获得**zippo等价于*&zippo[0][0],后者简化后即为一个int数zippo[0][0]。简言之,zippo是地址的地址,须要两次取值才能够获得一般的数值。地址的地址或指针的指针是双重间接的典型例子。指针
显然,增长数组维度会增长指针的复杂度。code
程序清单10.15对象
/*zippo1.c --有关zippo的信息*/ #include <stdio.h> int main(void) { int zippo[4][2]={{2,4},{6,8},{1,3},{5,7}}; printf("zippo = %p, zippo+1 = %p\n", zippo, zippo+1); printf("zippo[0] = %p, zippo[0]+1 = %p\n", zippo[0], zippo[0]+1); printf("*zippo = %p, *zippo+1 = %p\n", *zippo, *zippo+1); printf("zippo[0][0] = %d\n",zippo[0][0]); printf(" *zippo[0] = %d\n",*zippo[0]); printf(" **zippo = %d\n",**zippo); printf(" zippo[2][1] = %d\n",zippo[2][1]); printf("*(*(zippo+2)+1)=%d\n",*(*(zippo+2)+1)); return 0; }
在一个系统上输出结果以下ip
zippo = 0022FF20, zippo+1 = 0022FF28 zippo[0] = 0022FF20, zippo[0]+1 = 0022FF24 *zippo = 0022FF20, *zippo+1 = 0022FF24 zippo[0][0] = 2 *zippo[0] = 2 **zippo = 2 zippo[2][1] = 3 *(*(zippo+2)+1)=3
输出显示出二维数组zippo的地址和一维数组zippo[0]的地址是相同的,均为相应的数组首元素的地址,它的值是和&zippo[0][0]相同的。ci
然而,差异也是有的,在咱们系统上,int是4个字节长。前面咱们讨论过,zippo[0]指向4字节长的数据对象,对zippo[0]加1致使它的值增长4。数组名zippo是包含两个int数的数组的地址,所以它指向8字节长的数据对象。因此,对zippo加1致使它的值增长8。
程序显示*zippo和zippo[0]是相同的,这点是正确的。另外一方面,二维数组名必须再次取值才能取出数组中存储的数据。
具体地:zippo[2][1]的等价指针符号表示为*(*(zippo+2)+1)。表10.2中分步创建了这个表达式:
分析*(*(zippo+2)+1)
zippo | 第1个大小 为2个int的元素的地址 |
zippo+2 | 第3个大小为2个int的元素的地址 |
*(zippo+2) | 第3个元素,即包含2个int值的数组,所以也是其第1个元素(int值)的地址 |
*(zippo+2) +1 | 包含2个Int值的数组的第2个元素(int值)的地址 |
*(*(zippo+2) +1) | 数组第3行第2个int的值(zippo[2][1]) |
当您正好有一个指向二维数组的指针并须要取值时,最好不要使用指针符号,而应当使用形式简单的数组符号。
10.7.1 指向多维数组的指针
如何声明指向二维数组的指针变量pz?例如,在编写处理像zippo这样的数组的函数时,就会用到这类指针。指向int的指针能够胜任吗?不能够。这种指针只是和zippo[0]兼容。由于它们都指向一个单个的int值。可是zippo是其首元素的地址,而该首元素又是包含两个int值的数组。所以,pz必须指向一个包含两个int值的数组,而不是指向一个单个的int值。下面是正确的代码:
int (*pz) [2] ; //pz指向一个包含2个int值的数组
该语句代表pz是指向包含2个int值的数组的指针。为何使用圆括号?由于表达式中[]的优先级高于*。所以,若是咱们这样声明:
int * pax[2] ;
那么首先[]与pax结合,表示pax是包含两个某种元素的数组。而后和*结合,表示pax是两个指针组成的数组。最后,用int来定义,表示pax是由两个指向int值的指针构成的数组。这种声明会建立两个指向单个Int值的指针。程序清单10.16显示了如何使用指向二维数组的指针。
程序清单10.16 zippo2.c
/*zippo2.c --经过一个指针变量获取有关zippo的信息*/ #include <stdio.h> int main(void) { int zippo[4][2]={{2,4},{6,8},{1,3},{5,7}}; int (*pz)[2]; pz=zippo; printf(" pz = %p, pz+1 = %p\n", pz, pz+1); printf(" pz[0] = %p,pz[0]+1 = %p\n", pz[0], pz[0]+1); printf(" *pz = %p,*pz+1 = %p\n", *pz, *pz+1); printf("pz[0][0] = %d\n",pz[0][0]); printf(" *pz[0] = %d\n",*pz[0]); printf(" **pz = %d\n",**pz); printf(" pz[2][1] = %d\n",pz[2][1]); printf("*(*(pz+2)+1) = %d\n",*(*(pz+2)+1)); return 0; }
输出结果以下:
pz = 0022FF1C, pz+1 = 0022FF24 pz[0] = 0022FF1C,pz[0]+1 = 0022FF20 *pz = 0022FF1C,*pz+1 = 0022FF20 pz[0][0] = 2 *pz[0] = 2 **pz = 2 pz[2][1] = 3 *(*(pz+2)+1) = 3
不一样的计算机获得的结果可能有些差异,可是相互关系是同样的。尽管pz是一个指针,而不是数组名,仍然可使用pz[2][1]这样的符号。
更通常地,要表示单个元素,可使用数组符号或指针符号:而且在这两种表示中便可以使用数组名也可使用指针:
zippo[m][n] == *(*(zippo+m)+n)
pz[m][n] == *(*(pz+m)+n)
10.7.2 指针兼容性
指针之间的赋值规则比数值类型的赋值更严格。例如,您能够不须要进行类型转换就直接把一个Int数值赋给一个double类型的变量。但对于指针来讲这样的赋值是不容许的。
这些规则也适用于更复杂的类型。假设有以下声明:
int * pt; int (*pa)[3]; int ar1[2][3]; int ar2[3][2]; int **px; //指针的指针
那么,有以下结论:
pt = &ar1[0][0]; //都指向 int pt = ar1[0]; //都指向 int pt = ar1; //非法 pa = ar1; //都指向int [3] pa = ar2; //非法 p2 = &pt //都指向int * *p2 = ar2[0] //都指向int p2 = ar2; //非法
请注意,上面的非法赋值都包含着两个不指向同一类型的指针。例如,Pt指向一个int数值,可是ar1是指向由3个int值构成的数组。一样,pa指向由3个int值构成的数组,所以它与ar1的类型一致,可是和ar2的类型不一致,由于ar2指向由2个int值构成的数组。
后面的两个例子比较费解。变量p2是指向int的指针的指针,然而,ar2是指向由2个int值构成的数组的指针(简单一些说是指向int[2]的指针)。所以,p2和ar2的类型不一样,不能把ar2的值赋给p2。可是*p2的类型是指向int的指针,因此它和ar2[0]是兼容的。前面讲过,ar2[0]是指向其首元素ar2[0][0]的指针,所以ar2[0]也是指向int的指针。
通常地,多重间接运算不容易理解。例如,考虑下面这段代码:
int *p1; const int *p2; const int **p2; p1=p2; //非法,把const指针赋给非const指针 p2=p1; //合法,把非const指针赋给const指针 pp2=&p1;//非法,把非const指针赋给const指针
正如前面所提到的,把const指针赋给非const指针是错误的,由于您可能会使用新指针来改变const数据。可是把非const指针赋给const指针是容许的。这们的赋值有一个前提:只进行一层间接运算:
p2=p1; //合法,把非const指针赋给const指针
在进行两层间接运算时,这样的赋值再也不安全。若是容许这样赋值,可能会产生以下的问题:
const int **pp2; int *p1; const int n=13; pp2=&p1; //不容许,咱们假设容许 *pp2=&n; //合法,二者都是const,但同时会使p1指向n *p1=10; //合法,但这将改变const n的值
10.7.3 函数和多维数组
若是须要编写一个处理二维数组的函数,首先须要很好的理解指针以便正确声明函数的参数。在函数体内一般可使用数组符号来避免使用指针 。
下面咱们编写一个处理二维数组的函数,一种方法是把处理一维数组的函数应用到二维数组的每一行上,也就是以下所示这样处理:
int junk[3][4]={{2,4,5,8},{3,5,6,9},{12,10,8,6}}; int i,j; int total=0; for(i=0;i<3;i++) total+=sum(junk[i],4); //junk[i]是一维数组
若是junk是二维数组,junk[i]就是一维数组,能够把它看做是二维数组的一行。函数sum()计算二维数组每行的和,而后由for循环把这些和加起来获得“总和”。
然而,使用这种方法得不到行列信息。要具备行列信息,须要恰当地声明形参变量以便于函数可以正确的传递数组。在本例中,数组junk是3行4列的int数组。如前面所讨论的,这代表junk是指向由4个int值构成的数组的指针。声明此类函数参量的方法以下所示:
void somefunction(int (*pt) [4]) ;
当且仅当pt是函数的形式参量时,也能够做以下的声明:
void somefunction(int pt[][4]) ;
注意到第一对方括号是空的。这个空的方括号表示pt是一个指针,这种变量的使用方法和junk同样。程序清单10.17中的例子就将使用上面两种声明的方法。注意清单中展现了原型语法的3种等价形式。
//array2d.c --处理二维数组的函数*/ #include <stdio.h> #define ROWS 3 #define COLS 4 void sum_rows(int ar[][COLS],int rows); void sum_cols(int [][COLS],int); //能够省略名称 int sum2d(int (*ar)[COLS],int rows); //另外一种语法形式 int main(void) { int junk[ROWS][COLS]={ {2,4,6,8}, {3,5,7,9}, {12,10,8,6} }; sum_rows(junk,ROWS); sum_cols(junk,ROWS); printf("Sum of all elements = %d\n",sum2d(junk,ROWS)); return 0; } void sum_rows(int ar[][COLS],int rows) { int r ; int c ; int tot ; for (r=0;r<rows;r++) { tot=0; for(c=0;c<COLS;c++) tot+=ar[r][c]; printf("row %d: sum = %d\n",r,tot); } } void sum_cols(int ar[][COLS],int rows) { int r ; int c; int tot; for (c=0;c<COLS;c++) { tot=0; for (r=0;r<rows;r++) tot+=ar[r][c]; printf("col %d: sum = %d\n",c,tot); } } int sum2d(int ar[][COLS],int rows) { int r; int c; int tot=0; for(r=0;r<rows;r++) { for (c=0;c<COLS;c++) tot+=ar[r][c]; } return tot; }
这个函数能够在多种状况下工做,例如,若是把12做为行数传递给函数,则它能够处理12行4列的数组。这是由于rows是元素的数目;然而,每一个元素都是一个数组,或者看做一行,rows也就能够看做是行数。
请注意ar的使用方式同mian()中junk的使用方式同样。这是由于ar和junk是同一类型,它们都是指向包含4个int值的数组的指针。
请注意下面的声明是不正确的:int sum2(int ar[][],int rows); //错误的声明
回忆一下,编译器会把数组符号转换成指针符号。这就意味着,ar[1]会被转换成ar+1。编译器这样转换的时候,须要知道ar所指向对象的数据大小。下面的声明:
int sum2 (int ar[][4] , int rows ) ; //合法
就表示ar指向由4个Int值构成的数组,也就是16个字节长(本系统上)的对象,因此ar+1表示“在这个地址上加上16个字节大小”。若是是空括号,编译器将不能正确处理。
也能够像下面这样,在另外一对方括号中填写大小,但编译器将忽略之:
int sum2 ( int ar [3][4] ,int rows ) ; //合法,但3将被忽略
通常地,声明N维数组的指针时,除了最左边的方括号能够留空以外,其余都须要填写数值。
int sum4d (int ar [][12][20][30],int rows) ;
这是由于首方括号表示这是一个指针,而其余方括号描述的是所指向对象的数据类型。请参看下面的等效原型表示:
int sum4d ( int (*ar) [12][20][30] , int rows); //ar是一个指针
此处ar指向一个12x20x30的int数组。