数组与指针(二)

对于数组,我们最为头痛的莫过于数组与指针的关系。在我看来,想要把数组和指针的关系理解通透,关键在于理解指针的关联类型(每个指针都有一个与之关联的数据类型)。因为文章的篇幅比较长,所以分成了两篇,这是第二篇(没看第一篇的可以先看这里哦,磨刀不误砍柴工,大家要有点耐心 ^ ^ )http://www.cnblogs.com/CatDrinkMilk/p/4331355.html

二维数组与指针

在C++中,其实并没有真正意义上的多维数组,所有的多维数组其本质都是数组的数组(这句话很拗口,大家要多想几遍)。比如,二维数组是一维数组的数组,也就是说这个数组里面每个元素都是一个一维数组。按照这样的概念类推下去,我们就能定义n维数组了。就比如:

1 int a[2][3] = { {1, 2, 3}, {4,5, 6} };  

对这段代码,我们可以这样看:

1 int (a[2])[3] = { {1, 2, 3}, {4,5, 6} };  

也就是说,a是一个含有两个元素的数组(a[2]),而每个元素的类型是包含3个int类型的数组,即int[3],如图01所示。

用上面的这种概念,我们就可以理解如何用二级指针创建一个二维数组了,代码如下所示:

1 int **ary = new int*[2];
2 int v = 1;
3 for(int i = 0; i < 2; i++){
4         ary[i] = new int[3];
5         for(int j = 0; j < 3; j++){
6                  ary[i][j] = v++;
7         }
8 }  

对于这段代码,完全可以用图01的那种方式去理解,比较正确的图示如图02所示(从右往左会比较好理解)。

对于二维数组的一些基本操作在这里就不一一解释了,我们直接开始进入我们的主题。按照一维数组与一级指针的思路,我们看如下代码:

1 int a[2][3] = { {1, 2, 3}, {4, 5, 6} };  

和一维数组一样,二维数组变量名是数组中第一个元素的地址,也就是一个一维数组的地址。在这里,为了后面更好的解释,我要说明下,二维数组在内存中的存储也是占一片连续的内存空间,而且是以高维优先的方式存放的,如图03所示。

对于很多C++新手来说,很容易就以为既然一维数组可以用一级指针来操作,那么二维数组也能用二级指针来操作。其实,这是一种很错误的想法。如果读者能够用我在一维数组中的原理解释原因的话,那么恭喜你,你对数组与指针的理解已经很透彻了。当然,解释不了你就乖乖看完下面吧   ^ ^

我们来看下这段代码,一段错误的代码:

1 int a[2][3] = { {1, 2, 3}, {4, 5, 6} };
2 int **pp;
3 pp = a;  

在这段代码中,出现了上述所说的问题。首先,对于二维数组a,我们都知道,它是数组中第一个元素的地址,也就是一个指针(数组名即代表一个变量也代表一个指针)。而这个指针的关联类型就是一个长度为3的整数数组,即int[3](a是一个包含两个int[3]的数组,因此第一个元素的类型为int[3])。而对于二级指针变量pp,它存储的是关联类型int*的指针。

在C++中,int*和int[3]可是不一样的哦!你可以这样想,int*可以是一个保存某个特定的int类型整数的地址的指针变量,也可以是一个保存不同长度int类型整数(一维)数组的指针变量;但是,int[3]只能是一个长度为3的整数数组。

因此,把一个关联类型为int[3]的指针a赋值给一个存储的关联类型为int*的二级指针变量pp是错误的!正确的赋值方式如下:

1 int a[2][3] = { {1, 2, 3}, {4, 5, 6} };
2 int (*pp)[3];
3 pp = a;  

说到这里,我们得提一下,很多C++的书籍都会提到可以用一级指针去操作二维数组,我们用上面的思路也可以很好地去理解。例如,对于下面这段代码:

1 int a[2][3] = { {1, 2, 3}, {4, 5, 6} };
2 int *p;
3 for(p = a[0]; p < a[0]+6; p++){
4        cout<<*p<<" ";
5 } 

这段代码的输出是:1 2 3 4 5 6

对于这段代码,合理的解释如下:

首先,对于一级指针p,它存储的是关联类型为int的指针,这点很明显。其次,a[0]是二维数组中的第一个元素,也就是一个长度为3的整数数组,即int[3]。那么有趣的过程来了,a[0]代表的是什么呢?

用一维数组中提到的思路,a[0]其实就是一维数组int[3]的数组名,就是说,a[0]是一维数组int[3]中第一个元素的地址,也就是一个关联类型为int的指针!因此,将a[0]赋值给一级指针是完全正确的。

最后,为什么输出的结果是整个二维数组的数据呢,即为什么用一级指针能遍历二维数组呢?

在一维数组与一级指针中,我提到过指针的加减运算法则:指针的加减运算是与指针的关联类型的字节数相关的,+1代表向前移动一个关联类型的字节数,-1代表向后移动一个关联类型的字节数。p存储的是关联类型为int的指针,因此p++每次都移动一个int的字节数,即移动到下一个int。而二维数组是存储在一片连续的内存空间中的,如此便能遍历整个数组。

我们做一个延伸:在上述代码中,a+1代表什么?与a[0]+1是否一样?

其实,提出这个问题,我是想再次强调一下,a是一个指针,且指针的加减运算与它的关联类型相关。因此,因为a的关联类型是int[3],所以a+1的含义是移动3个int的字节数,即指向了a[1](移动到数组中的第二个元素)。而a[0]的关联类型是int,因此a[0]+1的含义是移动一个int的字节数,也就是指向了a[0][1](移动到二维数组中第一个元素的第一个元素...拗口 = =)。可参考图04。

对于二维数组,我们最后要解决的就是对于下面的理解:

  • &a的含义
  • &a[0]的含义
  • &a[0][0]的含义

正如我在一维数组与一级指针中提到的,取址运算符&返回的是一个对象的地址,即一个关联类型为该对象的数据类型的指针。

用这个思维,我们就能解决上面的三个含义问题。

对于&a,因为a是一个二维数组int[2][3],因此它的数据类型就是int[2][3],&a返回的是一个关联类型为int[2][3]的指针,所以要赋值给一个存储关联类型为int[2][3]的指针的指针变量。如下所示:

1 int (*p0)[2][3] = &a;

对于&a[0],因为a[0]是一个一维数组int[3],所以a[0]的数据类型为int[3],&a[0]返回的是一个关联类型为int[3]的指针,所以要赋值给一个存储关联类型为int[3]的指针的指针变量。如下所示:

1 int (*p1)[3] = &a[0];

对于&a[0][0],因为a[0][0]是一个int类型整数,&a[0][0]返回的是一个关联类型为int的指针,所以要赋值给一个存储关联类型为int的指针的指针变量。如下所示:

1 int *p3 = &a[0][0];       

结尾

其实写到这里,对于数组和指针的关系,我已经解释完了。对于更高维度的数组和指针的关系,只要稍微用上面的思维迭代以下就能解释得很清楚的。

我所参考的资料主要有三本书:

  • 《C++程序设计基础(第四版)》(想必华工软院的学生会很熟悉吧)
  • 《C++ Primer(第四版)》
  • 《C++ Primer Plus(第六版)》

对于数组和指针的理解,是我根据自己学到的知识和编程经验,并根据上述三本书中提到的原理总结出来的,所以如果各位读者有更好的理解和解释,或者我的解释有问题,都请务必告知我,在这里就先谢过了 ^ ^

最后的最后,感谢我的第一位读者小亚和第二位读者志伟~

 尊重别人的知识成果,转载/引用请注明出处~

By Milk

2014/3/12

时间: 2024-10-06 21:45:55

数组与指针(二)的相关文章

关于数组以为指针二维指针的应用举例

事实上,计算机系统的多维数组其实最终还是以一维数组的形式实现的.就N x M的二维数组来讲,设其数组名为array.指针array指向一个数组,该数组存放的是一系列指针,这些指针分别指向相应的一维数组,而这些数组中存放的才是我们的数据. 由此array 是第i个指针变量地址,array[j]则表示相对于第i个指针变量偏移j*sizeof(数组类型).系统通过这种机制访问了该二维数组的第i行,第j列的内容.有上述可知,指向二维数组的指针其实是指向“指针变量地址”的指针变量.所以在声明指向二维数组的

浅谈C中的数组和指针(二)

原文转载地址:http://www.cnblogs.com/dolphin0520/archive/2011/11/09/2242419.html 在原文基础上增加自己的理解作为修改 浅谈C/C++中的指针和数组(二) 前面已经讨论了指针和数组的一些区别,然而在某些情况下,指针和数组是等同的,下面讨论一下什么时候指针和数组是相同的. C语言标准对此作了说明: 规则1:表达式中的数组名被编译器当做一个指向该数组第一个元素的指针: 注:下面几种情况例外 1)数组名作为sizeof的操作数 2)使用&

二维数组与指针

二维数组: int / char / flaot a[n][m]; 可以看做是将一维数组做为基本类型产生的一维数组的数组类型,共n*m个最基本类型.这样看有许多优点(实际上计算机也是这样分配的). 二维数组数组名的注意事项: 1 #include <stdio.h> 2 int main() 3 { 4 int *p,a[3][4]; 5 p = a;//a是二维数组的首地址本质为行指针,原型为 a[][]:无法赋值给普通指针类型 *p: 6 return 0; 7 } 编译就会出现如下错误

程序猿之--C语言细节13(二维数组和指针,&amp;*a[i][0]的理解,数组1[e]和e[1]很可能你没见过)

主要内容:二维数组和指针,&*a[i][0]的理解.数组1[e]和e[1] #include <stdio.h> #define NUM_ROWS 10 #define NUM_COLS 10 int main(int argc, char **argv) {     int a[NUM_ROWS][NUM_COLS], *p, i = 0; // a理解为指向整数指针的指针 即int **     int c, d=2,*test, e[2] = {4,5},f[2][2] = {{

一维数组,二维数组,三维数组,数组与指针,结构体数组,通过改变指针类型改变访问数组的方式

 打印数组中的每个元素,打印每个元素的地址: #include <stdio.h> #include <stdlib.h> void main(void) { int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; for (int *p = a; p < a + 10;p++)  //指针类型决定4个字节 { printf("\n%p,%d", p, *p); } getchar(); } 指针数组 #inclu

C语言 二维数组与指针笔记

今天分析了C语言二维数组和指针的基本理解,感觉有点懵...代码记录一下,如果有大神临幸发现哪里有误,欢迎指正~~~ #include <stdio.h> #include <stdlib.h> #include <string.h> //void func(int p[][]) //这样写等同于void func(int **p) p++移动了四个字节,(*p)++移动了四个字节,不符合二维数组规律 //{ //} //列优先输出的函数(即竖着输出) void func

直观理解C语言中指向一位数组与二维数组的指针

一维数组和指针: 对于一位数组和指针是很好理解的: 一维数组名: 对于这样的一维数组:int a[5];  a作为数组名就是我们数组的首地址, a是一个地址常量 . 首先说说常量和变量的关系, 对于变量来说, 用箱子去比喻再好不过了, 声明一个变量就声明一个箱子,比如我们开辟出一个苹果类型的箱子, 给这个变量赋值就是把盛放苹果的箱子中放入一个实实在在的苹果, 这就是变量的赋值.  而对于数组来说, 就是一组类型相同的箱子中,一组苹果箱子, 可以放入不同的苹果. 一维数组空间: 变量被声明后, 我

关于二维数组和指针

在C语言中有如下结论: 一.在表达式中数组名都是其首元素的地址 但有两种情况除外: (1)当数组名作为sizeof操作符的操作数时,sizeof返回的是整个数组长度,而不是指向数组的指针长度 (2)当数组名作为&操作符的操作数时,所产生的是一个指向数组的指针,而不是一个指向指针常量的指针 二.在任何变量的前面加&,就是取这个变量的地址. 三.举例: 若有二维数组:a[x][y],则有: (1)a=&a[0] (2)a[0]=&a[0][0]=*a (3)&a=整个二

【C语言学习】指针再学习(二)之数组的指针

★一维数组 一个整型数据为4个字节.4个字节就是32位,即可以表示2^32个数字 在程序中定义一个数组a[5] = {1,2,3,4,5}; 那么a[0]的地址就是0x00000000,数组名a是数组首元素的地址,a的地址也是0x00000000.a+1则表示的地址是0x00000004,而不是0x00000001.因为1这个值,根据前面的指针a所指向的类型的长度来调整自己的长度.也就是说如果a是指向整型的指针,那么后面加的1也表示4个字节,如果a是指向字符型的指针,那么后面加的1表示1个字节.