C指针与二维数组

先贴上完整的代码:

#include<stdio.h>
int main(int argc, char *argv[]){
	int a[3] [5]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
	int (*p)[5];
	int i,j;
	p=a;
	
	//打印各元素地址 
	for(i=0;i<3;++i){
		for(j=0;j<5;++j){
			printf("%d  ",&a[i][j]);
			if(j==4) printf("\n");
		}
	}
	printf("\n");
	
	//打印第零行地址 
	printf("%d \n", a);
	printf("%d\n", a+0);
	printf("%d\n", a[0]);
	printf("%d\n", &a[0]);
	printf("%d\n", *(a+0));
	printf("\n");
	
	//打印第一行地址 
	printf("%d \n", a+1);
	printf("%d\n", a[1]);
	printf("%d\n", &a[1]);
	printf("%d\n", *(a+1));
	printf("\n");

	//打印第二行地址 
	printf("%d\n", a+2);
	printf("%d\n", a[2]);
	printf("%d\n", &a[2]);
	printf("%d\n", *(a+2));
	printf("\n");
	
	//不同方式取第二行第二个元素 
	printf("%d\n", *(*(a+2+2)));
	printf("%d\n");
	printf("%d\n", *(a[2]+2));
	setbuf(stdout, NULL);
	printf("%d\n");
	printf("%d\n", (*(&a[2]+2)));
	printf("%d\n", *(*(a+2)+2));
	printf("\n");

	//打印sizeof大小 
	printf("sizeof()\n");
	printf("a-->%d\n", sizeof(a));
	printf("a+0-->%d\n", sizeof(a+0));
	printf("a[0]-->%d\n", sizeof(a[0]));
	printf("&a[0]-->%d\n",sizeof(&a[0]));
	printf("*(a+0)-->%d\n", sizeof(*(a+0)));
	printf("&*(a+0)-->%d\n", sizeof(&*(a+0)));
	printf("\n");
	
	//测试“放大”和“缩小”功能 
	printf("%d\n",*(a+1));
	printf("%d\n", &*(a+1));
	printf("%d\n",*&*(a+1));
	printf("\n");
	
	//测试不同的转换 
	printf("p-->%d\n", sizeof(p));
	printf("sizeof()\n");
	printf("a+2-->%d\n", sizeof(a+2));
	for(p=a; p<= a+2; ++p){
		for(i=0;i<5;++i)
		{
			printf("%d\t", *(*p+i));
		if(i==4)
			printf("\n");
		}
	}
	printf("\n");
	
	printf("sizeof()\n");
	printf("a[2]-->%d\n", sizeof(a[2]));
	for(p=a; p<= a[2]; ++p){//cast
		for(i=0;i<5;++i)
		{
			printf("%d\t", *(*p+i));
		if(i==4)
			printf("\n");
		}
	}
	printf("\n");
	printf("sizeof()\n");
	printf("&a[2]-->%d\n", sizeof(&a[2]));
	for(p=a; p<= &a[2]; ++p){
		for(i=0;i<5;++i)
		{
			printf("%d\t", *(*p+i));
		if(i==4)
			printf("\n");
		}
	}
	printf("\n");

	printf("sizeof()\n");
	printf("*(a+2)-->%d\n", sizeof(*(a+2)));
	for(p=a; p<= *(a+2); ++p){//cast
		for(i=0;i<5;++i)
		{
			printf("%d\t", *(*p+i));
		if(i==4)
			printf("\n");
		}
	}
	return 0;
}


下面是运行的结果:

2293368  2293372  2293376  2293380  2293384
2293388  2293392  2293396  2293400  2293404
2293408  2293412  2293416  2293420  2293424


2293368
2293368
2293368
2293368
2293368


2293388
2293388
2293388
2293388


2293408
2293408
2293408
2293408


2293640
2293640
13
0
2293448
13


sizeof()
a-->60
a+0-->4
a[0]-->20
&a[0]-->4
*(a+0)-->20
&*(a+0)-->4


2293388
2293388
2293388


p-->4
sizeof()
a+2-->4
1       2       3       4       5
6       7       8       9       10
11      12      13      14      15


sizeof()
a[2]-->20
1       2       3       4       5
6       7       8       9       10
11      12      13      14      15


sizeof()
&a[2]-->4
1       2       3       4       5
6       7       8       9       10
11      12      13      14      15


sizeof()
*(a+2)-->20
1       2       3       4       5
6       7       8       9       10
11      12      13      14      15

文章开始定义了一个int型二维数组a[3][5],并赋予1~15初值。然后定义了一个指针p( int (*p)[5] ),将 p=a。在讲解之前先从语法和语义分析一下定义一个指针是什么意思。比如有int *p,说明p是一个指针,指向一个int。但单从p的类型上来说,p是int*型,从而sizeof(p)时,其结果一个是4(通常情况下)。回到int (*p)[5],p是一个指针,指向一个有5个int元素的数组。但p的类型是什么呢?用sizeof(p)来算,其值也是4,即p的型应该也是int *。只不过其语义表示为“指向一个包含5个int的”指针。再比如指向函数的指针q,int (*q)(int),q虽然是一个指针,但指针的类型是“接受一个int参数,并返回int的指针”,虽然都是指针,但是语义上是有分别的,如果q=p,则肯定是行不通的。记住:指针的类型是什么!

2293368  2293372  2293376  2293380  2293384	a+0	a[0]	*(a+0)	&a[0]	a
2293388  2293392  2293396  2293400  2293404	a+1	a[1]	*(a+1)	&a[1]
2293408  2293412  2293416  2293420  2293424	a+2	a[2]	*(a+2)	&a[2]

回到本文。开始计算了15个int元素的地址。

后面的:

a+0	a[0]	*(a+0)	&a[0]	a
a+1	a[1]	*(a+1)	&a[1]
a+2	a[2]	*(a+)	&a[2]
表明,这些值都是相等的,代表的是每一行元素首地址。

具体的代码片段为(详细代码在上面):

	
	printf("%d \n", a);
	printf("%d\n", a+0);
	printf("%d\n", a[0]);
	printf("%d\n", &a[0]);
	printf("%d\n", *(a+0));
	printf("\n");
	
	printf("%d \n", a+1);
	printf("%d\n", a[1]);
	printf("%d\n", &a[1]);
	printf("%d\n", *(a+1));
	printf("\n");

	printf("%d\n", a+2);
	printf("%d\n", a[2]);
	printf("%d\n", &a[2]);
	printf("%d\n", *(a+2));
	printf("\n");
	
2293368
2293368
2293368
2293368
2293368

2293388
2293388
2293388
2293388

2293408
2293408
2293408
2293408
之所以把a也拿出来,因为a有点特殊,这个后面讨论。
可见。去一个二维数组每一行的首地址,有四种方式(第0行多出一种方式)。以本文第3行为例,有 a+2,a[2], &a[2]和 *(a+2)这四种方式。我们知道,数组的名称可以当作数组首元素的地址,而C语言中只有一维数组,但是其元素可以是任何类型,包括数组类型,这样就模拟了二维数组。

拿a[3][5]为例,a是一个3元数组,每一个元素是包含5个int的数组。 先从最简单看,如果int b[3]的话,b[0]代表着第0个元素,而a[0]呢,则代表着第0个元素(记住,这个元素是一个包含5个int的数组)的地址。从“语义”来说,a[0]表示着“我是有5个int的数组”的地址。对于&a[0],&理解为取地址操作,则&a[0]从“语义”上变成取“5个int数组的第一个元素”的地址,从而a[0]=&a[0]。虽然它俩相等,但是语义上有区别,对它俩进行sizeof()操作。得到:

a[0]-->20
&a[0]-->4
“语义”上来说,a[0]代表着一个“有5个int数组”的地址(如果把地址理解为指针,则该指针的类型是“指向5个int型的数组”的指针),但是“我的其他信息(就是指针的类型)说明,我其实是指向有5个int的数组”,从而编译器在计算sizeof(a[0])是得到20(字节)。sizeof(&a[0])取的是第一个元素的地址(如果把该地址理解成指针,则指针的类型则为指向“一个int"型),可以理解成“指向5个元素的第一个元素”,得到4个字节。
下面来看看a+0和*(a+0),a是首行的地址,则a+0肯定也是首行的地址咯,毕竟+0没有产生变化,但其实从“语义”上来看,已经发生了变化,只不过变化的不是它的值(表示地址)。从上面代码中可以看到,a+0和*(a+0)的确是首行的地址。a+0表示首行地址很容易理解,但是*(a+0)为什么也是首行地址呢?我们对这两个进行sizeof操作:

a+0-->4
*(a+0)-->20
从上面可以看到,这两个所代表的内存大小不一致。*(a+0)代表的是一行的大小,而a+0则只是一行的首元素的大小。即,虽然它们都是表示一个地址,但是“语义”上是有区别的。那有没有规律呢?我们知道 *和&某种情况下可以理解为“互逆”操作,取地址,取地址表示的内存值。而sizeof(&a[0])表示的是一行中一个元素的大小,sizeof(*(a+0))则是一行中所有元素的大小。在这里,可以理解&为“缩小”,理解*为“放大”。如果对*(a+0)再&一次会如何呢?

	printf("*(a+0)-->%d\n", sizeof(*(a+0)));返回20
	printf("&*(a+0)-->%d\n", sizeof(&*(a+0)));返回 4

从上面来看,将&和*理解为”缩小“和”放大“还是挺有意思的。

刚才刻意避开某一行的首元素,这里还必须讲。a+0表示首元素的地址,但是sizeof(a+0)的值为4,即第0行第0个元素的大小。如果对sizeof(a+1),sizeof(a+2),也可以得到4。即a+0,a+1和a+2,存储的是每一行首元素的地址(也是改行的地址),但语义上来说,其表示的都是某个元素,而非整行。

现在来看一直被忽略的a,a存储的肯定是首行(首个元素)的地址,但是sizeof(a)返回的是60,即二维数组a[3][5]所有元素的内存大小。即”地址仍然是同一个地址“,但是”语义“已经发生了变化了。a+0呢?语义再一次发生变化,只表示首元素的大小。(0真TD烦人,无聊在计算机中还是数学中)。

小结:

  1. a[0]代表一行地址,sizeof(a[0])得到一行的内存大小。&a[0]”缩小“为一个元素的大小。
  2. a+0代表一行地址,sizeof(a+0)得到一个元素的内存大小。*(a+0)“放大”为一行元素大小。

-------------------------------------------------------------分割线--------------------------------------------------------------------------------------------------------------------


既然地址搞清楚了,那该怎么取地址表示的内存中的值呢?取地址有有 a+2,a[2], &a[2]和 *(a+2)这四种方式,那分别从这四个方面来入手,看看取到的值如何。这里想取第2行(从0行开始),第2个元素(为13):

	printf("%d\n", *(*(a+2+2)));	2293640
	printf("%d\n");			2293640
	printf("%d\n", *(a[2]+2));	13
	setbuf(stdout, NULL);		
	printf("%d\n");			0
	printf("%d\n", (*(&a[2]+2)));	2293448
	printf("%d\n", *(*(a+2)+2));	13



首先是*(a+2+2),可以看到,如果单纯的在地址上面+2,是取不到13这个值的。这个很明显,超出数组内存区域了。*(a[2]+2)可以正确的取到,从“语义”上来看,a[2]表示第二行的地址,也即第二行首地址,其大小表示为整行的大小。a[2]+2可以理解为,在本行大小范围内(在a[2]语义中,本行代表这一个数组,故是所有元素的大小),取第2个(从0开始)元素地址。再用*就可以得到值了。而&a[2]+2则得不到正确的值,为什么呢?&a[2]表示的是某个元素的大小,其“语义”仅仅表示一个元素的内存大小。&a[2]+2会超出范围,故不会成功取值。*(a+2)表示一行地址,其“语义”是“我是一个数组,可以表示所有元素大小”,故对*(a+2)+2,可以正确取到它的值。这里有个小插曲,用printf("%d\n"),会把上一行的值打印出来,这个值在缓冲区中,下次打印的仍然是上一次的值。用setbuf(stdout,NULL)清空缓冲区,再打印,原来的值没了,输出为0(是不是代表缓冲区内存值被初始化为0了呢?)。

------------------------------------------------------------------------------------分割线-------------------------------------------------------------------------------------------------

下面测试上面所说的:

代码开始定义了:

int (*p)[5];
sizeof(p)p-->4

可以看到。虽然p是指向包含5个int的数组,但是sizeof(p)得出4,表示p的内存大小 。这个结论下面要用到。

for(p=a; p<= a+2; ++p)
for(p=a; p<= a[2]; ++p)//cast
for(p=a; p<= &a[2]; ++p)
for(p=a; p<= *(a+2); ++p)//cast
在尝试输出数组的值时,我们用了上面四中不同的办法,都可以编译通过,但是第2个和第4个会有警告“[Warning] comparison of distinct pointer types lacks a cast [enabled by default]”,提示“不同的指针类型(pointers types)缺少转换(cast),默认会转换。上面提到,sizeof(p)得到4,但是sizeof(a[2])和sizeof(*(a+2))得到的是20(这里很明显可以看到指针与数组地址的差别,通常情况下,指针都代表着一块32位地址,而“数组地址”则不一定),这说明,指针的类型不同。在这四个a+2,a[2], &a[2]和 *(a+2)中,虽然地址都一样,但是指针类型(pointers types)不一样,在通过它们四个直接取元素值时,有的可以用,有的会出错。

附件:

这是一份不错的指针学习材料,供参考:http://pan.baidu.com/s/1dDs0P7j









posted @ 2014-08-26 12:53  徐小鱼  阅读(256)  评论(0编辑  收藏  举报