再再再论___数组名与指针

来源:互联网 发布:top域名好便宜 编辑:程序博客网 时间:2024/05/17 07:33

计算机内存由一个个物理存储单元构成,这些存储单元是有序的,按照字节编码,字节是最小的存储单位。可以将物理内存想像为一条线性长链,长链由一个个节首尾相接构成,每个节代表可以存储一个字节长度的物理内存单元。一个物理内存单元包括内存单元的内容与内存单元的地址两个不同的概念。存储单元是用来存储数据的,因此自然有内存单元的内容这个概念。同时存储单元在内存中的编排方式是线性排列有序的,因此这就涉及到一个特定物理内存单元在整个内存这条线性长链中的位置这个问题,这就反映为内存单元的地址。因此,物理内存单元的内容与地址是物理内存单元的两个基本要素。

 

地址就是与物理内存单元一一对应的有序数值。

 

内存地址的基本特征

内存地址的基本特征有唯一性、指向性和有序性。

(1) 唯一性

内存地址与物理内存单元之间是一一对应的。一个物理内存单元对应于唯一的地址,一个地址只对应于唯一的物理内存单元,不同的地址对应于不同的物理内存单元。这就是地址的唯一性。

(2) 指向性

由于地址与物理存储单元之间对应的唯一性,根据一个特定的地址就能找到与其唯一对应的物理内存单元以及此物理内存单元中所包含的内容,因此地址自身有一种内禀的指向性--指向与其唯一对应的物理内存单元以及此物理内存单元中包含的内容。

(3) 有序性

地址的有序性是物理内存单元编排有序性的表现。当沿着物理内存这条假想的长链前后移动时,经过的物理内存单元所对应的地址也随之增大或减小,每移动过一个字节,地址的数值改变1。地址数值的大小对应着物理内存单元在物理内存这条假想的长链中的前后位置。

 

地址与地址型是两个有区别的概念。地址是与单个物理内存单元直接对应的基本概念,而地址型是一个类型,包括数据与操作两个方面。

 

地址型的数据

地址型的数据包含两三个要素:首地址值相邻地址间距数据解释依据。其中后两项相邻地址间距和存储数据解释依据可以合并由存储数据类型提供,但由于其功能相对独立故分开来讲。首地址值是存储数据在内存地址中的起始点,存储数据类型则提供存储数据类型长度存储数据解释依据。有了首地址值和存储数据类型长度就可以将内存区分成以存储数据类型长度为单位的一个个节(每个节的长度可以为多个字节,即多个存储单元)。存储数据类型长度就是相邻数据单元所对应的地址之间的间距,也称为相邻地址间距。如果用ad来表示地址型对象(可以为常量也可以为变量),那么ad作为一个完整的地址型对象不仅要有ad所代表的首地址值这个信息,而且要有相邻地址间距和数据解释依据这些信息。缺少了这些信息就无法对地址型对象进行运算。可以将一个地址型的数据形式定义为:ad:{ad_value; d; type} ,其中ad_value代表首地址值,d代表相邻地址间距,type代表数据类型即解释依据。

 

地址型的操作

地址型的操作包含两个基本运算:平移运算与指针运算。这是由地址这种类型的性质决定的。

(1) 平移运算

平移运算来源于内存地址的线性有序排列这个特征,涉及到首地址值与相邻地址间距这两个要素,其运算的一般形式为ad+z,其中z=0,1,-1,2,-2…。平移运算将地址型对象变换为地址型对象,只改变数据中的地址值,而不改变相邻地址间距。因此平移运算是从地址型到地址型的映射,其定义域和值域都是地址类型的对象。假设ad所代表的内存单元的地址值为ad_value,相邻地址间距为d字节,则ad+z所代表的内存单元对应的地址为ad_value+d*z 。如果没有相邻地址间距这个信息,ad+z就没有明确的定义。地址型数据中相邻地址间距这个信息保证了地址型数据对平移运算是有定义的,可以正常操作。

 

(2) 指针运算*

指针运算来源于地址的唯一性和指向性,一个地址唯一地对应于一个物理内存单元以及其中包含的内容。指针运算同时涉及到地址型数据的三个要素首地址值、相邻地址间距和数据解释依据。指针运算的一一般形式为*ad ,其中ad是地址型对象。指针运算*的作用是取出内存中从ad_value到ad_value+d一段内存地址中的数据内容,并依据地址类型中的存储数据类型来解释所提取的数据。因此可形式定义*ad: {valueof(*ad)=data between ad_value and ad_value+d; typeof (*ad)=type within the definition of ad} 。指针运算的定义域是地址型对象,值域是对应于物理内存单元的数据(变量)。指针运算将地址型对象变换为由首地址值和相邻地址间距所确定的一段内存单元中的数据(包括其数据类型的信息),一般来说变换的结果不一定是地址型对象,虽然特殊情况下有可能仍然是地址型对象,如指向指针变量的指针变量(二级指针)在指针运算后变成(一级)指针变量,仍然属于地址型对象。指针运算是地址型对象的公共操作,并不是指针变量所独有的。之所以可以对指针变量p 进行*p这种指针运算,究其根源是由于指针变量是一种地址型对象。注意ad既可以是地址型变量(即指针变量)也可以是地址型常量,因此指针运算*不仅可以作用于指针变量上,也可以作用于地址型常量上。而且指针运算还可以作用于地址型表达式上,例如由地址型进行平移得到的表达式:*(ad+z) 。

 

 

取地址运算&

&不是地址类型数据的基本运算符。&是单目运算符,只能作用于有物理内存单元相对应的变量名或表达式上,不能作用于常量上。其作用结果是生成地址型常量。因此&的定义域是对应物理内存单元的变量,值域是地址型常量。&是生成地址型数据的一个主要方式,其生成的地址型常量既然是地址型数据自然要满足地址型数据的定义,其数据应包括三个方面--首地址值、相邻地址间距和数据解释依据。其中首地址值就是变量v的地址值,相邻地址间距是变量v的类型长度,数据解释依据是变量v的类型。因此可形式定义&v:  {ad_value=&v; d=sizeof(typeof(v)); type=typeof(v)} 。&只能作用于变量名上而不能作用于常量上,是由于常量与变量之间的区别所致。根据变量的定义,变量在内存中分配有内存单元用于存储变量的值,而变量名是此内存单元的符号地址,可以用取地址运算符得到此内存单元的地址。而常量不在内存空间中分配存储单元来存储其值,因此对常量取地址没有意义。例子:& 2没有意义。int m=2, &(m+1)没有意义,但&(m=m+1)有意义。若p是指针变量,则&(++p)也有意义,p先自加1, 然后取存储p的(注意不是p所指向的)内存单元的地址。

 

取地址运算&与指针运算*之间的关系

从&与*的定义上来看,&运算是从存储单元取出地址和数据类型,而*运算是从地址和存储的数据类型得到存储单元中所存储的数据(包括类型),因此取地址运算&似乎与指针运算*互为逆运算,但仔细分析起来两者的关系并非严格的数学意义上的互逆。理由在于&*与*&的作用对象范围不同,&*的定义域是地址型对象,值域也是地址型对象;*&的定义域是变量,值域也是变量。因此&*是从地址型到地址型的恒等运算,而*&是从变量到变量的恒等运算。虽然同为恒等运算,但是作用域不同。地址型对象和变量这两个概念之间重叠但不重合。地址型对象中的地址型变量,如指针变量,属于变量这个范畴,但地址型常量不属于变量这个范畴。变量中的子类,地址型变量属于地址型这个范畴,但其它类型的变量不属于地址型这个范畴。只有当对象同时属于地址型与变量这两个范畴时&*和*&才有可能相等,且同为恒等运算。指针型变量和数组名都是同时属于地址型与变量这两个范畴的对象,不过指针型变量的地址型与变量这两个特征是紧密地结合在一起的,而数组名的地址型与变量这个两个 特征是一种杂交的方式结合在一起的--地址型符号常量中的地址型这个特征与数组型变量中的变量这个特征杂合在一起。这也是数组名与指针变量之间非常容易混淆的最主要原因!

 

 

通常所说的数据类型如int, float, long, double等等,属于 C++中所说的类,包括数据定义与运算操作两个方面。数据类型一般可分为常量与变量(有可能还需要加入过渡量这个子类)两个子类,而常量又可分为数值常量与符号常量两个子类。地址型作为一种数据类型也分为地址型常量与地址型变量两个子类,地址型常量也可分为地址型数值常量与地址型符号常量两个子类。例子有,数组名是代表数组首地址的地址型符号常量,即数组名是一种符号常量且此常量的类型为地址型。但这不是数组名的唯一本质特征,数组名还有作为数组这种变量的名称的特征,这两个特征合在一起才能完全刻画数组名。指针变量是地址型变量,即指针变量是以地址这种数据类型为其值的变量。

 

数组名既是地址型符号常量又是数组型变量名,指针变量是地址型变量。

数组名与指针变量之间的相同与不同之处。从地址型的角度来看,数组名与指针变量都是地址类型的对象,这是它们的共性;它们的不同点在于数组名是地址型符号常量而指针变量是地址型变量。从变量名的角度来看,数组名与指针变量都是变量名,这是它们的共性;它们的不同点在于数组名是数组这种数据类型的变量名,而指针变量名是地址这种数据类型的变量名。总结如下:

相同之处:(1)都是地址型对象 (2)都是变量名。这两个特征是数组名和指针变量都可以被取地址运算&和指针运算*作用的基础。

不同之处:(1)数组名是地址型符号常量,指针变量是地址型变量。(2) 数组名是数组型变量名,指针变量是地址型变量名。

 

 

数组名的双重含义

(1) 数组型变量名:数组名是数组这种数据类型的变量的名称。

(2) 地址型符号常量:数组名是一个地址型符号常量,其地址型数据中的地址值是数组的首地址,相邻地址间距是数组元素数据类型的长度。

数组名的定义中似乎矛盾的两个方面,址型符号常量和数组型变量名,是困难所在

 

问题:数组名与一般变量名有何区别?

首先看变量的定义:其值可以改变的量称为变量。一个变量应该有一个名字,在内存中占据一定的存储单元,在该存储单元中存放变量的值。注意区分变量名与变量值这两个概念。变量名实际上一个符号地址,在对程序编译连接时由系统给每个变量名分配一个内存地址,即存放此变量值的内存单元的地址。在程序中从变量中取值,实际上是通过变量名找到相应的内存地址,从其存储单元中读取数据。这样看来数组名与一般变量名似乎没有什么区别,都是符号地址。但是在源代码中变量名表示变量值,对变量名的操作即是对变量值进行的操作。如int m; m*=2;是指对m的值进行运算,而不是对m这个符号地址进行运算。也就是说在运行程序时m被替换为其符号地址所相应的内存单元中的内容,隐含着由符号地址到提取内存单元内容这个过程。而在源代号中数组名是被当作地址型符号常量而不是此地址所相应内存单元中的内容来处理的。这是数组名作为地址符号常量与一般变量名的区别。但是数组名也具有一些一般变量名可以进行的操作,其中最主要的一个就是取地址运算&,这时取的是整个数组的地址,相邻地址间隔为整个数组的长度。

 

问题:数组名什么情况下当作数组形变量名,什么情况下当作地址型符号常量呢?

这要取决于数组名的应用环境,我的看法是如果可以解释为地址型符号常量就如此解释,否则就解释为数组形变量名,也就是说地址型符号常量这个解释的优先级高于数组形变量名这个解释。一个典型的例子就是&a与a 的理解,其中a是数组a[]的变量名。

 

a 在&a里充当什么角色?是地址型符号常量还是数组形变量名呢?根据算符&的定义可知,其定义域是变量,只有变量才有相应的物理存储单元与之相对应,只有这样取地址运算才有意义,因此这里的a必须理解为数组形变量名,而不能理解为地址型符号常量。也就是说&a不能理解为地址符号常量的地址,因为这地址符号常量本身不对应物理内存单元,取其地址是没有意义的。&a只能理解为取数组a的地址,其结果可形式地写为&a=={ad_value=&a[0]; d=数组a[]所占的字符数; type=数组a[]}。注意”d=数组a[]所占的字符数”的含义,相邻地址间隔并不等于数组元素所占的字符数,而是整个数组所占的字符数。这可以通过cout<<&a<<endl; cout<<&a+1<<endl; 比较看出来,输出的地址之间相差的长度为整个数组a的长度。

 

单独一个a又该如何理解?根据优先级规则将a解释为地址型符号常量。a=={ad_value=&a[0]; d=sizeof(typeof(a[0])); type=typeof(a[0])}。注意”d= sizeof(typeof(a[0]))”的含义,相邻地址间隔是数组元素的长度,而不是整个数组的长度。这可以通过cout<<a<<endl; cout<<a+1<<endl; 比较看出来,输出的地址之间相差的长度为数组元素的长度。

 

例如,对int a[10],a既是这个整形数组的变量名,又是一个地址型符号常量a: {ad value==&a[0]; d==sizeof(a[0])==4; type=int}.对于多维数组又如何呢? 对于二维数组int b[3][4],b既是一维数组b[](每个元素又是一个长度为4*4=16字节的一维数组)的变量名(此变量的长度在内存中的长度为整个数组的长度: 3*4*4=48字节),又是一个地址型符号常量b: {ad_value==&b[0]; d==b[0]变量数据类型的长度==16; type==typeof(b[0]数组)==长度为3的整型数组}。b[0]既是一维数组b[0][](每个元素是一个4字节的整形变量)的变量名(此变量的长度在内存中的长度为4*4=16字节),又是一个地址型符号常量b[0]: {ad_value==&b[0][0]; d== b[0][0] 变量数据类型的长度==4; type==typeof(b[0][0])==int }。b[1]既是一维数组b[1][](每个元素是一个4字节的整形变量)的变量名(此变量的长度在内存中的长度为4*4=16字节),又是一个地址型符号常量b[1]: {ad value==&b[1][0]; d== b[1][0] 变量数据类型的长度==4; type==typeof(b[1][0])==int}。&b[0][0]是地址型常量&b[0][0]:{ad_value==&b[0][0], d== b[0][0] 变量的长度==4; type==typeof(b[0][0])==int },可见作为地址型常量&b[0][0]与b[0]是相同的,但要注意区别是b[0]除了作为地址型符号常量外,还是一个变量名,可以对其进行取地址运算得到&b[0],而对&b[0][0]则不能进行取地址运算(因为取地址运算只能对变量进行,而&b[0][0]不是变量)。同理,&b[1][0] 作为地址型常量与b[1]是相同的,但b[1] 除了作为地址型符号常量外,还是一个变量名,可以对其进行取地址运算得到&b[1]。&b[0]是地址型常量&b[0]: {ad_value==&b[0], d== b[0] 变量数据类型的长度==16; type==typeof(b[0]数组)== 长度为3的整型数组},作为地址型常量它与b是相同的,但b除了是地址型符号常量外,还是变量名,可以进行取地址运算得到&b: {ad value==&b, d==b变量数据类型的长度==3*4*4==48; type==typeof(b数组)==三行四列的二维整型数组}。

原创粉丝点击