C 知识量:16 - 74 - 317
多维数组涉及更加复杂的指针,下面通过一个示例来更深入的理解指针的含义。
假设有以下一个二维数组:
int box[3][2];
根据数组和指针的关系,可以推断出以下几点:
因为数组名代表一个指针,因此,数组名box指向该数组首元素的地址。而该数组是一个二维数组,所以,该数组首元素是一个内含两个int值的数组,由此推断出:数组名box指向这个内含两个int值的数组的地址,也就是说,数组名box的值是这个内含两个int值的数组的地址。
因为box[0]就是box数组的首元素,而&box[0]就是box数组首元素的地址,所以,box的值就是&box[0]的值。
box[0]本身也是一个内含两个int值的数组,因此,box[0]的值就是其首元素的地址,也就是说,box[0]的值就是&box[0][0]的值。
解引用一个指针(即在指针前使用*运算符)或在数组名后面使用带下标的[]运算符,可以得到引用对象代表的值。因此,*(box[0])的值就等于*(&box[0][0])的值,即等于box[0][0]的值;*box的值就等于*(&box[0])的值,即等于box[0]的值,而由上一条可知:box[0]的值就是&box[0][0]的值。由此推断出:*box的值就等于&box[0][0]的值,因此,**box的值就是*&box[0][0]的值,即box[0][0]的值。
box指向一个占用两个int大小对象的地址;box[0]指向一个占用一个int大小对象的地址。由于它们都始于同一个地址,所以,box与box[0]的值相同。
给指针加1,其值会增加对应类型大小的数值。由于box与box[0]指向对象的大小不同,所以,即使始于同一个地址,但box+1与box[0]+1的值却不相同。
以下是一个多维数组的示例:
#include <stdio.h> int main(void) { int box[3][2] = { {1, 2}, {3, 4}, {5, 6} }; printf(" box=%p, box+1=%p\n", box, box + 1); printf("box[0]=%p, box[0]+1=%p\n", box[0], box[0] + 1); printf(" *box=%p, *box+1=%p\n", *box, *box + 1); printf("box[0][0]=%d\n", box[0][0]); printf(" *box[0]=%d\n", *box[0]); printf(" **box=%d\n", **box); printf("box[2][1]=%d\n", box[2][1]); printf("*(*(box+2)+1)=%d\n", *(*(box + 2) + 1)); system("pause"); return 0; }
运行的结果为:
box=0061FF18, box+1=0061FF20 box[0]=0061FF18, box[0]+1=0061FF1C *box=0061FF18, *box+1=0061FF1C box[0][0]=1 *box[0]=1 **box=1 box[2][1]=6 *(*(box+2)+1)=6
由以上结果可以清楚的看出:
box与box[0]指向的初始地址都是0061FF18,但是它们指向对象的大小却不同,这导致box+1后移动了8个字节(2个int类型),而box[0]+1后移动了4个字节(1个int类型)。
*box==box[0]
**box==*box[0]==box[0][0]
box[2][1]==*(*(box+2)+1),即使用数组表示法和使用指针表示法的效果是一样的,这也进一步表明C语言中的数组与指针的关系。
如果要创建一个指向多维数组的指针,需要使用类似以下形式的语句:
int (*p)[2];
以上代码声明了一个指针p,它指向一个数组,该数组内含两个int类型的值。之所以使用圆括号是因为[]的优先级高于*。如果不这样做,将代码修改为:
int * p[2];
那么,由于[]的优先级更高,将优先与p结合,p就成为了一个内含两个元素的数组;然后*又表示p数组内含的元素均是一个指针;最后int表示p数组中的指针都是指向int类型值的。整个表达式的含义就变成了:声明了一个内含两个指向int类型值的指针的数组p。
以下是一个指向二维数组的指针的示例:
#include <stdio.h> int main(void) { int box[3][2] = { {1, 2}, {3, 4}, {5, 6} }; int (*p)[2]; //指向二维数组的指针p p = box; printf(" p=%p, p+1=%p\n", p, p + 1); printf("p[0]=%p, p[0]+1=%p\n", p[0], p[0] + 1); printf(" *p=%p, *p+1=%p\n", *p, *p + 1); printf("p[0][0]=%d\n", p[0][0]); printf(" *p[0]=%d\n", *p[0]); printf(" **p=%d\n", **p); printf("p[2][1]=%d\n", p[2][1]); printf("*(*(p+2)+1)=%d\n", *(*(p + 2) + 1)); system("pause"); return 0; }
运行结果为:
p=0061FF14, p+1=0061FF1C p[0]=0061FF14, p[0]+1=0061FF18 *p=0061FF14, *p+1=0061FF18 p[0][0]=1 *p[0]=1 **p=1 p[2][1]=6 *(*(p+2)+1)=6
以上示例与上一节的示例很相似,但是通过指向二维数组的指针来引用和打印数组内容。虽然p是指针,不是数组名,但是也可以使用p[2][1]这样的写法。总之,可以使用数组表示法或指针表示法来表示一个数组元素,box[2][1]与p[2][1]代表的含义是相同的。
指针之间的赋值比数值类型之间的赋值要严格。例如:不用类型转换就可以将int类型的值赋给double类型的变量,但是两种不同类型的指针不能这样做。以下是一些简单的示范:
int n = 100; double m; int * p1 = &n; //允许,类型一致。 double * p2 = &m; //允许,类型一致。 m = n; //允许,隐式类型转换。 p2 = p1; //不允许,指针类型不同,编译会出错。
涉及数组的更复杂的指针示范如下:
int box23[2][3] = { {1, 2, 3}, {4, 5, 6} }; int box32[3][2] = { {1, 2}, {3, 4}, {5, 6} }; int * p1; int ** p2; int (*p3)[3]; p1 = &box23[0][0]; //允许,都是指向int的指针。 p1 = box32[0]; //允许,都是指向int的指针。 p1 = box23; //不允许,p1指向int;box23指向一个内含3个int元素的数组。 p3 = box23; //允许,都指向一个内含3个int元素的数组。 p3 = box32; /*不允许,p3指向一个内含3个int元素的数组; box32指向一个内含2个int元素的数组。*/ p2 = &p1; //允许,都是指向int的指针的指针。 *p2 = box32[0]; //允许,都是指向int的指针。 p2 = box32; /*不允许,p2是指向int的指针的指针; box32是指向一个内含2个int元素的数组(指针)的指针。*/
除了以上赋值规则外,把const指针赋给非const指针是不安全的,因为这样就可以使用新的指针改变const指针指向的数据。但是反过来,把非const指针赋给const指针是安全的,不过也有前提,那就是只进行一级解引用。而如果进行两级解引用,那么这样的赋值也不安全。例如:
const int **pp; int *p; const int n = 100; pp = &p; //允许,非const赋给const,但pp的const限定符失效,因为可以使用p修改pp指向内容。 *pp = &n; //允许,都是const,但是导致p指向n,pp指向内容被修改了。 *p = 200; //允许,但是改变了n的值,n的const限定符也失效了。
二维数组可以视为一个包含行和列的表格,在处理二维数组的函数中,要正确处理行和列的信息,就需要借助设置正确的函数形参来实现。例如对于box[2][3]来说,可以这样声明函数的形参:
void somefunction(int (*p)[3]);
如果p只用于表示一个形参时,也可以这样声明:
void somefunction(int p[][3]);
空的方括号表明p是一个指针。以下是一个使用函数处理二维数组的完整的示例:
#include <stdio.h> #define ROWS 2 #define COLS 3 void sum_r(int box[][COLS], int rows); //声明计算行元素的和的函数 void sum_c(int [][COLS], int); //声明计算列元素的和的函数 int sum_both(int(*p)[COLS], int rows); //声明计算所有元素和的函数 int main(void) { int box[ROWS][COLS] = { {1, 2, 3}, {4, 5, 6} }; sum_r(box, ROWS); sum_c(box, ROWS); printf("Sum of all elements = %d\n", sum_both(box, ROWS)); system("pause"); return 0; } //定义计算行元素的和的函数 void sum_r(int box[][COLS], int rows) { int r; int c; int tot; for (r = 0; r < rows; r++) { tot = 0; for (c = 0; c < COLS; c++) tot += box[r][c]; printf("row %d: sum = %d\n", r, tot); } } //定义计算列元素的和的函数 void sum_c(int box[][COLS], int rows) { int r; int c; int tot; for (c = 0; c < COLS; c++) { tot = 0; for (r = 0; r < rows; r++) tot += box[r][c]; printf("col %d: sum = %d\n", c, tot); } } //定义计算所有元素和的函数 int sum_both(int box[][COLS], int rows) { int r; int c; int tot = 0; for (r = 0; r < rows; r++) for (c = 0; c < COLS; c++) tot += box[r][c]; return tot; }
运行结果为:
row 0: sum = 6 row 1: sum = 15 col 0: sum = 5 col 1: sum = 7 col 2: sum = 9 Sum of all elements = 21
需要注意,下面声明多维数组的方式是错误的:
int sum(int box[][], int rows);
编译器会把数组表示法转换成指针表示法,对于box[1],编译器会转换成box+1。但是转换的过程中,编译器需要知道box所指向对象的大小,box[][3]中的“3”就是表明box指向一个内含3个int类型值的数组,box+1需要在box指向地址上加上12个字节(一个int类型占4个字节,4*3=12),而如果第2个[]内是空的,编译器就不知道指针指向对象的大小,也就不能正确完成转换。
通常,声明一个指向N维数组的指针时,只能省略最左边方括号中的值,例如声明一个5维数组:
int sum(int box[][2][3][4][5], int rows);
声明时,第1对方括号只用于表明box是一个指针,其他方括号用于描述指针指向对象的类型。如果在第1对方括号内填写了数值,当然也是可以的,只不过编译器会忽略它。
Copyright © 2017-Now pnotes.cn. All Rights Reserved.
编程学习笔记 保留所有权利
MARK:3.0.0.20240214.P35
From 2017.2.6