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