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

posted on 2015-03-12 01:22  程序员的猫  阅读(183)  评论(0编辑  收藏  举报