您的位置:首页 > 其它

二维数组、数组指针、指针数组和指针的指针分析(另数组的内存分配方式)

2014-03-10 10:50 204 查看
在这里一共有四个概念:二维数组、数组指针、指针数组和指针的指针。

下面我们一个个来进行分析。

二维数组:

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


这个概念应该都很清楚,用起来也都很顺手,a表示二维数组名,同时也是一个指向a[0][0]地址的指针,改地址不可变,也可以理解成a是一个const指针,也就是a指向的地址是不能改变的,但是a指向地址的值是可以被更改的。a[0]表示一维数组名,也是指向a[0][0]地址的指针,同a。

虽然a和a[0]本质上相同,都是指向a[0][0]的指针,但是在编译器看来他们是不一样的,一个是二维数组名,一个是一维数组名。所以看下面例子:

cout<<a<<endl;
cout<<a[0]<<endl;
cout<<*a<<endl;
cout<<*a[0]<<endl;
我们会发现前三个输出值都是一样的,也就是说其实a=*a,类型不同操作不同。

谈谈二维数组在内存上的存放:

其实二维数组在内存空间里是一维数组形式出现的,按行来摆放,先放入a[0]这个一维数组,放完后继续存放a[1]这个一维数组然后a[2]。如下图所示:



接下来介绍一下数组名的使用:

int *p;
// error, p和a的类型不同,不能将a赋值给p;
p = a;
// correct, 将a进行强制转换,因为我们已经知道内存上的存储方式,所以不会出错;
p = (int *)a;
// correct, a[0][0]是一个int类型,所以对其取地址并赋给p是可行的;
p = &a[0][0];
// correct, 和a不同,a[1]是指向一维数组的指针,在编译器看来是可以进行转换的;
p = a[1];
数组指针:

数组指针本质上是指针,该指针指向的是二维数组的首地址,也就是说数组指针相当于行指针。

int (*p)[3];
// correct, p为行指针,指向维数为3的行指针;
p = a;
int b[3][4];
// error, 这里的b虽然是二维数组的指针,但是编译器下可以转换为维数为4的行指针;
// 所以和p的定义大小不同,所以不能将b复制给p;
p = b;


在这里谈一下内置类型,像int,char等都是内置类型,其实我们也可以将数组看作是一种内置的数据结构,它为数组设立了属于它的解引用*、下标[]、+1等操作。

所以在上述的p=b的赋值是行不通的,因为p和b的类型不符合,p是指向维数为3的行指针,而b只能转换成维数为4的行指针。看下面的例子。

int (*p)[3];
// error, 这里的a[0]是一维数组第一个元素的指针,不是行指针;
p = a[0];
// correct, 通过强制类型转换赋值给p;
p = (int (*)[3])a[0];
cout<<a[0]<<endl;
cout<<p<<endl;
cout<<*a[0]<<endl;
cout<<*p<<endl;
cout的四个结果分别是:
0030F818

0030F818

1

0030F818

其实,储存在a[0]和p里的地址都是一样的,那为什么最后两个解引用*的效果不一样呢。这就是因为不同的类型有属于自己的解引用操作,该操作并不是都是取值,在数组指针(行指针)里,编译器为其定义的解引用是取出行指针指向的一维数组的第一个元素的地址,也就是说数组指针的解引用得到的值依然是一个地址,所以cout<<p结果和cout<<*p是一样的。同样,若p+1,则指向的地址是a[1][0]而不是a[0][1]。

so,记住一点:不同的数据结构不同的操作。

指针数组:

这个概念比较容易理解,它的本质是数组,数组储存的数据是指针,也就是说指针数组是储存多个指针的数组,换种说法,这个数组的类型是int *而不是int,在这里把int *当做和int不一样的类型看待。

指针的指针:

指针的指针也比较容易理解,简单来说指针指向的地址也是储存另一个指针的地址,所以要取值的话需要两次解引用。

总结:

要记住不同的数据类型有不同的操作,对象如此,指针也是如此,要理解内存上的储存情况来更好的了解对不同类型的操作,以及强制转换的可能性。

另:数组的内存分配方式:

定义了数组之后,编译器会将其放入所属的栈中,这又是为什么数组下标需要使用const或者常量,根据栈的操作规则,栈是一片连续的内存空间,每个入栈的元素都是连续的,如果数组下标不是常量的话可能会导致栈为数组预留的空间不够使得覆盖掉其他数据,程序也会因此崩溃。

类似于类指针和类引用的静态类型和动态类型,内存分配也存在静态分配和动态分配之分,像数组属于静态分配,而使用new则是动态分配,区别在于静态分配是在编译器完成编译时已经分配好内存空间了,而动态分配则是程序在运行的过程中动态根据需求进行内存的分配,这一特点也说明了为什么数组下标需要使用常量进行声明。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: