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.数组指针:本质是一个指针,而指针指向一个数组. 二.我们该怎么区分指针数组 ...
随机推荐
- Kubernetes部署Prometheus+Grafana(非存储持久化方式部署)
1.在master节点处新建一个文件夹,用于保存下载prometheus+granfana的yaml文件 mkdir /root/prometheus cd /root/prometheus git ...
- Leetcode(869)-重新排序得到 2 的幂
从正整数 N 开始,我们按任何顺序(包括原始顺序)将数字重新排序,注意其前导数字不能为零. 如果我们可以通过上述方式得到 2 的幂,返回 true:否则,返回 false. 示例 1: 输入:1 输出 ...
- 云原生系列1 pod基础
POD解决了什么问题? 成组资源调度问题的解决. mesos采用的资源囤积策略容易出现死锁和调度效率低下问题:google采用的乐观调度技术难度非常大: 而k8s使用pod优雅的解决了这个问题. po ...
- Ajax & JSONP 原理
Ajax & JSONP 原理 AJAX不是JavaScript的规范,它只是一个哥们"发明"的缩写:Asynchronous JavaScript and XML,意思就 ...
- 使用 js 实现十大排序算法: 归并排序
使用 js 实现十大排序算法: 归并排序 归并排序 refs js 十大排序算法 All In One https://www.cnblogs.com/xgqfrms/p/13947122.html ...
- Principle for iOS App Animation Design
Principle for iOS App Animation Design Animate Your Ideas, Design Better Apps https://principleforma ...
- how to input special keyboard symbol in macOS(⌘⇧⌃⌥)
how to input special keyboard symbol in macOS(⌘⇧⌃⌥) emoji ctrl + command + space / ⌘⇧⌃ ⌘⇧⌃ Character ...
- wireshark 获取指定进程id的数据
>netstat -aon | findstr 11380 TCP 191.127.1.7:57936 29.225.107.216:3734 ESTABLISHED 11380 过滤器: tc ...
- 法兰西金融专访SPC空投重磅来袭
最近,法兰西金融日报联合德意志财经等知名金融媒体就SPC这一话题进行了专访. 法兰西金融日报记者德维尔斯问到,之前2020年的BGV项目等市场反响异常火爆,2021年已经来到,NGK目前有何新的大动作 ...
- c#初体验
虚方法.抽象类.接口区别:虚方法:父类可能需要实例化,父类方法需要方法体,可以找到一个父类 抽象类:抽象方法,父类不能实例化,且父类方法不能实现方法体,不可以找出一个父类,需要抽象 接口:多继承 le ...