引用、指针与数组名

来源:互联网 发布:insert多条数据 编辑:程序博客网 时间:2024/04/18 18:11

引用、指针与数组名都涉及到内存取址的问题,其中引用、数组名都和指针密切相关,它们都在程序存取数据,尤其是函数的参数传递上面发挥着很大的作用。单独地讨论其中任何一个都是不够深入的,只有在相互的比较中,才能更加深刻地理解它们。

指针与数组名

       刚刚接触C语言的时候,就开始使用数组。一维的数组是很好理解的,二维的数组也可看作是平面的栅格,三维的数组是空间的格子,这都很直观。那么,四维、五维的数组呢?尤其是在了解了指针的多级间址后,直观地理解多维数组的思想完全是对正确理解高维数组和多级间址的妨碍。我们知道,数组名本身就带有指针的特性----事实上,所有对资源的存取都是通过指针的地址指向完成的----值得注意的是无论数组名多么像指针,我们都不能简单地把它看做指针,它还有自身的特性,比如,sizeof一个数组名就知道了。

       C++中的数组在内存中的存储是一维的,也就是无论多少维的数组,在内存中都会映射为一维的。而映射的规则就是按高维(注:在这里定义最靠近数组名的是最高维)映射的。例如数组:

我们可以把A看作是一个指向数组对象的指针,这个数组中共有对象。那么则可以看作是一个指向数组对象的指针于是,我们可以把看做是一个指向数组的指针。既然如此,那么指针和任意维数数组名都是可以互换的。(有一点需要说明的是,在数组中,sizeof(A)==sizeof(&A)A==&A,所以在我有限的使用中,没有看出A&A的区别。)

       首先,我们来讨论数组名和一般指针的关系。两个指针(以下讨论都将将数组名看作是指针,就目前讨论的范围来说这样做是可行的)是否完全一样(在这里不考虑显式和隐式的转化)的判断依据包括两个方面:一是指向的数据类型是否一致;二是指向的内存空间大小是否一致。其中,指向的内存空间大小是一个极其重要的依据。原因很简单,如果P是一个指针,那么当P+1时,CPU需要知道P+1的确切地址,在这个时候,若是不知道P指向的内存空间的大小,程序是无法运行的。那么,针对一个int型的三维数组int A[2][3][4]A[0][1]指向长度为4int型数组,而A[0][2]的地址是A[0][1]的地址加4×4=16个字节。而 int (*p)[4]中的pA[0][1]的指针特性是完全一样的,所以在函数的参数中,两者可以互换传递。(不一样的是,sizeof(p)=4,sizeof(A[0][1])=16)。这里,再引入一个在图像处理中常常用到的指针概念:多级间址。

顾名思义,指针的多级间址就是指向指针的指针的指针int **pp。那么,pp可以指向一个int型指针数组的开头,而这个数组中存储的每一个int型指针元素又可以分别指向不同的int型数组。可以看出,pp就相当于一个二维数组的数组名了。由此可推知int ***ppp相当于三维数组的数组名了。但是,以上的两个相当于并不是等于!因为依据指针相等的规则,pp与二维数组名并不满足指向的内存空间一致这个条件,它们只是指向的内容一致罢了。例如:int B[2][3]int **pp。用上面所说的方法,我们可以让pp指向B[2][3]的所有内容,但是B[0]B[1]所跳过的是3*4=12个字节,因为B指向的空间是一个3维的int型数组;而pp+1所跳过的仅仅是1*4=4个字节,因为pp指向的是一个存储了int型指针的数组!虽然pp[i][j]==B[i][j],但这只是因为下标计算的一种诡道罢了。它们走的是两条不同的路,尽管道殊归同。所以,在函数的参数中,二者是不可以互换传递的。

最后,我们还要提一下指针de-reference的概念。如以下的代码所示,我们定义一个指向double变量数组的指针dap:

double (*dap)[2][3][4]

那么,

       dap:一个指向double变量的指针;

       *dap:一个指向维数为[2][3][4]的三维double数组的指针

       **dap:一个指向维数为[3][4]的二维double数组的指针

       ***dap:一个指向维数为[4]的一维double数组的指针

       ****dap: 一个double变量

*号不断增加的过程,就是一个de-reference的过程。事实上,数组名和多级指针后的[ ]起到的也是de-reference的作用。

引用

接下来主要讨论的是引用。引用的定义如下int &p = VariableName。嗯,看上去引用就是一个别名。没错,引用就是一个别名,所以它具有如下性质:

1.        引用必须初始化。引用总是指向在初始化时被指定的对象,以后不能改变。

2.        不存在指向空值的引用。所以在使用引用之前不需要测试它的合法性。

总而言之,引用在性质上像是指针(址传递),在表象上却像变量名(值传递)。我不认同那些认为引用的使用能够使得函数的参数传递显得干净整洁的观点。在我看来,它作为函数参数只会让人迷惑:这究竟是值传递还是址传递?我同样不认同那些认为引用作为函数参数能够减小内存使用的观点:就差了四个字节用来存储指针的内存?况且,引用同样也是需要额外的空间的。

Bjarne Stroustrup认为引用一般用在函数返回值,函数的const参数以及一些特定的操作符的重载(如[]操作符)中。我觉得,引用并不适合作为函数的返回值----出错的可能性太大了。

值得注意的一点是,当按地址传递参数而又不愿意改变该参数的值时,最好使用const的引用。在这种情况下,引用比起指针来有一大优势:能够正确得到Temporary Object的值。例如:

void funcCR(const int &p);

void funcR(int &p)

void funcCP(const int *p);

void funcP(int *p);

以上四个函数,当调用时的参数为1时,只有funcCR是正确的,如funcCR(1)
原创粉丝点击