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烦人,无聊在计算机中还是数学中)。
小结:
- a[0]代表一行地址,sizeof(a[0])得到一行的内存大小。&a[0]”缩小“为一个元素的大小。
- 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
C指针与二维数组的更多相关文章
- C:指针遍历二维数组
C 指针遍历二维数组 http://blog.csdn.net/lcxandsfy/article/details/55000033 C++ 字符串指针与字符串数组 https://www.cnblo ...
- 论C语言中二级指针和二维数组之间的区别
刚开始学习C语言的时候,觉得一个数组可以定义一个一级指针去访问,想当然的就觉得可以定义一个二级指针去访问二维数组.很显然这是错误的. 我们来看看C语言的数组在内存中的存储方式. 实际上C语言中的数组, ...
- 20130330 printf数组改变 数组指针便利二维数组 二级指针遍历二维数组 ZigZag
1.为什么printf之后数组的值会改变? #include<stdio.h> ; int * Zigzag() { ,j=,limit=; ; ; int a[N][N]; int (* ...
- c语言,指针与数组--指针与二维数组2
指向一维数组的指针 char (*p)[10] ;指向一维数组的指针类型 typedef char(*TYPE_P2ARRAY)[10] ; 该指针可以指向数组 ,且使用起来效果节本相同, ...
- C语言数组篇(五)多级指针和二维数组指针的区别
多级指针 以二级指针为例 二级指针的由来是 指针数组 的指针形式. int *p[10] 读取的顺序是 p[] --> 10个空间的数组 * p[] --> 这10个空间的数组里面存放 ...
- 唠唠C++二级指针、二维数组、指针数组、数组指针等的区分
今天看c++primer第六章,有这部分的内容,脑子有点糊涂了,看了几篇博客,自己敲了下,记录一下备忘. 二级指针: int **p; 二维数组: int p[10][10]; char q[10][ ...
- C++ 指针与二维数组名
和一维数组类似,C++ 将二维数组名解释为其第一个元素的地址,而二维数组的第一个元素为一维数组,以下面的程序为例,二维数组名 array2d 和 &array2d[0] 等效,它们的类型都为 ...
- C++之指针指向二维数组
一维指针通经常使用指针表示,其指向的地址是数组第一元素所在的内存地址,例如以下 int ary[4][5]; int(*aryp)[5] = ary; 那么ary[4]相当于int(*aryp).下面 ...
- (一)二维数组&&指针数组与数组指针
一.首先我们从字面意思理解一下什么是指针数组什么是数组指针 1.指针数组:本质是一个数组,数组中的每一个元素是一个指针. 2.数组指针:本质是一个指针,而指针指向一个数组. 二.我们该怎么区分指针数组 ...
随机推荐
- HDU 4336 Card Collector(状压 + 概率DP 期望)题解
题意:每包干脆面可能开出卡或者什么都没有,一共n种卡,每种卡每包爆率pi,问收齐n种卡的期望 思路:期望求解公式为:$E(x) = \sum_{i=1}^{k}pi * xi + (1 - \sum_ ...
- Linux 驱动框架---驱动中的阻塞
描述和API 阻塞IO和非阻塞IO的应用编程时的处理机制是不同的,如果是非阻塞IO在访问资源未就绪时就直接返回-EAGAIN,反之阻塞IO则会使当前用户进程睡眠直到资源可用.从应用场景来说两种方式分别 ...
- HTML spaces types: &   &  
HTML spaces types: & & What is the difference between and https://stackoverflow.co ...
- Apache HTTP Server & WS (websockets)
Apache HTTP Server & WS (websockets) Apache HTTP Server Version 2.4 https://httpd.apache.org/doc ...
- PAUL ADAMS ARCHITECT:澳大利亚楼市保持涨势
澳大利亚最新房价变化显示,住宅价格指数连续第10周上涨,包括五个主要首府城市的上涨了0.29%. 12月截至24日,布里斯班以1.03%涨幅领跑,五个首府城市平均涨幅0.78%. 在过去3个月里,悉尼 ...
- RocketMq灰皮书(三)------MQ使用
RocketMq灰皮书(三)------MQ使用 在使用MQ之前,我们回顾一下前两篇博文的内容. 我们大致了解了RocketMQ的四个概念,分别是:Producer,Consumer,Message和 ...
- 一文读懂Servlet
1 Servlet简介 Servlet就是sun公司开发动态web的一门技术 Sun在这些API中提供一个接口叫做:Servlet. 开发一个Servlet程序,只需两步: 编写一个类,实现Servl ...
- Error: Actions must be plain objects. Use custom middleware for async actions.
原本代码: import { SREACH_FOCUS, SREACH_BLUR } from "./actionType" export const searchFocus = ...
- MongoDB语句命令
更新列名 db.xx.update({}, {$rename : {"StoreId" : "MetaId"}}, false, true) 查询长度 db.g ...
- 类关系与uml图示表示
1. 关联(Association).聚合(Aggregation).组合(Composition)区别 association: 两者之间存在某种关联即可,很弱的关系,如student and co ...