先贴上完整的代码:

  1. #include<stdio.h>
  2. int main(int argc, char *argv[]){
  3. int a[3] [5]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
  4. int (*p)[5];
  5. int i,j;
  6. p=a;
  7.  
  8. //打印各元素地址
  9. for(i=0;i<3;++i){
  10. for(j=0;j<5;++j){
  11. printf("%d ",&a[i][j]);
  12. if(j==4) printf("\n");
  13. }
  14. }
  15. printf("\n");
  16.  
  17. //打印第零行地址
  18. printf("%d \n", a);
  19. printf("%d\n", a+0);
  20. printf("%d\n", a[0]);
  21. printf("%d\n", &a[0]);
  22. printf("%d\n", *(a+0));
  23. printf("\n");
  24.  
  25. //打印第一行地址
  26. printf("%d \n", a+1);
  27. printf("%d\n", a[1]);
  28. printf("%d\n", &a[1]);
  29. printf("%d\n", *(a+1));
  30. printf("\n");
  31.  
  32. //打印第二行地址
  33. printf("%d\n", a+2);
  34. printf("%d\n", a[2]);
  35. printf("%d\n", &a[2]);
  36. printf("%d\n", *(a+2));
  37. printf("\n");
  38.  
  39. //不同方式取第二行第二个元素
  40. printf("%d\n", *(*(a+2+2)));
  41. printf("%d\n");
  42. printf("%d\n", *(a[2]+2));
  43. setbuf(stdout, NULL);
  44. printf("%d\n");
  45. printf("%d\n", (*(&a[2]+2)));
  46. printf("%d\n", *(*(a+2)+2));
  47. printf("\n");
  48.  
  49. //打印sizeof大小
  50. printf("sizeof()\n");
  51. printf("a-->%d\n", sizeof(a));
  52. printf("a+0-->%d\n", sizeof(a+0));
  53. printf("a[0]-->%d\n", sizeof(a[0]));
  54. printf("&a[0]-->%d\n",sizeof(&a[0]));
  55. printf("*(a+0)-->%d\n", sizeof(*(a+0)));
  56. printf("&*(a+0)-->%d\n", sizeof(&*(a+0)));
  57. printf("\n");
  58.  
  59. //测试“放大”和“缩小”功能
  60. printf("%d\n",*(a+1));
  61. printf("%d\n", &*(a+1));
  62. printf("%d\n",*&*(a+1));
  63. printf("\n");
  64.  
  65. //测试不同的转换
  66. printf("p-->%d\n", sizeof(p));
  67. printf("sizeof()\n");
  68. printf("a+2-->%d\n", sizeof(a+2));
  69. for(p=a; p<= a+2; ++p){
  70. for(i=0;i<5;++i)
  71. {
  72. printf("%d\t", *(*p+i));
  73. if(i==4)
  74. printf("\n");
  75. }
  76. }
  77. printf("\n");
  78.  
  79. printf("sizeof()\n");
  80. printf("a[2]-->%d\n", sizeof(a[2]));
  81. for(p=a; p<= a[2]; ++p){//cast
  82. for(i=0;i<5;++i)
  83. {
  84. printf("%d\t", *(*p+i));
  85. if(i==4)
  86. printf("\n");
  87. }
  88. }
  89. printf("\n");
  90. printf("sizeof()\n");
  91. printf("&a[2]-->%d\n", sizeof(&a[2]));
  92. for(p=a; p<= &a[2]; ++p){
  93. for(i=0;i<5;++i)
  94. {
  95. printf("%d\t", *(*p+i));
  96. if(i==4)
  97. printf("\n");
  98. }
  99. }
  100. printf("\n");
  101.  
  102. printf("sizeof()\n");
  103. printf("*(a+2)-->%d\n", sizeof(*(a+2)));
  104. for(p=a; p<= *(a+2); ++p){//cast
  105. for(i=0;i<5;++i)
  106. {
  107. printf("%d\t", *(*p+i));
  108. if(i==4)
  109. printf("\n");
  110. }
  111. }
  112. return 0;
  113. }

下面是运行的结果:

  1. 2293368  2293372  2293376  2293380  2293384
  2. 2293388  2293392  2293396  2293400  2293404
  3. 2293408  2293412  2293416  2293420  2293424
  4.  
  5. 2293368
  6. 2293368
  7. 2293368
  8. 2293368
  9. 2293368
  10.  
  11. 2293388
  12. 2293388
  13. 2293388
  14. 2293388
  15.  
  16. 2293408
  17. 2293408
  18. 2293408
  19. 2293408
  20.  
  21. 2293640
  22. 2293640
  23. 13
  24. 0
  25. 2293448
  26. 13
  27.  
  28. sizeof()
  29. a-->60
  30. a+0-->4
  31. a[0]-->20
  32. &a[0]-->4
  33. *(a+0)-->20
  34. &*(a+0)-->4
  35.  
  36. 2293388
  37. 2293388
  38. 2293388
  39.  
  40. p-->4
  41. sizeof()
  42. a+2-->4
  43. 1       2       3       4       5
  44. 6       7       8       9       10
  45. 11      12      13      14      15
  46.  
  47. sizeof()
  48. a[2]-->20
  49. 1       2       3       4       5
  50. 6       7       8       9       10
  51. 11      12      13      14      15
  52.  
  53. sizeof()
  54. &a[2]-->4
  55. 1       2       3       4       5
  56. 6       7       8       9       10
  57. 11      12      13      14      15
  58.  
  59. sizeof()
  60. *(a+2)-->20
  61. 1       2       3       4       5
  62. 6       7       8       9       10
  63. 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,则肯定是行不通的。记住:指针的类型是什么!

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

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

后面的:

  1. a+0 a[0] *(a+0) &a[0] a
  2. a+1 a[1] *(a+1) &a[1]
  3. a+2 a[2] *(a+) &a[2]

表明,这些值都是相等的,代表的是每一行元素首地址。

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

  1. printf("%d \n", a);
  2. printf("%d\n", a+0);
  3. printf("%d\n", a[0]);
  4. printf("%d\n", &a[0]);
  5. printf("%d\n", *(a+0));
  6. printf("\n");
  7.  
  8. printf("%d \n", a+1);
  9. printf("%d\n", a[1]);
  10. printf("%d\n", &a[1]);
  11. printf("%d\n", *(a+1));
  12. printf("\n");
  13.  
  14. printf("%d\n", a+2);
  15. printf("%d\n", a[2]);
  16. printf("%d\n", &a[2]);
  17. printf("%d\n", *(a+2));
  18. printf("\n");
  1. 2293368
  2. 2293368
  3. 2293368
  4. 2293368
  5. 2293368
  6.  
  7. 2293388
  8. 2293388
  9. 2293388
  10. 2293388
  11.  
  12. 2293408
  13. 2293408
  14. 2293408
  15. 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()操作。得到:

  1. a[0]-->20
  2. &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操作:

  1. a+0-->4
  2. *(a+0)-->20

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

  1. printf("*(a+0)-->%d\n", sizeof(*(a+0)));返回20
  2. 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):

  1. printf("%d\n", *(*(a+2+2))); 2293640
  2. printf("%d\n"); 2293640
  3. printf("%d\n", *(a[2]+2)); 13
  4. setbuf(stdout, NULL);
  5. printf("%d\n"); 0
  6. printf("%d\n", (*(&a[2]+2))); 2293448
  7. printf("%d\n", *(*(a+2)+2)); 13
  1.  

首先是*(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了呢?)。

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

下面测试上面所说的:

代码开始定义了:

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

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

  1. for(p=a; p<= a+2; ++p)
  1. for(p=a; p<= a[2]; ++p)//cast
  1. for(p=a; p<= &a[2]; ++p)
  1. 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指针与二维数组的更多相关文章

  1. C:指针遍历二维数组

    C 指针遍历二维数组 http://blog.csdn.net/lcxandsfy/article/details/55000033 C++ 字符串指针与字符串数组 https://www.cnblo ...

  2. 论C语言中二级指针和二维数组之间的区别

    刚开始学习C语言的时候,觉得一个数组可以定义一个一级指针去访问,想当然的就觉得可以定义一个二级指针去访问二维数组.很显然这是错误的. 我们来看看C语言的数组在内存中的存储方式. 实际上C语言中的数组, ...

  3. 20130330 printf数组改变 数组指针便利二维数组 二级指针遍历二维数组 ZigZag

    1.为什么printf之后数组的值会改变? #include<stdio.h> ; int * Zigzag() { ,j=,limit=; ; ; int a[N][N]; int (* ...

  4. c语言,指针与数组--指针与二维数组2

    指向一维数组的指针   char (*p)[10] ;指向一维数组的指针类型 typedef  char(*TYPE_P2ARRAY)[10]  ;   该指针可以指向数组 ,且使用起来效果节本相同, ...

  5. C语言数组篇(五)多级指针和二维数组指针的区别

    多级指针   以二级指针为例 二级指针的由来是 指针数组 的指针形式. int *p[10] 读取的顺序是 p[] --> 10个空间的数组 * p[] --> 这10个空间的数组里面存放 ...

  6. 唠唠C++二级指针、二维数组、指针数组、数组指针等的区分

    今天看c++primer第六章,有这部分的内容,脑子有点糊涂了,看了几篇博客,自己敲了下,记录一下备忘. 二级指针: int **p; 二维数组: int p[10][10]; char q[10][ ...

  7. C++ 指针与二维数组名

    和一维数组类似,C++ 将二维数组名解释为其第一个元素的地址,而二维数组的第一个元素为一维数组,以下面的程序为例,二维数组名 array2d 和 &array2d[0] 等效,它们的类型都为 ...

  8. C++之指针指向二维数组

    一维指针通经常使用指针表示,其指向的地址是数组第一元素所在的内存地址,例如以下 int ary[4][5]; int(*aryp)[5] = ary; 那么ary[4]相当于int(*aryp).下面 ...

  9. (一)二维数组&&指针数组与数组指针

    一.首先我们从字面意思理解一下什么是指针数组什么是数组指针 1.指针数组:本质是一个数组,数组中的每一个元素是一个指针. 2.数组指针:本质是一个指针,而指针指向一个数组. 二.我们该怎么区分指针数组 ...

随机推荐

  1. Kubernetes部署Prometheus+Grafana(非存储持久化方式部署)

    1.在master节点处新建一个文件夹,用于保存下载prometheus+granfana的yaml文件 mkdir /root/prometheus cd /root/prometheus git ...

  2. Leetcode(869)-重新排序得到 2 的幂

    从正整数 N 开始,我们按任何顺序(包括原始顺序)将数字重新排序,注意其前导数字不能为零. 如果我们可以通过上述方式得到 2 的幂,返回 true:否则,返回 false. 示例 1: 输入:1 输出 ...

  3. 云原生系列1 pod基础

    POD解决了什么问题? 成组资源调度问题的解决. mesos采用的资源囤积策略容易出现死锁和调度效率低下问题:google采用的乐观调度技术难度非常大: 而k8s使用pod优雅的解决了这个问题. po ...

  4. Ajax & JSONP 原理

    Ajax & JSONP 原理 AJAX不是JavaScript的规范,它只是一个哥们"发明"的缩写:Asynchronous JavaScript and XML,意思就 ...

  5. 使用 js 实现十大排序算法: 归并排序

    使用 js 实现十大排序算法: 归并排序 归并排序 refs js 十大排序算法 All In One https://www.cnblogs.com/xgqfrms/p/13947122.html ...

  6. Principle for iOS App Animation Design

    Principle for iOS App Animation Design Animate Your Ideas, Design Better Apps https://principleforma ...

  7. how to input special keyboard symbol in macOS(⌘⇧⌃⌥)

    how to input special keyboard symbol in macOS(⌘⇧⌃⌥) emoji ctrl + command + space / ⌘⇧⌃ ⌘⇧⌃ Character ...

  8. wireshark 获取指定进程id的数据

    >netstat -aon | findstr 11380 TCP 191.127.1.7:57936 29.225.107.216:3734 ESTABLISHED 11380 过滤器: tc ...

  9. 法兰西金融专访SPC空投重磅来袭

    最近,法兰西金融日报联合德意志财经等知名金融媒体就SPC这一话题进行了专访. 法兰西金融日报记者德维尔斯问到,之前2020年的BGV项目等市场反响异常火爆,2021年已经来到,NGK目前有何新的大动作 ...

  10. c#初体验

    虚方法.抽象类.接口区别:虚方法:父类可能需要实例化,父类方法需要方法体,可以找到一个父类 抽象类:抽象方法,父类不能实例化,且父类方法不能实现方法体,不可以找出一个父类,需要抽象 接口:多继承 le ...