myxs

地狱与天堂

指针漫谈

   看到很多很多初学网友的关于指针,数组的疑问,我知道,对于一个好学的人来书,怎么做不重要,关键是为什么要这样做,只有深入到这一步 了,才敢说 阿,我懂了 ,于是,以后碰到类似的问题,就可以从我们知道的原理出发,悠然自得而又满怀信心地推导出正确的结果。然而,好学的人往往注定了 困惑于一时的不解,并且,付出过多的时间去试图解决这种迷惑不解,其中的辛苦是可想而知的,因为,我就是这样走过来的。所以,一直都想写一个关于指针,数 组,地址,左右值等等这些在语言的学习里面最让人困惑不解的问题。怎奈时间有限,一直没有能完成这样的想法,今天一样是太忙,可能不能完整的把我想说的写 出来了,那么,就走个捷径,抛开 c++/C 的语义,从系统底层的原理来就事论事的解决一下 ‘k7ta () ’ 网友的问题,本人水平不高,全当抛砖引玉。
[
注意,下面的讲解全部以 32 位系统为例,也就是说,一个 int 占用 4 bytes]
问题:
----------------------------------------------------------------------
1.
多维数组传值
我知道 2 维数组传值 func(type [][SIZE]), 但是 3 维的怎么办? 3 维以上?
若使用类似以上方法,函数中得到的是有 const 修饰的,也就是说不能更改数组中的值
若用指针怎么写,我知道二维 func(type** array), 但数组不能直接定义成 array[][], 要用循环,麻烦
3
维以上怎么办?数组怎么定义?

2. 有什么函数可以高效复制一个数组出来?不然只有再去循环了
=======================================================================
首先讲讲 c/c++ 对数组变量的解释,
我们知道语言对变量的解释虽然都是对某一段内存的 标识 ,但是,对于不同的类型的变量,编译器对他的使用和解释都是有区别的。
比如:
void main()
{
int a=0;
int array[10];
a=3;//ok
array[0]=4;//ok
array={1,2,4};//error
printf("%d,%d,%d",a,array[0],array);//ok
return;
}
于上面的程序段, a 代表了系统中一个 4byte 的内存区域,编译时候用 a 来代表内存的值 [ 也就是所谓右值 ] array 来代表一段( 4*10 byte 存区域的值,而具体到 array 这个变量的身上,编译器解释他的时候,把它看成是这段内存的首地址。所以上面的程序段中的那个 printf 打印出来的因该 'a 的值, array 第一个元素的值 ,array 的首地址 ' 。基于以上的一些事实和理论,
int *p=array;
int **p1=&array;
p
p1 从他们的值上面来说,他们是一样的,都是 array 元素的首地址,他们的不同在于他们的语义上的区别,
*p=array;//
一个指向了数组的指针
int **p1=&array;//
一个指向了 指向数组的指针 得指针。
语义上的不同,对于编译器来解释这个变量的行为的时候非常重要,但是对于系统的内部来说,基本的内存的结构一样的,我们如果能抓住这点的话,那么,指针对我来说将是透明的。
下面举个例子:
int array[10];
int array2d[3][10];
这两个数组的区别和联系分别是什么,如果你能很清楚地认识到那么,你已经有不错的功力了。
首先,不同点,
最表面的语义上的不同就是一个是 1 维,一个是二维,
for(int i=0;i<10;i++)
{
for(int j=0;j<3;j++)
{
array2d[j][i]=array[i];
}

}
上面的程序将有 3 行数据的 array2d 数组每行都设成和 array 相同的值,从这一层来看,我们更能清晰地感觉到行和列的存在 [ 也就是意识到了维的存在 ]
但是在内存内部的实现又是什么样的情况呢 [ 注意,现在来说相同点啦 ]
我们知道,只要是数组,那么系统将会分配给连续的空间,不管是几维的数组
对于 int array[10]     系统分配了 4*10 bytes 的连续空间
int array2d[3][10], 系统分配了 4*(10*3)bytes 的连续空间 , 注意,两者的内存模型完全一样,只是可用的长度不同而已,只要找到这两 个连续的空间的头位置,就可以根据偏移来访问任何一个元素,不管是几维的数组,并且,数组名永远指代连续空间的头地址,于是,我们定义
int* p=array;
*p==array[0];
*(p+1)==array[1];
*(p+9)==array[9];
......
array[x]=*(p+x);
/////////////////
同样,
int* p=array2d;
*p==array[0][0];
*(p+1)==array[0][1];

*(p+9)==array[0][9];
*(p+15)==array[1][5];
.......
array[x][y]==*(p+(10*x)+y);

推广到 3 维, 4 维,道理都是一样的 , 比如,
int array[A][B][C][D];
int *p=array;
array[a][b][c][d]=*(p+a*B*C*D+b*C*D+c*D+d);
后面的部分 ‘a*B*C*D+b*C*D+c*D+d’ 称作偏移量

其实,编译器生成的代码中对于数组元素的访问,就是通过上面的等式完成的,到现在,你可能也感觉到了语义层面上的区别,以及他们的重要性了。清晰地说明如下:
int array[2][3];

int array1[3]2];
对系统来说,都是申请了一个 2*3*4=24bytes 的连续空间,但是由于定义规范的不同导致了,内存偏移计算不一样。
考察
array[1][1]
array1[1][1] 的偏移,
array[1][1]
的偏移是 1*3+1=4;(4 int 型长的偏移,也就是 4*4=12byte 的偏移 )
array1[1][1]
的偏移是 1*2+1=3;
所以,数组的定义在内存访问的层面上来讲,就是决定了元素偏移的计算方法。

好,作了这么多的准备性说明,回到 ‘k7ta () ’ 网友的两个问题,

1. 多维数组传值
对于数组这样的东西,系统并不是值传递的, [ 如果说值传递的话,那么也是数组地址的值传递 ] ,也就是说,并没有建立一个完全一样的拷贝数组,而是将数组的地址作为一个值压入栈,供函数使用。所以,你定义的时候,只要定一个指针传入,然后后面给出维数和各维大小就可以了
比如要出入一个 4 维数组,你可以定义成这个样子
void fun(int *p,int d1,int d2,int d3,int d4);
然后,在函数内部,直接使用偏移来访问各个元素
于是,当你要传入
int array[A][B][C][D];
时候,你可以这样调用
fun(array,A,B,C,D);
根据上面的方法,你还以用不定参数函数的方法,或者用维数数组和数组长的方法来定义出适合任意维数组的寒暑,这个问题留给读者自己去完成了 :-).

2. 有什么函数可以高效复制一个数组出来?不然只有再去循环了
其实这个问题到这里不言自明了,仅仅举个例子
int array1[A][B][C][D];
int array2[A][B][C][D];
拷贝 array1 内容到 array2 中去,由于下面使用到了系统相关的底层操作的缘故,假定在 win32 系统下,
太简单了,一句话搞定
memmove(array2,array1,A*B*C*D);

 

posted on 2006-12-05 15:36 地狱与天堂 阅读(87) 评论(0)  编辑 收藏 引用


只有注册用户登录后才能发表评论。
网站导航:   博客园   博客园最新博文   博问   管理


My Links

Blog Stats

常用链接

留言簿

文章档案

搜索

最新评论