二位数组与二维指针
闲话少说,先看一个程序:
//源码: #include #define SIZE(x) sizeof(x)/sizeof(int) #define printaddr(s,x) printf("addr-%s:%x\n",s,x) #define printint(s,x) printf("Int-%s:%d\n",s,x); int main() { printf("Array a:\n"); int a[5] = {1,2,3,4,5}; int *test = &a;//waring Type not match! int (*p_OneDimensional)[5] =&a; int *p_Point = a; int *p_PointAnother = &a[0]; printaddr("a",a); printaddr("a+1",a+1); printaddr("&a[0]+1",&a[0]+1); printaddr("&a+1",&a+1);
printf("\n\n\nArray b:\n"); int b[3][4]={ 1,2,3,3, 4,5,6,6, 7,8,9,9 };
printaddr("b",b); printaddr("*b",*b); printaddr("*b[0]",*b[0]); printaddr("&b+1",&b+1); printaddr("b+1",b+1); printaddr("&b[0]+1",&b[0]+1); printaddr("b[0]+1",b[0]+1); printaddr("&b[0][0]+1",&b[0][0]+1);
printint("\n\n\nSize(*b)",SIZE(*b)); printint("Size(*b[0])",SIZE(*b[0])); printint("Size(&b)",SIZE(&b)); printint("Size(b)",SIZE(b)); printint("Size(&b[0])",SIZE(&b[0])); printint("Size(b[0])",SIZE(b[0])); printint("Size(&b[0][0])",SIZE(&b[0][0]));
int (*p_TB)[3][4] = &b; int (*p_SB)[4] = b;//or &b[0] int *p_PB = b[0];//or &b[0][0]
} |
//输出: Array a: addr-a:bf932450 addr-a+1:bf932454 addr-&a[0]+1:bf932454 addr-&a+1:bf932464
Array b: addr-b:bf932420 addr-*b:bf932420 addr-*b[0]:1 addr-&b+1:bf932450 addr-b+1:bf932430 addr-&b[0]+1:bf932430 addr-b[0]+1:bf932424 addr-&b[0][0]+1:bf932424
Int-Size(*b):4 Int-Size(*b[0]):1 Int-Size(&b):1 Int-Size(b):12 Int-Size(&b[0]):1 Int-Size(b[0]):4 Int-Size(&b[0][0]):1
|
程序中只有int *test = &a;//waring Type not match!会有警告,其他全部正确。警告来自类型不匹配,因为&a的类型是int(*)[5]的。从程序中我们也可以看出对二维数组b,&b是int (*)[3][4]类型,a与&a[0]是
int (*)[4]类型,而a[0]与&a[0][0]是int *类型的。
不过当计算SIZE时,我们会发现a,&a[0]不是等价的,a[0],&a[0][0]也不是等价的,这是为什么呢,那是在sizeof中,二维数组名a和一维数组名a[0]并没有退化。a表示整个二维数组(是一个标量),a[0]也表示整个二维数组的第一行(一个一维数组,也是标量)。所以可以得出结论退化后它们是等价的。
Sizeof()传入标量会计算该类存储空间的大小,传入指针自然是计算指针这个本质上是标量的空间的大小,而不是计算其指向地址空间的大小,传入字面值也计算其对应类型的大小。如sizeof(5);sizeof(&a);等。
下面援引<<c专家编程>>上一段话:
“数组名被改写成一个指针参数”的规则并不是递归定义的,数组的数组会被改写成“数组的指针”,而不是指针的指针。
实参 |
| 匹配形参(指针) |
|
数组的数组 | Char c[5][6] | Char (*c)[6] | 数组指针 |
指针数组 | Char *c[10] | Char **c | 指针的指针 |
数组指针 | Char (*c)[10] | Char (*c)[10] | 不变 |
指针的指针 | Char **c | Char **c | 不变 |
从表中可知:当传数组的数组名c时,其实是传了一个 char (*c)[6]这样的指针,故此时c其实等价于&c[0],是二维数组首行的首地址(注意与首元素首地址含义是不一样的)(同理c+1是第二行首地址)。
注:看数据具体类型还可以在gdb下,其次用g++编译,然后故意复制给错误的对象,如全部赋值给整型,报错中会有类型说明。
下面我们来看看二维数组都有哪些声明方法:先看一个程序
//源码: #include
void OutByIndexAll(int p[3][4]) { printf("the first way to OutPut a By IndexAll:\n"); int i = 0; int j = 0; for (i;i < 3;i++) { printf("Row %d: ",i+1); for (j = 0;j < 4;j++) printf("%d ",p[i][j]); printf("\n"); } printf("\n");
}
void OutByIndexPart(int p[][4],int num) { printf("the second way to OutPut a By IndexPart:\n"); int i = 0; int j = 0; for (i;i < num;i++) { printf("Row %d: ",i+1); for (j = 0;j < 4;j++) printf("%d ",p[i][j]); printf("\n"); } printf("\n");
}
void OutByPToATwo(int (*p)[4],int num) { printf("the second way to OutPut a By pointer to Array:\n"); int count = 1; while(num--) { int j = 0; printf("Row %d: ",count++); for (j = 0;j < 4;j++) printf("%d ",*(*p+j)); printf("\n"); p++; } printf("\n"); }
void OutByPToAOne(int (*p)[4],int num) { printf("the first way to OutPut a By pointer to Array:\n"); int i = 0; int j = 0; for (i;i < num;i++) { printf("Row %d: ",i+1); for (j = 0;j < 4;j++) printf("%d ",p[i][j]); printf("\n"); } printf("\n");
}
void OutByPToInt(int *p,int num) { printf("the way to OutPut a By pointer to Int:\n"); int i = 0; for (i;i < num;i++) { printf("%d ",p[i]); } printf("\n"); }
void OutByPToIntForTest(int *p,int row,int col) //sure error { printf("the way to OutPut a By pointer to Int For test:\n"); int i = 0; int j = 0; for (i;i < row;i++) { printf("Row %d: ",i+1); for (j;j < col;j++) //printf("%d ",p[i][j]);//编译通不过 printf("\n"); } printf("\n"); }
void OutByPToP(int **p,int row,int col) { printf("the way to OutPut a By pointer to pointer:\n"); printf("----the first way to dealwith:----\n"); int all = row*col; int i =0; for (i;i < all;i++) { printf("%d ",p[i]); } printf("\n----the second way to dealwith:----\n"); i = 0; int j = 0; for (i;i < row;i++) { printf("Row %d: ",i+1); for (j;j < col;j++) printf("%d ",p[i][j]); printf("\n"); } printf("\n"); }
int main() {
int (*p)[4]; int *r; int **q; int i = 0; int a[3][4] = { 1,2,3,4, 5,6,7,8, 9,1,2,3 }; printf("\n****OutPut a use array name as argument:****\n"); OutByIndexAll(a); OutByIndexPart(a,3); OutByPToAOne(a,3); OutByPToATwo(a,3); OutByPToInt(a,12); OutByPToInt(a[0],12); //OutByPToP(a,3,4);// printf("a = 0x%x a+1 = 0x%x\n",a,a+1); printf("\n****OutPut a use pointer to array as argument:****\n"); p = a; printf("p = 0x%x p+1 = 0x%x\n",p,p+1); OutByIndexAll(p); OutByIndexPart(p,3); OutByPToAOne(p,3); OutByPToATwo(p,3); OutByPToInt(p,12); OutByPToInt(p[0],12); //OutByPToP(p,3,4);//函数前半部分正常后半部分不行,段错误
printf("\n****OutPut a use pointer to Int as argument:****\n"); r = a; printf("r = 0x%x r+1 = 0x%x\n",r,r+1); OutByIndexAll(r); OutByIndexPart(r,3); OutByPToAOne(r,3); OutByPToATwo(r,3); OutByPToInt(r,12); //OutByPToInt(r[0],12);//r[0] in this is a value not a address! //OutByPToP(r,3,4);//同上 //OutByPToIntForTest(r,3,4);//函数本身错误 printf("\n****OutPut a use pointer to Pointer as argument:****\n"); q = a; printf("q = 0x%x q+1 = 0x%x\n",q,q+1); OutByIndexAll(q); OutByIndexPart(q,3); OutByPToAOne(q,3); OutByPToATwo(q,3); OutByPToInt(q,12); //OutByPToInt(q[0],12);//as above r[0] //OutByPToP(q,3,4);//同上
return 1; } |
输出:
****OutPut a use array name as argument:**** the first way to OutPut a By IndexAll: Row 1: 1 2 3 4 Row 2: 5 6 7 8 Row 3: 9 1 2 3
the second way to OutPut a By IndexPart: Row 1: 1 2 3 4 Row 2: 5 6 7 8 Row 3: 9 1 2 3
the first way to OutPut a By pointer to Array: Row 1: 1 2 3 4 Row 2: 5 6 7 8 Row 3: 9 1 2 3
the second way to OutPut a By pointer to Array: Row 1: 1 2 3 4 Row 2: 5 6 7 8 Row 3: 9 1 2 3
the way to OutPut a By pointer to Int: 1 2 3 4 5 6 7 8 9 1 2 3 the way to OutPut a By pointer to Int: 1 2 3 4 5 6 7 8 9 1 2 3 a = 0xbff88280 a+1 = 0xbff88290
****OutPut a use pointer to array as argument:**** p = 0xbff88280 p+1 = 0xbff88290 the first way to OutPut a By IndexAll: Row 1: 1 2 3 4 Row 2: 5 6 7 8 Row 3: 9 1 2 3
the second way to OutPut a By IndexPart: Row 1: 1 2 3 4 Row 2: 5 6 7 8 Row 3: 9 1 2 3
the first way to OutPut a By pointer to Array: Row 1: 1 2 3 4 Row 2: 5 6 7 8 Row 3: 9 1 2 3
the second way to OutPut a By pointer to Array: Row 1: 1 2 3 4 Row 2: 5 6 7 8 Row 3: 9 1 2 3
the way to OutPut a By pointer to Int: 1 2 3 4 5 6 7 8 9 1 2 3 the way to OutPut a By pointer to Int: 1 2 3 4 5 6 7 8 9 1 2 3
****OutPut a use pointer to Int as argument:**** r = 0xbff88280 r+1 = 0xbff88284 the first way to OutPut a By IndexAll: Row 1: 1 2 3 4 Row 2: 5 6 7 8 Row 3: 9 1 2 3
the second way to OutPut a By IndexPart: Row 1: 1 2 3 4 Row 2: 5 6 7 8 Row 3: 9 1 2 3
the first way to OutPut a By pointer to Array: Row 1: 1 2 3 4 Row 2: 5 6 7 8 Row 3: 9 1 2 3
the second way to OutPut a By pointer to Array: Row 1: 1 2 3 4 Row 2: 5 6 7 8 Row 3: 9 1 2 3
the way to OutPut a By pointer to Int: 1 2 3 4 5 6 7 8 9 1 2 3
****OutPut a use pointer to Pointer as argument:**** q = 0xbff88280 q+1 = 0xbff88284 the first way to OutPut a By IndexAll: Row 1: 1 2 3 4 Row 2: 5 6 7 8 Row 3: 9 1 2 3
the second way to OutPut a By IndexPart: Row 1: 1 2 3 4 Row 2: 5 6 7 8 Row 3: 9 1 2 3
the first way to OutPut a By pointer to Array: Row 1: 1 2 3 4 Row 2: 5 6 7 8 Row 3: 9 1 2 3
the second way to OutPut a By pointer to Array: Row 1: 1 2 3 4 Row 2: 5 6 7 8 Row 3: 9 1 2 3
the way to OutPut a By pointer to Int: 1 2 3 4 5 6 7 8 9 1 2 3
|
printf("a = 0x%x a+1 = 0x%x\n",a,a+1);
输出:a = 0xbff88280 a+1 = 0xbff88290
很容易理解a+1=a+4*4(一行)= 0xbff88280+16=0xbff88290
p = a;
printf("p = 0x%x p+1 = 0x%x\n",p,p+1);
输出:p = 0xbff88280 p+1 = 0xbff88290
很容易理解:p的类型为int (*)[4], p=a没有隐式类型转化。p 指向数组首行首地址(以行为单位)。
r = a;
printf("r = 0x%x r+1 = 0x%x\n",r,r+1);
输出:r = 0xbff88280 r+1 = 0xbff88284
明显r只是指向首元素的首地址(以单个元素为单位)。之所以其它调用成功是因为函数接受形参时也会发生默认形参转换。但此时r[0]表示a[0][0]=1;调用函数是转化是成功了,但检测到这不是系统授权的地址,不可使用,出现段错误。
q = a;
printf("q = 0x%x q+1 = 0x%x\n",q,q+1);
输出:q = 0xbff88280 q+1 = 0xbff88284
和r情形一样,此时q指向数组首元素首地址,部分函数调用成功也是隐士类型转换缘故。q[0]等同于r[0]。(同理q[1] = r[1] 类型不同而已)
OutByPToP()函数为何之前的一半会正确呢,那是因为q指向的是数组首元素的首地址,而那么*q = a[0][0],*(q+1)= a[0][1]……
那么后一半为何会错呢,这是有原因的,这是因为p它本身是个指针可以指向其他内存空间(即p中存储的必须是地址),另外,p中存放的地址所指向的内存空间还必须存放地址,这样对p的二级解引用才是合法的。
怎奈此处发生了隐式的强制类型转换,实际赋予q的地址空间中存的不是地址,故二级解引用出现段错误,如:**q ó*(a[0][0])=*0x00000001,这个过程需要访问内存空间,是需要通过审核,系统说:“等等,我手头的内存分配表并没有查到该地址,你在试图访问非法内存,这是不允许的,年轻人可别想干坏事,我会严格执法,也别想贿赂我哦!”。
我们需要重申一点,我们可以正常使用q的一级解引用,就像使用某个一级指针一样。
注:如果你不怕晦涩死人,你同样可以把二级指针当一级指针使用,只是你永远不需要使用它的二级解引用操作。
从上面的讨论我们貌似可以得出一个结论,我们不可以用二维指针来像矩阵那样操作二维数组(如:int **p = a;p[i][j],或**p++)。 事实并非如此,如果二维数组向下面这样声明:
int *a[4];(或者char *a[4])
二维指针就会有用武之地了。
下面用一个简单的程序演示一下:
源码: #include
int main() { char *p1 = "Hello"; char *p2 = "world"; char *p3 = "people"; char *a[3]={p1,p2,p3}; char **p = a; int i = 0; int j = 0; while (i < 3) { j = 0; while (p[i][j] != '\0') { printf("%c",p[i][j]); j++; } printf(" "); i++; } printf("\n");
return 1; } |
至此告一段落!下面一节我们将讨论文件链接属性!