1 一维数组的内存模型
一维数组说开了就是一组连续的数值,这组数值存储在一段连续的内存空间之中,只是不一样的是,若是我们不定义数组我们来访问这一连串的数值需要n个变量对应这n个数值或是需要一个指针进行遍历。只是现在我们为这一组值起了个名字而已,以后我们便可以通过这个名字+下标来访问这一组值了。
如我们定义了一个数组:
char a[] = “hello world”;
它的大小为sizeof a = 12(包含一个’/0’)
如果我们不想包含这个’/0’,可以改用下面的方式初始化:
char a[] = {'h','e','l','l','o','w','o','r','l','d'};
这样数组的大小就变成了sizeof a = 10;去掉了一个空格和’/0’;
下面我们用程序探索一下它的内存模型:
#include "stdio.h"
void main()
{
char a[] = {'h','e','l','l','o','w','o','r','l','d'};
printf("addr of &a:%#x/n",&a);
printf("addr of a:%#x/n",a);
printf("addr of a[0]:%#x/n",&a[0]);
printf("addr of a[1]:%#x/n",&a[1]);
printf("addr of &a+1:%#x/n",&a+1);
printf("addr of a+1:%#x/n",a+1);
}
输出结果为:
好的,我们来分析一下结果:
在分析之前我们先去理解一些这几个符号所代表的含义:
&a: - 代表整个数组的地址
a: 代表第一个元素的地址
&a[0]: 代表第一个元素的地址
&a[1]: 代表第二个元素的地址
&a+1: 最末端后一个地址
a+1: 第一个元素的地址
&a[0]+1: 第一个元素的地址
如果你能把这些都理解了,那也就无需分析了。呵呵。。。
好的,我们来看一下它的内存模型吧:
2 二维数组
二维数组比一维数组稍复杂了些,它可以理解为数组的数组,即在一维数组里每个元素又是一个数组。
同样我们用一个程序来看一下二维数组的内存模型:
#include "stdio.h"
char a[2][5];
void main()
{
printf("addr of &a:%#x/n",&a);
printf("addr of a:%#x/n",a);
printf("addr of a[0]:%#x/n",&a[0]);
printf("addr of a[1]:%#x/n",&a[1]);
printf("addr of &a+1:%#x/n",&a+1);
printf("addr of a+1:%#x/n",a+1);
printf("addr of a[0][0]:%#x/n",&a[0][0]);
printf("addr of a[0][1]:%#x/n",&a[0][1]);
printf("addr of a[1][0]:%#x/n",&a[1][0]);
}
输出结果:
用图来表示内存模型如下:
3 三维数组
三维数组更为复杂,在我们编程生活中很少遇得到的。不过我们还是应该了解一下的.
同样程序:
#include "stdio.h"
int apricot[2][3][5];
int (*r)[5] = apricot[0];
int *t = apricot[0][0];
void main()
{
printf("addr of apricot:%#x/n",apricot);
printf("addr of r:%#x/n",r);
printf("addr of t:%#x/n",t);
printf("addr of ++r:%#x/n",++r);
printf("addr of ++t:%#x/n",++t);
printf("addr of apricot+1:%#x/n",apricot+1);
printf("addr of &apricot+1:%#x/n",&apricot+1);
printf("addr of apricot[0][0][0]:%#x/n",&apricot[0][0][0]);
printf("addr of apricot[0][0][1]:%#x/n",&apricot[0][0][1]);
printf("addr of apricot[1][0][0]:%#x/n",&apricot[1][0][0]);
}
输出结果:
由于三位数组比较多,这里就不在画图了,它在内存中的存储仍是顺序存储的。即如:
char a[2][3][5];这个三位数组.它有两个元素,每个元素又是一个二维数组。存储的时候首先存储第一个二维数组,然后在顺序存储第二个二维数组。
总结:
经过对一位数组,二维数组,三维数组的分析,我们可以总结出一个规律。即当一个地址+1的时候,这个1的含义是根据这个地址的类型来确定的。如:
int a[5];
a代表第一个元素的地址。它的类型为一个元素的地址。故a+1代表a[1].只往后移动了一个元素位置,即sizeof(*a);
&a代表整个数组的地址。所以&a+1.往后移动的长度为:sizeof(a);
&a[1]代表第一个元素的地址。所以&a[1]+1往后移动的长度为:sizeof(a[1]);
如:
int a[3][10];
a 代表第一维的地址。所有a+1往后移动的距离为:sizeof(a[0]);即:10*sizeof(int)
&a 代表二维数组的地址.所以&a+1往后移动过的距离为:sizeof(a);即3*10*sizeof(int);
&a[1]代表第二维的地址。所以&a[1]+1往后移动的距离为:sizeof(a[1])+1:即10*sizeof(int).
另外我们总结一下能够遍历数组所需用的指针:
遍历一位数组:
如:int a[10];
由于数组中的元素的类型为int,所以我们只需一个int型指针就可以遍历数组了:
int *p = a;
遍历二维数组:
如:int a[3][20];
由于我们可以理解为是数组的数组。所以数组元素的类型为数组。所以我们需要一个数组指针来遍历二维数组:
int (*p)[20];
p = a;
现在p为指向含有20个元素的一位数组指针。他可以指向a[0],a[1],a[2].如需要再去遍历各个一位数组中的内容。我们仍需要改变:我们可以按照下面程序中的方法去进行;
#include "stdio.h"
void main()
{
int a[3][3] = {{1,2,3},{4,5,6},{7,8,9}};
int (*p)[3] = a;
printf("p[0]:%d/n",*p[0]);
printf("p[1]:%d/n",*p[1]);
printf("p[2]:%d/n",*p[2]);
printf("*p+1:%d/n",*(*p));
printf("*p+1:%d/n",*(*p+1));
printf("*p+1:%d/n",*(*p+2));
printf("*p+1:%d/n",*(*(p+1)));
printf("*p+1:%d/n",*(*(p+1)+1));
printf("*p+1:%d/n",*(*(p+1)+2));
printf("*p+1:%d/n",*(*(p+2)));
printf("*p+1:%d/n",*(*(p+2)+1));
printf("*p+1:%d/n",*(*(p+2)+2));
}
输出结果:
这里我们须明白的是:
p指向的二维数组的首地址,目前它代表二维数组的首地址。
*p 代表第一个一维数组的地址。
*p+1代表第二个一维数组的地址。
……
**p则是代表第一个元素了。
*(*p+1)代表第二个元素.
剩下的也就顺理成章了….