最后,对于数据我们剩下的工作,就是如何运用FROTRAN语言来完整地加以描述。
FORTRAN语言完成对数据的描述的语法单位是数据声明语句,在前面2章里面,我们整理好问题当中需要涉及的数据及其结构之后,要以FORTRAN语言写下来,就是使用FORTRAN的声明语句,这些语句的句法设计保证能够完备的描述我们关于数据所需要说明的一切属性。
数据对象首先需要予以说明的当然就是它的类型,因此我们首先给出对象的类型声明语句,特别地,需要说明在最初FORTRAN标准里面遗留下来的一种数据类型描述方法——隐式类型描述法,然后讨论各种数据对象所可能具有的属性。
● 数组属性;
● 指针属性;
● 值特征;
● 对象可访问性与使用属性;
● 特征兼容性;
● 自动数据对象;
● 存储关联。
由于本章集中讨论FORTRAN的声明语句,而声明的对象除了数据之外,其他的程序对象,例如函数,过程等也需要声明其类型或属性,因此本章一并讨论对其他程序对象的声明:
● 过程属性;
● NAMELIST语句;
所谓一个对象的属性,就是程序使用该对象的方式。
本章所讨论的属性的大体分类与作为其名称的关键词见表7-1:
表7-1属性的分类及其关键词
数据类型 INTEGER REAL(以及DOUBLE PRECISION) COMPLEX LOGICAL CHARACTER TYPE(派生类型的名称由用户命名) |
数组属性 DIMENSION ALLOCATABLE |
指针属性 POINTER TARGET |
值设置 DATA PARAMETER |
对象可访问性与调用 PUBLIC PRIVATE INTENT OPTIONAL SAVE |
过程属性 EXTERNAL INTRINSIC |
对象关系属性 NAMELIST EQUIVALENCE COMMON |
这些关键词的使用,或者说属性的声明有2种方式:
● 在类型声明时附加属性说明,这种语句形式侧重在给出数据对象,属性说明是附加的;
● 使用单独的属性声明语句,这种语句形式侧重在说明属性本身。
之所以会出现2种声明方式,完全是历史的缘故。FORTRAN的早期版本里,任何的对象属性说明,都是运用单独的属性声明语句,如果一个数据对象同时具有多种属性,就需要使用相应的多个属性声明语句,从程序阅读的角度来看,会显得很烦琐,因此到了FORTRAN90和95版本,只要在程序单元当中需要声明一个数据对象的类型,那么就可以把它的其他所有属性都附加到它的类型声明语句当中,使得程序显得更加紧凑。
【例7-1】 我们要定义2个实型变量X,Y,同时还要声明它们都具有指针属性,在早期FORTRAN语言里写为:
REAL X,Y
POINTER X,Y
换一种更加紧凑的写法就是把这2条语句写为1句:
REAL POINTER::X,Y
之所以我们还需要讨论单独的属性语句,主要是有时候需要保持源码的向前兼容性,而且在少数情况下,这2种形式也并完全等价。因此在本章在讨论各种属性的声明方式的时候,将首先给出面向数据对象的声明方式,然后给出等价的面向属性的声明方式。
在这2种表示属性的方式里,同样由于历史的缘故,数组的维度属性DIMENSION既可以附加在类型声明语句当中表述,也可以单独声明,实际上DIMENSION这个关键词属于老式FORTRAN的遗留物,在新的表述方式里面,完全可以省略它。
【例7-2】
COMPLEX X
DIMENSION X(35)
SAVE X
COMPLEX X(35)
SAVE X
COMPLEX,DIMENSION(35),SAVE::X
COMPLEX,SAVE::X(35)
以上4种表述方式是完全等价的,显然最后一种最简洁。
在一个程序单元里面,引用一个数据对象的前提是已经声明了它的种种必需属性,数据对象的各种属性里面,最为基本的当然是数据对象的类型,然后还需要声明其各种必需的属性,这样该数据对象才能被程序正确地调用访问。然而在一个实际的程序里面,数据对象的声明在表面上可能并不是完备的,同时又不是非法的语法,出现这种情况的原因如下:
● 在FORTRAN的早期版本里,为了求简化的缘故,使用了一种隐式约定数据类型的方式,即通过数据对象的名称的第一个字母来分辨其所属类型,这种类型声明方式被后来的FORTRAN版本一直沿用下来了,因此如果一个程序单元里出现的数据对象没有经过类型声明语句的专门声明,则需要考虑其是否属于隐式声明。相反,如果想排除这种隐式声明的影响,则需要另外单独加以IMPLICIT NONE语句作为声明。
● 任何其他的属性如果没有出现的话,或者是因为该数据对象不需要某种属性,或者是该数据对象的某种属性采取了默认设置。
从上面的分类表7-1可以看到,FORTRAN的属性除了用来说明数据类型的属性之外,其他属性都是针对不同的数据类型或程序对象,以及它们的各种特征的。例如数组属性只是用来说明数组,指针属性只是用来说明指针,而过程属性只是用来说明过程,下面将分类讨论这些属性。
我们在讨论第5章讨论数据类型的时候给出数据类型的基本声明语句的句法形式,这里将侧重于数据类型的声明与其他相关属性的兼容,因为从语言的简洁与自然的角度出发,把一个数据对象的类型和其他属性都归结为一条数据声明语句是非常可取的,所以我们得到如下的一般的类型声明句法形式(R501):
type-specification [[ ,attribute- specification]…:: ] entity-declaration-list
即作为选项,在一条数据对象的类型声明(type-specification)语句当中,在数据项声明的列表(entity-declaration-list)之前,给出相应的属性说明(attribute- specification),方括号[]表示可选。
● 类型说明(type-specification)的一般句法形式(R502)是如下几种形式之一:
INTEGER [kind-selector]
REAL [kind-selector]
DOUBLE PRECISION
COMPLEX [kind-selector]
CHARACTER [character-selector]
LOGICAL [kind-selector]
TYPE(type-name)
其中种别选择符(kind-selector)的句法形式(R506)为:
([KIND = ] kind-value)
其中种别值(kind-value)是一个标量整型表达式。
● 属性说明(attribute- specification)的一般句法形式(R503)为如下几种形式之一:
PARAMETER
ALLOCATABLE
DIMENSION(array- specification)
EXTERNAL
INTENT(intent-specification)
INTRINSIC
OPTIONAL
POINTER
SAVE
TARGET
access- specification
其中的可访问性说明(access- specification)包括PUBLIC和PRIVATE。
● 数据项声明的一般句法形式(R505)为如下2种形式之一:
object-name [(array- specification)] [*character-length] [initialization]
function-name [(array- specification)] [*character-length]
其中初始化(initialization)的一般句法形式(R505)为如下2种形式之一:
= initialization-expression
=>NULL()
类型声明的一般规则如下:
● 对数据对象的说明优先于隐式类型描述,即隐式类型描述法永远只是在数据对象没有获得任何说明的情况下的默认法则,而显式说明既可以与隐式法则一致,也可以不一致。
● 在一个类型声明语句当中,同一个属性只能出现一次。
● 在一个作用域内,一个数据项的任何属性只要约定了一次,就不可再次约定。
● 种别选择符所取的种别值只能是编译系统所许可的相应类型的种别参数之一。
● 字符长度(character-length)选项只能出现在CHARACTER类型的声明语句当中。
● 如果使用初始化语句,则必须在数据项声明前使用双冒号(;;)。
● 如果数据变量被初始化设置为数组,那么该数组的形状一定要得到说明,或者是在类型声明语句当中,或者是在同一个作用域内此前的属性声明语句当中。
● 如果数据对象被赋予PARAMETER属性,那么其中必须包含初始化语句。
● 如果在初始化语句当中出现符号=>,那么该被初始化的对象必定具有POINTER属性;如果在初始化语句当中出现符号=,那么该被初始化的对象肯定不具有POINTER属性。
● 所谓函数名称(function-name),或者是一个外部函数的名称,或者是一个固有函数的名称,或者是一个函数哑过程的名称,或者是一个语句函数的名称。
● 一个数组函数名称或者是被指定为显形数组,或者是具有POINTER属性,从而被指定为待定形数组。
有关属性声明的其他规则以及属性之间的兼容性在后面会详悉讨论。
【例7-3】 下面是一些附加了其他属性的数据类型声明语句:
REAL, INTENT(IN) :: COS
REAL, INTRINSIC :: SIN
INTEGER X(25)
LOGICAL,DIMENSION(10,15)::RESULT1,RESULT2
INTEGER,PARAMETER::SHORT=SELECTED_INT_KIND(4)
COMPLEX :: SQUARE_ROOT = ( 2.76, -0.85)
REAL, ALLOCATABLE :: X( : , : )
TYPE, POINTER :: CURRENT_OF_SAMPLE => NULL()
INTEGER语句声明了整型数据对象的名称,同时也可以附加种别参数以及其他属性。如果给出种别选择符,即说明该整型数据的表示方法。
声明一个数据对象属于整型数据的基本语句句法为:
INTEGER [ ( [ KIND = ] kind-value ) ] [ [ , attribute-list] :: ] entry-list
【例7-4】
以下这些声明语句主要是要说明数据项:
INTEGER X
INTEGER DIMENSION(:), POINTER
:: days, hours
INTEGER(SHORT)RED_BALL
INTEGER(2) POINTER :: k,
limit
INTEGER(1) DIMENSION(10)
:: min
【例7-5】
以下这些声明语句主要是要说明数据的属性:
INTEGER days, hours
INTEGER(2) k, limit
INTEGER(1) min
DIMENSION days(:),
hours(:), min (10)
POINTER days, hours,
k, limit
REAL语句声明了实型数据对象的名称,同时也可以附加种别参数以及其他属性。如果给出种别选择符,即说明该实型数据的表示方法。
双精度实型数据还可以使用DOUBLE PRECISION语句加以声明,当然与在REAL语句当中使用种别参数是完全等价的。由于DOUBLE PRECISION本身指出了精度表示方法,因此不能再在后面附加种别选择符。
不过DOUBLE这个词也可以作为一个命名整型常量,取双精度实型的种别参数值,这样就可以使用REAL(DOUBLE)来声明双精度实型数据了。
实型数据的声明的格式如下:
REAL [ ( [KIND=] kind-value ) ] [ [ , attribute-list] :: ] entry-list
DOUBLE PRECISION [ ,attribute-list] :: ] entry-list
【例7-6】 下面是各种形式的主要说明数据项的声明语句:
REAL (KIND = high), OPTIONAL :: testval
REAL, SAVE :: a(10), b(20,30)
DOUBLE PRECISION,POINTER::A,B(:,:)
DOUBLE PRECISION,DIMENSION(5,10)::TABLE1,TABLE2
REAL(DOUBLE),POINTER::A,B(:,:)
REAL(DOUBLE),DIMENSION(5,10)::TABLE1,TABLE2
【例7-7】 下面是各种形式的主要说明数据属性的声明语句:
REAL (KIND = high) testval
REAL a(10), b(20,30)
OPTIONAL testval
SAVE a, b
DOUBLE PRECISION TABLE1,TABLE2
DIMENSION TABLE1(5,10),TABLE2(5,10),B(:,:)
POINTER A
可以比较什么两种表示方法,在说明了同样的意思的前提下,把属性说明附加在数据类型说明语句当中,比每一种属性单独一个语句加以说明要简洁自然得多。
COMPLEX语句声明了复型数据对象的名称,同时也可以附加种别参数以及其他属性。如果给出种别选择符,即说明该复型数据的表示方法。说明复型数据类型的句法为:
COMPLEX [([KIND = ] kind-value ) ] [ [ , attribute-list] :: ] entry-list
【例7-8】 下面是各种形式的复型数据对象声明语句:
COMPLEX ch
COMPLEX (KIND=4),PRIVATE :: zz, yy !等价于COMPLEX*8 zz, yy
COMPLEX(8) ax, by ! 等价于COMPLEX*16 ax, by
COMPLEX (kind(4)) y(10)
complex (kind=8) x, z(10)
LOGICA语句声明了逻辑型数据对象的名称,同时也可以附加种别参数以及其他属性。如果给出种别选择符,即说明该逻辑型数据的表示方法。说明逻辑型数据类型的句法为:
LOGICAL [([KIND = ] kind-value ) ] [ [ , attribute-list] :: ] entry-list
【例7-9】 下面是各种形式的主要说明逻辑型对象的声明语句:
LOGICAL, ALLOCATABLE :: flag1, flag2
LOGICAL (KIND = byte), SAVE :: doit, dont
【例7-10】 下面是各种形式的主要说明对象的属性的声明语句:
LOGICAL flag1, flag2
LOGICAL (KIND = byte) doit, dont
ALLOCATABLE flag1, flag2
SAVE doit, don’t
CHARACTER语句声明了字符型数据对象的名称,同时也可以附加种别参数以及其他属性。如果给出字符选择符,即说明该字符型数据的最大字符长度。说明字符型数据类型的句法为:
CHARACTER [character-selector] [ , attribute-list :: ] entry-list
其中的字符选择符(character-selector)的句法形式(R507)可以为如下几种:
length-selector
(LEN =length-value , KIND = kind-value )
(length-value , [ KIND = ] kind-value )
(KIND = kind-value [,LEN =length-value] )
其中长度选择符(length-selector)的句法形式(R508)可以是:
([LEN =] length-value)
* character-length[ ,]
其中字符长度(LEN)(R509)可以是:
(length-value)
scalar-integer-literal-constant
其中长度值(length-value)(R510)可以是:
specification-expression
*
在上面的类型说明当中,使用星号*来标志字符长度的方式属于过时的方式,不过不是指在数据项里面使用的星号*。
字符型数据对象类型声明语句的一般规则如下:
● 只有在没有使用双冒号的情况下,在长度选择符里才可以使用逗号。
● 字符型数据对象的字符长度在一定的情形下,是可以发生动态变化的,即当一个字符型数据的声明语句出现在一个过程或者是过程界面当中,同时该数据对象又不是某个派生数据对象的成员,那么它的字符长度可以使用非常量表达式,该表达式的具体取值只有当访问该过程时才被确定,并且当该过程的运行时,表达式里变量的变化并不影响字符长度值。这样一种数据对象如果不属于哑元,则属于自动数据对象的范畴。
● 字符长度的说明有三种情形:
• 首先在数据项或数据项列的成员里给出字符长度;
• 如果没有上面的长度说明,则在数据类型说明部分给出命名字符数据项或派生类型定义当中的字符成员的字符选择符,用来给出长度属性;
• 如果一个字符型数据既没有使用字符选择符,也没有使用字符长度来说明其长度,那么默认的字符长度为1。
● 如果长度参数取负值,那么相应的字符项长度为0。
● 给定了字符长度的标量整型文字常量不能再附加任何种别参数,否则在固定源码形式里面就会导致歧义。
● 采用带星号*的长度说明只能使用于以下几种情形:
•可以应用于过程的哑元,当过程被调用时,哑元就被赋予相应实元的长度;
•可以用于声明命名常量,这时它的长度是一个常量值;
•可以用于声明一个外部函数的结果变量的长度。任何调用该函数的作用域单元如果使用带星号的长度说明,就意味着主程序可以访问该声明。当函数被调用的时候,结果变量的长度的取值由引用该函数的程序单元里面的相应声明语句决定。
本规则暗示了在IMPLICIT语句当中不能使用带星号*的长度说明方式。
● 如果一个函数属于内部函数或模块函数,或者该函数的值为数组,指针或递归形式,那么该函数名不能使用带星号*的长度声明。
● 字符值语句函数或字符型语句函数哑元的长度必须是整型常量表达式。
【例7-11】 下面是各种形式的主要说明字符型数据对象的声明语句:
CHARACTER (LEN=25,KIND=GREEK),DIMENSION(11)::Z1
CHARACTER (LEN=20,KIND=KANJI),SAVE::GREETING(2)
CHARACTER (10)::QUESTION=“WHERE?”
CHARACTER (LEN=*,KIND=CHINESE),PARAMETER::MESSAGE = &
“简朴是语言的一种美德”
CHARACTER (*),INTENT(IN)::SCHOOL, HOME
CHARACTER *3, SAVE :: COMPONENT_1, LONGER(9) *20, COMPONENT_2
CHARACTER :: RESULT = “PASS”
例7-12:下面是各种形式的主要说明字符型数据的属性的声明语句:
CHARACTER (70)PROJECT
CHARACTER (LEN=30, KIND=GERMAN)TRANSFORMATION
CHARACTER (LEN=25,KIND=GREEK)Z1
CHARACTER (LEN=20,KIND=KANJI)GREETING(2)
CHARACTER (10)QUESTION
CHARACTER (*)SCHOOL, HOME
CHARACTER *3 COMPONENT_1, LONGER(9) *20, COMPONENT_2
CHARACTER RESULT
SAVE GREETING(2)
INTENT(IN)SCHOOL, HOME
DATA QUESTION /“WHERE?”/
CHARACTER (*)MESSAGE
PARAMETER( MESSAGE = &
“简朴是语言的一种美德”)
TYPE语句声明了用户派生数据类型对象的名称,派生类型的名称写在紧跟TYPE后面的一对括号当中,声明派生类型的数据对象的一般句法为:
TYPE(type-name)[ , attribute-list :: ] entry-list
派生类型声明语句的一般规则如下:
● 如果一个派生类型对象是私有的话,就不能附加PUBLIC属性。
● 结构构造器必须用来初始化派生类型数据对象,结构构造器里的表达式必须是初始化表达式。
● 如果需要声明的派生数据对象是一个函数的结果,那么它就可以在FUNCTION语句当中予以说明。
【例7-13】 下面是各种形式的主要说明派生数据对象的声明语句:
TYPE(SAMPLE),DIMENSION( :),ALLOCATABLE::STOVE
TYPE(STUDENT),SAVE::GRAD(5)
TYPE(HOMEWORK),SAVE::QUESTION,TAPE,WORD_SHEET
【例7-14】 下面是各种形式的主要说明派生数据对象的属性的声明语句:
TYPE(SAMPLE)STOVE
TYPE(STUDENT)GRAD(5)
TYPE(HOMEWORK)QUESTION,TAPE,WORD_SHEET
DIMENSION STOVE( :)
ALLOCATABLE STOVE
SAVE GRAD(5), QUESTION,TAPE,WORD_SHEET
注意上面这两种说明方法里面标点的不同用法。
当初FORTRAN使用隐式类型声明方法,是与语言的简朴形态相适应的,在硬件资源相对紧缺的时代,简朴也能成为语言的一种美德,不过如果到了硬件资源相对富余的今天,还吝啬于增加几个关键词和几条声明语句,就会显得很落伍了,所以后来就增加了更为接近自然语言的声明语句,不过,为了缅怀FORTRAN语言作为祖母级高级语言的荣耀,隐式类型声明方式还是保留下来了,甚至默认为总是起作用,除非首先声明了IMPLICIT NONE语句。
当然,只要你乐意使用,这种类型声明方式在某些情况下,还是非常方便简明的。
隐式类型声明方法的出发点,是考虑到任何需要加以类型声明的对象,如变量,命名常量,函数等,都需要一个名称来指称,那么就可以把它的类型这个信息负载在它的名称上,最简单的约定,就是让名称的第一个字母来标记它的类型,例如在默认的情形下:
REAL: A B C D E F G H
INTEGER: I J K L M N
REAL: O P Q R S T U V W X Y Z
可以发现,上面对字母的默认分配并不是很难记忆,FORTRAN沿用了数学里的很多习惯,这里使用I,J,K,L,M,N来表述整数,就是一个明显的数学习惯。
除了上面默认的首字母隐式类型法则,隐式类型声明方法还有很灵活的一面,即利用IMPLICIT语句来自定义字母分配模式,一般的IMPLICIT语句的句法形式(R541)有2种,分别行使不同的功能:
IMPLICIT type-specification(letter-specification-list)
IMPLICIT NONE
其中的字母分配说明列表的句法形式(R543)为:
letter[-letter]
这里IMPLICIT后面的字母分配表定义可以完全是自由定义,并且只是在该语句的作用域内有效,而任何名称在它的作用域内只要是没有专门的类型声明语句,也没有IMPLICIT语句,那么它就遵循上面的默认首字母隐式类型法则。
隐式类型的一般规则如下:
● 如果要使用IMPLICIT NONE语句,那么它必须放置在任何PARAMETER语句之前,并且在其作用域内,再也不能出现其他IMPLICIT语句。
● IMPLICIT语句当中出现的表述字母范围的letter-letter,左边的字母绝对不能是右边字母的按照字母表顺序的后面的字母。
● letter-letter的字母之间为减号,而非下划线,表示按照字母表顺序从左边字母到右边字母的所有字母。
● 在一个作用域里面的IMPLICIT语句里面,同一个字母不能出现在字母分配说明列表里面的不同项里,例如作为单个字母出现了,又同时处于另一个字母范围之中,或同时处于2个不同的字母范围里面。
● IMPLICIT语句同样可以用来为派生数据类型分配首字母作为类型标记。
默认的首字母隐式类型法则与IMPLICIT语句之间的关系值得特别加以注意。
例如给出约定:
IMPLICIT COMPLEX(E-G,W-Z)
然后在该语句的作用域里再也没有其他IMPLICIT语句,也没有类型声明语句,那么在该作用域内具有不属于(E-G,W-Z)这个范围的首字母的变量,将遵循默认首字母分配表,即首字母在范围(A-D,H,O-V)内的变量属于实型,而首字母在范围(I-N)内的变量属于整型。
IMPLICIT NONE可以用来检查名称的错误拼写,因为如果不加以这个语句的话,即使错误拼写的名称,也会被看成遵循首字母隐式类型声明方法的具有确定数据类型的变量,从而能够通过语法检查,反之使用该语句,就可以避免这种误解。
【例7-15】 下面的IMPLICIT语句都是合法语句:
IMPLICIT CHARACTER*30(B,T),COMPLEX(W-Z)
IMPLICIT LOGICAL(KIND=BIT)(Q)
IMPLICIT REAL(QUAD)(X-Z)
IMPLICIT TYPE(NUMBER)(A-E)
IMPLICIT TYPE(ARTICAL)(A,V),CHARACTER*100(B)
【例7-16】 下面的IMPLICIT语句都是非法语句:
IMPLICIT CHARACTER*30(B,T),COMPLEX(Z -W)!W应该放置在Z 之!前。
IMPLICIT LOGICAL(KIND=BIT)(Q),REAL(QUAD)(P-T)!Q同时表!示2种类型。
IMPLICIT TYPE(ARTICAL)(A-H),CHARACTER*100(B,Q)!B同时表!示2种类型。
使用隐式类型声明在一个嵌套作用域里面所导致的变量作用域混乱问题在有关作用域之间通讯时再讨论。
数组的概念来源于数学的向量概念,最大的特点就是数组的各个分量必须是同一种数据类型,同样的种别参数,具有同样的属性,因此数组本身作为一个数据对象,最主要的属性就只剩下它的维度,也就是它的秩。
作为一个变量的数组可以有多种形式,除了它的分量可以发生变化之外,它的维度也可以发生变化,有3种情形需要数组的维度不予固定:
● 在程序运行当中,数组所占据的存储空间不是固定的,而是可重新分配的,这意味
着该数组必定具有ALLOCATABLE属性;
● 该数组被赋予指针的属性,使得它的维度无法预先固定下来;
● 该数组本身就是一个哑元,它的维度依赖于别的变量对它的赋值情况。
因此数组本身形状的说明出现了4种形式,而数组所特有的属性,主要就是DIMENSION和ALLOCATABLE,下面分小节予以说明。
数组描述的4种形式的句法形式(R513)为:
explicit-shape-specification-list
deferred-shape-specification-list
assumed- shape-specification-list
assumed-size-specification
这四种形式分别用来描述数组在程序当中出现的四种可能情形:
● 首先是最基本的显形数组(explicit-shape arrays),即在定义数组的时候就已经给出它的形状;
● 当一个数组具有指针属性,或具有可分配属性时,它的形状就没法预先给定,需要在程序运行过程当中通过指针或分配过程予以确定,这时就需要使用待定形数组(deferred-shape arrays)的形式来加以描述;
● 当数组本身属于哑元时,它的形状就需要实元来赋予,这样就需要使用哑形数组(assumed- shape arrays)的形式来加以描述;
● 如果实元赋予一个作为哑元的数组时,只是决定它的尺度,而没有给定其他形状要素,那么就需要使用哑尺度数组(assumed-size arrays)的形式来加以描述。
下面我们将分节说明这四种数组的描述方式。
数组的描述的一般规则如下:
● 按照FORTRAN标准,数组的秩至少可以达到7,尽管一般应用环境里,秩为2或3是最常见的,但是对于一些特定的计算问题,可能需要用到非常高的维度的数组,所以一般的FORTRAN编译器都能支持远大于7的秩。
● 一个标量的秩为0。
● 待定形数组必定或者具有POINTER的属性,或者具有ALLOCATABLE的属性。
● 哑形数组和哑尺度数组必定属于哑元。
● 必须注意待定形数组和哑形数组的表示形式可能出现雷同的时候,即都是使用一个冒号表示。
顾名思义,所谓显形数组就是对于数组的每一个维度都给出了具体的上下界,每个维度的显形说明的句法形式(R514)如下:
[ low-bound :] upper-bound
其中的上界(upper-bound)和下界(low-bound)为说明表达式。参见表达式。
显形数组的一般规则如下:
● 显形数组的说明列表里所给出的上下界对的数目必须与该数组的维度数目一致。
● 如果下界被省略了,则表明取默认值1。
● 上下界可以是正整数,也可以是负整数,还可以是0。
● 数组的下标范围就是在上下界之间,包括上下界本身的所有整数集合,并且上界不能小于下界,如果出现这种情况,表明下标范围为空集,或者说其相应维度的宽度为0,而且数组的尺度也为0。
● 如果上下界是以表达式的形式出现,那么可能包含变量,使得上下界的具体取值在数组所处的过程运行当中发生变化,这时,该数组必定是一个哑元,或者是一个函数结果,或者是一个动态数组。
【例7-17】 下面是主要说明数据项的声明语句:
INTEGER X(20:30,5,-10:40)
….
SUBROUTINE AAA(I,J,K)
REAL,DIMENSION(10:I+2,J)::K
….
【例7-18】 下面是主要说明属性的声明语句:
INTEGER X(20:30,5,-10:40)
….
SUBROUTINE AAA(I,J,K)
REAL K
DIMENSION K(10:I+2,J)
….
待定形数组用来表示具有指针属性和可分配属性的数组。
● 对于指针数组来说,数组的每个维度的宽度是在指针被分配或指针赋值语句被执行之后才给定的;
● 对于可分配数组来说,它的上下界只是在被分配之后才给出。
因此对于这种数组的形状说明,只能使用一个冒号,即如下的句法形式(R518):
:
待定形数组的一般规则如下:
● 待定形数组的秩等于它的说明列表当中出现的冒号的数目。
● 当待定形数组完成存储分配之后,它的上下界在ALLOCATE语句当中被确定。
● 对一个指针数组的目标数组的每个维度执行固有函数LBOUND,就能得到指针数组的相应维度的下界;同样的,对一个指针数组的目标数组的每个维度执行固有函数UBOUND,就能得到指针数组的相应维度的上界。
因此,
● 如果上下界是由指针的分配决定,那么该数组的形状就可以由用户来指定;
● 如果上下界是由指针的赋值决定,那么该数组的形状就可以有二种情形:
● 如果指针目标是一个命名全数组,那么上下界就由该数组的声明决定,或者在该数组被分配之后给出;
● 如果指针目标是一个数组片断,那么它的下界为1,而上界为所处维度的宽度。
● 如果指针数组或可分配的上下界里面包含变量,那么当变量在随后有了重定义与去定义的情形的话,上下界不受这种变化的影响。
【例7-19】 下面是主要说明数据项的声明语句:
REAL,POINTER::A( : , : ),B( : , : )
REAL,ALLOCATABLE::A( : , : )
【例7-20】 下面是主要说明属性的声明语句:
REAL A( : , : ),B( : , : )
POINTER A,B
ALLOCATABLE A
所谓哑形数组,就是一个哑元,它的形状只有在与一个实元相结合时才能定下来,哑形数组的说明句法(R517)为:
[ low-bound] :
即只是可选地给出一个下界,然后就是一个冒号,下界阙如,表示未定。
哑形数组的一般规则如下:
● 哑形数组的说明列表当中出现的冒号的个数,就等于该数组的秩。
● 下界或者明确给出,或者省略,表示下界取默认值1。
● 上界等于数组在相应维度上的宽度加下界值减1。
● 哑形数组不能具有POINTER或ALLOCATABLE这二种属性。
● 未结合指针数组的尺度,界和形状都是未定义的,这样一个数组的任何部分都不能被引用或定义,不过这样的数组可以作为固有查询函数的变量出现,从而可以查询变量状态,数据类型性质,类型参数,或者结合状态。
【例7-21】 下面是主要说明数据项的声明语句:
SUBROUTING AAA(X,Y,Z)
REAL,DIMENSION(5:,:)::A
REAL,INTENT(IN)::X( :),Y(5:)
【例7-22】 下面是主要说明属性的声明语句:
SUBROUTING AAA(X,Y,Z)
REAL X( :),Y(5:),Z
DIMENSION A(5:,:)
INTENT(IN)X,Y
….
一个哑尺度数组就是一个哑元,而它的尺度由与它相关联的实元设定,因此在声明哑尺度数组时,就给定了它的尺度,即秩,宽度和界(最后一个维度的上界和宽度除外)。对于哑尺度数组和实元之间的关联有如下规则:
● 二者具有相同的初始数组元素。
● 后继数组元素是按照存储顺序排列的。
● 哑元的声明就需要给出数组的秩,所有维度的下界,以及除了最后一个维度之外的所有的上界与宽度。
● 实元的尺度哑元的尺度。
哑尺度数组的最后一个维度的上界不能给出,因此使用星号(*)表示。
哑尺度数组的句法形式(R519)为:
[ explicit-shape-list,] [ low-bound:] *
可见,哑尺度数组与显形数组在形式上的主要差别就是最后一个维度的上界有待给出。
哑尺度数组的一般规则如下:
● 哑尺度数组的秩就是它的显形数组的说明列表当中的说明数目加1。
● 哑尺度数组的尺度根据以下途径来决定:
● 如果与哑元相关联的实元是非默认字符类型的任意其他类型数组,那么哑元的尺度
就是实元的尺度。
● 如果与哑元相关联的实元是非默认字符类型的任意其他类型数组元素,并且它的下
标顺序值是一个尺度为x的数组当中的v,那么该哑元的尺度为x-v+1。
● 如果与哑元相关联的实元属于默认字符类型数组,或者属于默认字符型数组元,或
者属于默认字符型数组元素子串,假设它开始于一个以c作为字符存储单位的数组
的第t个字符存储单位,那么哑元的尺度为:
MAX(INT((c-t+1)/e),0)
其中e是哑字符数组里的元素的长度。
● 设数组的秩为r,那么前r-1维的界都由相应的显形数组说明给定,最后一个维度的下界则由声明中给出的下界给定,如果没有给出,则取默认值1。
● 如果界的表达式里面包含变量,那么当相应过程执行时,就会导致表达式取值发变化,但是当变量在随后如果是有了重定义与去定义的情形的话,上下界则不受这种变化的影响。
● 函数结果不能成为哑尺度数组。
● 除了以下两种情形,哑尺度数组不能作为全数组引用:
·在过程引用当中没有要求实元的形状;
·引用到固有函数LBOUND。
【例7-23】 下面是主要说明数据项的声明语句:
SUBROUTING PRO(A,B,C)
REAL,DIMENSION(A,*)::B
REAL C(100,20,*)
….
【例7-24】 下面是主要说明属性的声明语句:
SUBROUTING PRO(A,B,C)
REAL B, C(100,20,*)
DIMENSION B(A,*):
….
数组的四种声明形式可以在程序里扮演的脚色有各种限制,也就是说具有不同的语法作用。下面的表7-2给出这四种形式与不同语法单位的兼容性限制:
表7-2数组的四种声明形式的兼容性
|
数组声明的形式 |
|||
数组的语法单位 |
显形数组 |
待定形数组 |
哑形数组 |
哑尺度数组 |
表达式的主项 |
可 |
可 |
可 |
否 |
下标向量 |
可 |
可 |
可 |
否 |
哑元 |
可 |
可 |
可 |
可 |
实元 |
可 |
可 |
可 |
可 |
等价对象 |
可 |
否 |
否 |
否 |
公用对象 |
可 |
可 |
否 |
否 |
名称列表对象 |
可 |
否 |
否 |
否 |
保留对象 |
可 |
可 |
否 |
否 |
初始化数据对象 |
可 |
否 |
否 |
否 |
I/O列表项 |
可 |
可 |
可 |
否 |
格式 |
可 |
可 |
可 |
否 |
内部文件 |
可 |
可 |
可 |
否 |
分配对象 |
否 |
可 |
否 |
否 |
指针赋值语句里的指针对象 |
否 |
可 |
否 |
否 |
指针赋值语句里的目标对象 |
可 |
可 |
可 |
否 |
数组的维度除了可以在数组声明语句当中,通过数组名称的说明来给出之外,还可以使用DIMENSION属性语句来给出,实际上,DIMENSION语句属于FORTRAN早期标准的遗留物,现在既可作为单独的属性语句出现,也可以附加在数据声明语句里面。
附加DIMENSION属性的数据类型声明语句的句法形式为:
type-specification,DIMENSION(array-specification)[ ,attribute-list]::&
entity-list
可以跟在DIMENSION属性语句后面的其他属性有:
initialization
ALLOCATABLE
INTENT
OPTIONAL
PARAMETER
POINTER
PRIVATE
PUBLIC
SAVE
TARGET
另外,数组说明可以使用一个数组名称,然后后面附加其他语句来声明一个数组,这些语句可以是DIMENSION语句,类型声明,ALLOCATABLE,POINTER,TARGET,COMMON等语句。
如果以说明DIMENSION属性为主给出数组定义,那么可以采用如下的句法形式(R526):
DIMENSION[ ::] array-name(array-specification)&
[,array-name(array-specification)] …
【例7-25】 下面是主要说明数据项的声明语句:
REAL,ALLOCATABLE,TARGET::X( :,:)
REAL,DIMENSION(30),TARGET,SAVE::METER
【例7-26】 下面是主要说明属性的声明语句:
REAL X( :,:),METER
DIMENSION METER(30)
TARGET X,METER
ALLOCATABLE X
SAVE METER
【例7-27】 下面是在其他声明语句当中使用数组说明的例子:
REAL X,METER
TARGET X,METER(30)
ALLOCATABLE X( :,:)
SAVE METER
【例7-28】 下面是在COMMON语句当中使用数组说明的例子:
COMMON / MOTION / TIME(30),SPACE(1000,1000,1000)
一个数组可分配是指它的界只有在执行ALLOCATE语句之后才能确定。这样一个数组必定是一个待定形数组。ALLOCATABLE属性只是针对数组而言才成立。一个具有ALLOCATABLE属性的数组的类型声明的句法形式为:
type-specification,ALLOCATABLE [ ,attribute-list]::entity-list
能与ALLOCATABLE兼容的属性有:
DIMENSION(deferred-shape- specification-list)
PRIVATE
PUBLIC
SAVE
TARGET
单独的ALLOCATABLE属性语句的句法形式(R527)为:
ALLOCATABLE[::] array-name [(deferred-shape- specification-list)]&
[,array-name [(deferred-shape- specification-list)]] …
ALLOCATABLE属性声明的一般规则如下:
● 具有ALLOCATABLE属性的数组就不能是哑元或函数结果。
● 如果给定具有ALLOCATABLE属性的数组,再在任何位置给出具有DIMENSION属性,都必须使用单独的冒号来表示为待定形数组。
【例7-29】 下面是主要说明数据项的例子:
REAL,ALLOCATABLE,SAVE::X(:,:)
INTEGER,ALLOCATABLE,DIMENSION(:)::Y
【例7-30】 下面是主要说明属性的例子:
REAL X(:,:)
INTEGER Y
DIMENSION Y(:)
ALLOCATABLE X,Y
SAVE X
一般说来,我们给一个数据对象附加一种属性,总是意味着给该数据对象增加某种它本来没有的性质,但是指针(POINTER)属性则不然,当我们把POINTER属性附加给某个数据对象,实际上是把该数据对象本来具有的存储空间给拿掉了。
由于我们要引用一个数据对象,前提必然是已经确定了该数据对象的存储空间,才能准确地加以访问,那么显然,对于一个具有POINTER属性的数据对象,既然它不具有了初始的存储空间,也就无法加以引用,除非再给该数据对象关联上一个存储空间。
给POINTER对象关联到一个存储空间,可以通过如下方法:
● 使用ALLOCATE语句为POINTER对象创造一个新的存储空间;
● 使用指针赋值语句使得POINTER对象从另外一个具有存储空间的数据对象那里借用其存储空间,那个被借用存储空间的数据对象称为指针的目标。
目标数据对象在程序运行时是可以发生变化的,它有两种来源:
● 赋予了TARGET属性的数据对象,或者它的某个部分;
● 指针通过分配而创造出来的新的数据对象,或者它的某个部分。
指针可以被关联到另外一个指针的目标,或者目标的某个部分。
对于一个数组来说,要使得它成为某个指针的目标,除了具有ALLOCATABLE属性之外,还必须具有TARGET属性。
在一个程序里,最初建立一个指针对象之后,还必须对它进行初始化,也就是定义它的初始状态(参见7.5),然后才可以使用固有函数ASSOCIATED来查询指针的状态,否则,该指针既不能被引用,也不能出现在DEALLOCATE语句当中。
所谓指针的经过定义了的状态有两种:关联与去关联。
● 通过ALLOCATE与指针赋值语句,可以使得指针具有关联状态;
● 通过DEALLOCATE或者NULLIFY语句,或者让指针赋值给另外一个本来处于去关联状态的指针,可以使得指针具有去关联状态。
一个指针无论是处于关联状态还是去关联状态,总之是已经被定义的状态,因此可以使用固有函数ASSOCIATED来查询它是否处于关联状态。
实际上,对于指针我们还可以看成是一个描述符,这个描述符所占据的空间里存储了有关它所指称的对象的数据类型,种别参数,秩,宽度,以及目标的位置等这类信息。
因此一个实型标量对象指针必然和一个派生类型的数组指针具有完全不同的存储空间单位,那么给出一个指针必然无法预先给定它的存储单位,当我们要在一个公用块里声明指针属性的时候,就相当于给出存储的描述符,因此每当我们声明一个包含指针的公用块时,都必定指定相同的存储单位序列。
附加了POINTER属性说明的类型声明的句法形式为:
type-specification,POINTER [ ,attribute-list]::entity-list
可以与POINTER兼容的其他属性包括:
initialization
DIMENSION(deferred-shape- specification-list)
OPTIONAL
PRIVATE
PUBLIC
SAVE
单独的POINTER语句也可以用来声明一个指针对象,它的句法形式(R528)为:
POINTER [::] object-name [(deferred-shape- specification-list)]&
[,object-name [(deferred-shape- specification-list)]] …
指针声明的一般规则如下:
● 指针的目标可以是标量,也可以是数组。
● 指针数组必须被声明为待定形数组。
● 一个指针不能被引用或定义,除非它已经被关联到一个可以引用或已定义的目标(在指针赋值语句的右边的指针不能被视为一个引用)。
【例7-31】 下面是主要说明数据项的例子;
TYPE(SAMPLE),POINTER::CAPACITANCE
REAL,POINTER::A(:,:),B(:,:)
【例7-32】 下面是主要说明属性的例子:
TYPE(SAMPLE) CAPACITANCE
REAL A(:,:),B(:,:)
POINTER CAPACITANCE,A,B
被赋予TARGET属性的数据对象在程序运行当中,就可能成为某个指针的目标;如果一个数据对象不具有TARGET属性或者没有经过分配,那么它的任何部分都不能被指针访问。
在一个类型声明语句当中附加TARGET属性说明的句法形式为:
type-specification,TARGET [ ,attribute-list]::entity-list
可以与POINTER兼容的其他属性包括:
initialization
ALLOCATABLE
DIMENSION
INTENT
OPTIONAL
PRIVATE
PUBLIC
SAVE
单独的TARGET语句也可以用来指定指针目标,句法形式(R529)为:
TARGET [::] object-name [(array-specification)]&
[,object-name [(array-specification)]] …
【例7-33】 下面是主要说明数据项的例子:
TYPE(SAMPLE),TARGET::CAPACITANCE
REAL,TARGET,DIMENSION(100,100)::A,B(100)
【例7-34】 下面是主要说明属性的例子:
TYPE(SAMPLE) CAPACITANCE
REAL A,B(100)
DIMENSION A(100,100)
TARGET CAPACITANCE,A,B
所谓值特性就是描述一个变量,或者一个指针,或者一个命名常量的取值状态的属性。针对不同的数据对象,值特性的说明分为如下两种情形:
● 对于变量与指针,可以通过2种途径赋予初始化值特性:
·给一个变量赋予初始值,或者使一个指针无效化(或者说置空),都可以通过采用一个主要说明数据项的类型声明语句而获得。这个类型声明语句包含如下的数据项声明形式:
obiect-name = initialization-expression
pointer-name =>NULL()
·使用DATA语句。
然后这些变量与指针的取值,就可以在程序随后的运行当中发生变化。这样一种在类型声明语句当中附加数据项初始化声明形式的值的初始化的方式称为显式初始化。
● 对于命名常量的取值,也可以通过如下两个途径:
·通过采用一个附加了PARAMETER属性说明的主要说明数据项的声明语句来取值,而且该语句包含如下数据项声明形式:
obiect-name = initialization-expression
·使用PARAMETER语句。
命名常量通过如此方式给定的取值,不能在随后的程序运行过程当中发生变化,例如我们经常对一些数学常数使用固定的容易辨认的命名,然后在程序运行的开始给定一个固定的取值,比如说,取PI=3.14,那么这个命名常量的取值在随后的程序运行过程当中保持不变。
命名常量也可以用来给一些固定的数值加以命名,这样的数值在一定情况下保持不变,但也可能在某些情况下需要加以改变,例如利率,这时就需要在不同的地方加以不同的取值定义。
下面我们分两节来分别讨论值特性的两种情形。
对于变量与指针,可以通过数据初始化和DATA语句这2种途径来进行值的初始化,其中DATA语句作为一种属性,是唯一不能附加到类型声明语句当中去的一种属性。除了单独使用DATA语句来给出变量与指针的值的初始化之外,还可以在类型声明语句当中,使用特定的数据初始化形式来给出变量与指针的值的初始化。
上面这两种说明变量与指针的值的初始化的方法,都称为显式初始化,相对而言,如果在一个程序当中没有出现显式初始化,那么通过类型声明语句,实际上就按照默认的约定而进行了隐式初始化(参见第5章),那种隐式初始化对于同一类型的所有对象是一致的。
显式初始化总是优先于隐式初始化。
对于变量来说,初始化的结果并不改变变量本身的类型属性。例如给一个实型变量赋予一个整型值,那么变量仍然保持为实型,而那个整型值被潜在地转换为相应的实型值,一般而言,只要出现取值与变量的类型以及种别不一致的情形,取值总是潜在地被转换为与变量一致的类型以及种别。
对于指针来说,只有经过初始化,才能获得关联状态,即或者是关联,或者是去关联,否则,没有经过初始化的指针,即无定义的指针不能出现在很多的上下文当中。例如它不能被引用,不能出现在DEALLOCATE语句当中,也不能通过使用固有函数ASSOCIATED来查询其关联状态。
给出显式初始化的类型声明语句的句法形式为:
type-specification [ ,attribute-list]::object-name [(array-specification)]&
[*character-length] initialization
而其中的初始化(initialization)为以下形式之一:
obiect-name = initialization-expression
pointer-name =>NULL()
其中第一种形式用于指定非指针对象的初始值,第二种形式用于使得指针置空。
能够与变量初始化兼容的其他属性有:
DIMENSION
POINTER
PRIVATE
PUBLIC
SAVE
TARGET
变量通过类型声明语句而获得的初始化,或者变量的任意部分通过DATA语句而获得的初始化,都意味着变量自动具有了SAVE属性,除非这个变量是处于一个命名公用块当中。
这种自动获得的SAVE属性还可以通过单独的SAVE语句,或者在类型声明语句当中附加SAVE属性说明,来得到更加明确的表示。
DATA语句的句法形式(R532)为:
DATA data-object-list / data-value-list / &
[[ ,] data-object-list / data-value-list / ]…
其中数据对象(data-object)有以下形式(R534):
varible
data-implied-do
数据值(data-value)的形式(R538)为:
[repeat-factor * ] data-constant
其中重复因子(repeat-factor)为一个标量整型常量,数据常量(data-constant)的形式(R540)有以下几种:
scalar-constant
scalar-constant-subobject
signed-integer-literal-constant
signed-real -literal-constant
NULL()
structure-constructor
boz-literal-constant
隐式do数据(data-implied-do)的形式(R535)为:
(data-implied-do-object-list,scalar-integer-variable = &
scalar-integer-expression,scalar-integer-expression &
[ ,scalar-integer-expression])
隐式do数据对象(data-implied-do-object)的形式(R536)有如下几种:
array-element
scalar-structure-component
data-implied-do
数据初始化的一般规则如下:
● 如果需要进行初始化的数据对象为字符型或逻辑型,那么用于初始化的常量必须是同一个类型。
● 如果需要进行初始化的数据对象为实型或复型,那么用于初始化的常量必须是整型,实型或复型。
● 如果需要进行初始化的数据对象为整型,那么用于初始化的常量必须是整型,实型或复型。如果初始化是在DATA语句当中指定,那么相应的常量可以是二进制,八进制,或十六进制的字面常量。
● 如果需要进行初始化的数据对象为派生类型,那么用于相应的常量必须是同一个派生类型。
● 如果NULL()出现在初始化说明里面,或者表现为DATA语句的常量,那么相应的被初始化的对象必须具有POINTER属性。
● 相应于非指针对象的初始表达式的值,或者是DATA语句里的常量,必须可以通过使用固有赋值语句而赋值给相应对象,从而使得相应对象获得初始值。
● 类型声明语句当中进行的初始化优先于类型定义当中默认的初始化,只要进行了显式初始化,就可以认为不存在默认初始化。
● 具有默认初始化的派生类型的对象不能在DATA语句当中进行初始化。
● 在一个可执行程序里,同一个对象,或者是一个对象的同一个部分,只能进行一次显式初始化。
● 以下数据对象不能进行数据初始化:
·哑元
·可被用户或主程序访问的对象
·函数结果
·动态对象
·可分配数组
·命名公用块当中的对象,除非该对象的数据初始化是在一个数据块程序单元中进行的空公用块里的对象、外部过程或固有过程。
● 如果一个对象被初始化,那么它的所有下标,片断下标,子串始点和子串终点都必须是满足以下条件的表达式:
·表达式的所有算元都必须是常量,常量的子对象,或隐式do变量;
·表达式的所有运算都必须是固有运算。
● 结构构造器的每一个用于初始化的成员都必须是初始化表达式。
● 出现在DATA语句当中的经过显式的类型声明的变量,可以出现在随后的其他类型声明语句当中的唯一前提就是:随后的声明不与它的显式声明冲突。
● 出现在DATA语句当中的数组名,数组片断,数组元素都同样地具有此前该数组被定义时所具有的性质。
● 如果一个数组元素或结构成员为隐式do数据对象,那么就不能拥有父常量。
● DATA语句的重复因子必须是正数或0;如果是一个命名常量,那么当DATA语句进行计数时,它的值必须在同一个作用域单位里的处于DATA语句之前的其他语句里被指定。
● 隐式do结构里的标量整型表达式的运算元只能是常量,常量的子对象,或隐式do变量;表达式的所有运算都必须是固有运算。
● 数据对象列表可以扩张为一个标量变量序列。而一个数组或数组片断等价于它的元素按照数组元素序排列得到的序列。一个隐式do数据在隐式do变量的控制下,可以扩张为一个由数组元素组成的序列。一个尺度为0的数组,或一个重复计数为0的隐式do变量对于扩张出来的序列没有任何影响,但是一个具有0长度的字符变量却能够对序列产生影响。
● 数据值列表可以扩张为一个属于固有类型的标量常量值序列。当DATA语句被计数时,每一个得到的数据值都必须是编译器认可的数据常量。DATA语句的重复因子实际上表示了进入序列的后续的数据常量的数目。如果重复因子为0,则后续的数据常量就不进入序列了。
● 标量变量和扩张序列当中的数据常量必须是一一对应的,扩张序列当中的每一个数据常量都表示相应变量的初始值或初始状态。两个扩张序列的长度一定是相同的。
【例7-35】 下面是主要说明数据项的例子:
CHARACTER(LEN= 20)::SYMBOL = “AIPHA”
INTEGER,DIMENSION(1:10)::COUNT = (/(1,I=2,11)/)
TYPE(LINK),POINTER::START=>NULL()
TYPE(SAMPLE)::SAMPLE1 = SAMPLE(33,“BETA”)&
SAMPLE2= SAMPLE(45,“BETA”)
REAL::OVERFLOW(100,100)=RESHAPE((/((1.0,J=1,K-1),&
(0.0,J=K,100),K=1,100)/),(/100,100/))
【例7-36】 下面是主要说明属性的例子:
CHARACTER(LEN= 20)SYMBOL
INTEGER COUNT
DIMENSION OUNT(1:10)
TYPE(LINK)START
POINTER START
DATA START /NULL()/
DATA SYMBOL /AIPHA /,COUNT /10*0/
TYPE(SAMPLE)SAMPLE1,SAMPLE2
DATA SAMPLE1 / SAMPLE(33,“BETA”)/
DATA SAMPLE2 / SAMPLE(45,“BETA”)/
REAL OVERFLOW(100,100)
DATA ((OVERFLOW(J,K),J=1,K-1),K=1,100)/4950*1.0/
DATA ((OVERFLOW(J,K),J=K,100),K=1,100)/5050*0.0/
在上面的例子里,字符变量SYMBOL的初始化值为AIPHA。整数数组COUNT的10个元素都始化为0,在主要说明数据项的例子里使用了数组构造器,而在主要说明属性的例子里使用了重复因子。指针START开始被置空。SAMPLE1和SAMPLE2是属于派生类型SAMPLE的数据对象,其中SAMPLE1的初始化使用了结构构造器,而SAMPLE2的初始化是通过给定它的每个成员的值而得到的。
在主要说明数据项的例子里还使用了固有函数RESHAPE,因为数组OVERFLOW的秩为2,而在主要说明属性的例子里则是使用了重复因子。
在类型声明语句附加PARAMETER属性说明或者单独使用PARAMETER语句,可以给常量赋予一个名称。
在类型声明语句当中附加PARAMETER属性说明的句法形式为;
type-specification,PARAMETER [ ,attribute-list]::name= initialization-expression
在一个类型声明语句当中可以对多个常量命名。
与PARAMETER属性兼容的其他属性有:
initialization(一定要有)
DIMENSION
PRIVATE
PUBLIC
SAVE
根据固有赋值规则,由初始化表达式给出常量的名称。而出现在初始化表达式当中的命名常量只能属于以下情形:
· 在本类型声明语句的前面已经被定义,或者在前面的类型声明语句当中已经被定义。
· 编译器已知的命名常量。
单独的PARAMETER语句也可以用来给出命名常量,其句法形式(R530)为:
PARAMETER (name-constant= initialization-expression &
[,name-constant= initialization-expression ]…)
PARAMETER语句的一般规则如下:
● 哑元,函数,或公用块里的对象不能具有PARAMETER属性。
● 出现在PARAMETER语句当中的经过显式的类型声明的命名常量,可以出现在随后的其他类型声明语句当中的唯一前提就是:随后的声明不与它的显式声明冲突。
● 出现在PARAMETER语句当中的数组常量具有该数组此前被定义时所具有的一切性质。
● 为避免出现歧义,命名常量不能出现在格式说明当中。
【例7-37】 下面是主要说明数据项的例子:
INTEGER,PARAMETER::LOCAL=100
INTEGER,PARAMETER::A=COUNT(20,10),&
TEST=100+LOCAL
【例7-38】 下面是主要说明属性的例子:
INTEGER LOCAL,A,TEST
PARAMETER (LOCAL=100)
PARAMETER(A= COUNT(20,10),&
TEST=100+LOCAL)
有一类属性是专门用来描述数据对象是否具有访问限制,以及如何使用相应的数据对象的,当然也不是所有的数据对象都可以具有这样的属性,只有三类数据对象可以进行访问控制:
● 模块中的对象;
● 哑元;
● 子程序里声明的变量。
相关的属性包括:
● PUBLIC和PRIVATE属性用来控制模块当中的数据对象的可访问性;
● INTENT用于控制子程序当中的哑元的使用;
● OPTIONAL用于控制子程序当中变量在进行子程序引用时的使用;
● SAVE用于控制子程序里变量的值在进行子程序引用时的使用。
在类型声明语句里附加的PUBLIC属性和PRIVATE属性控制了对模块里的类型定义,变量,函数,以及命名常量的访问。
● PUBLIC属性表明了模块里的数据对象可以通过关联被模块外部访问。
● PRIVATE属性则刚好相反,防止了模块外部通过关联来访问模块里的数据对象。
在类型声明里附加PUBLIC属性和PRIVATE属性说明的句法形式为;
type-specification,PUBLIC [ ,attribute-list]::entity-list
type-specification,PRIVATE [ ,attribute-list]::entity-list
PUBLIC属性和PRIVATE属性说明也可以用于派生数据类型的声明当中:
TYPE,PUBLIC::type-name
TYPE,PRIVATE::type-name
如果在类型声明当中使用了PRIVATE属性,但是却没有包含访问id列表,那么就说明尽管从模块外部可以访问该类型的数据,但是却不能访问数据的成员。
可以与POINTER兼容的其他属性包括:
initialization
ALLOCATABLE
DIMENSION
EXTERNAL
INTRINSIC
PARAMETER
POINTER
TARGET
SAVE
单独的PUBLIC语句和PRIVATE语句也可以用来控制对数据对象的访问。除了可以作用于类型定义,变量,函数,和命名常量之外,它们还可以用来控制一些不能通过类型说明来定义的对象,例如子例行程序,通用描述符,以及名称列表集合。
PUBLIC语句和PRIVATE语句的句法形式(R522)为:
PUBLIC [ [ ::] access-id-list ]
PRIVATE [ [ ::] access-id -list ]
其中访问id(access-id)的形式(R523)为以下几种:
use-name
generic-specification
其中类说明(generic-specification)的形式(R1207)为以下几种:
generic-name
OPERATOR(已定义算符)
ASSIGNMENT(=)
类说明的具体讨论参见第13章的过程界面。
【例7-39】 下面是使用类说明的PUBLIC和PRIVATE语句的例子:
PUBLIC HYPERBOLIC_COS, HYPERBOLIC_SIN !类名称
PRIVATE HY_COS_RAT, HY_SIN_RAT !种名称
PRIVATE HY_COS_INF_PREC!种名称
PUBLIC :: OPERATOR( .THAT.), OPERATOR(+), ASSIGNMENT(=)
PUBLIC和PRIVATE语句的一般规则如下:
● PUBLIC和PRIVATE只能用于模块。
● 有效名称(use-name)可以是变量,过程,派生数据类型,命名常量,以及名称列表集合这些对象的名称。
● 在一个模块的作用域单位里只能存在一个省略了访问id的PUBLIC语句或PRIVATE语句,该语句决定了对于模块的默认可访问性。
● 在派生数据类型的声明里面可以使用PRIVATE语句,来表明该种类型的结构成员不能被模块外部访问。这种情况不能使用相反的PUBLIC语句。
● 具有公共的通用标识符的过程,可以通过通用标识符而被访问,尽管它的特指名称是不可访问的。
● 一个模块过程如果包含私有类型的变量,或私有类型的函数结果,则过程本身也就是私有的,就不能具有公共的通用标识符。
模块中定义的对象在默认情形下,都是具有PUBLIC属性的。可以在模块里给出一个缺乏访问id列表的PUBLIC属性说明来明确表明模块内部对象的默认可访问性质。在模块里使用不含访问id列表的PRIVATE属性说明可以改变这种默认PUBLIC属性。
【例7-40】 下面是主要说明数据项的例子:
REAL,PUBLIC::GLOBAL_1
TYPE, PRIVATE :: LOCAL_DATA
LOGICAL :: ANSWER
REAL,DIMENSION(20) :: DENSITY
END TYPE LOCAL_DATA
【例7-41】 下面是主要说明属性的例子:
REAL GLOBAL_1
PUBLIC GLOBAL_1
TYPE, LOCAL_DATA
LOGICAL ANSWER
REAL DENSITY (20)
END TYPE LOCAL_DATA
PRIVATE LOCAL_DATA
【例7-42】 下面是一个具有私有成员的公有类型的例子:
TYPR LIST_ELEMENT
PRIVATE
REAL VALUE
TYPE (LIST_ELEMENT), POINTER:: NEXT, FORMER
END TYPR LIST_ELEMENT
【例7-43】 下面是一个改变默认访问性质的例子:
MODULE M
PRIVATE
REAL A, B, TEMP(20)
REAL, PUBLIC :: X(20), Y(20)
…
END MODULE M
针对哑元的INTENT属性更多的是为了方便程序作者或用户,通过给哑元附加INTENT属性,就可以有意地使用哑元,从而便于检查错误,可以为程序读者提供有用的信息,为调试修改源码提供方便,从而可以改善源码的效率。
哑元的运用分三种情形:
● 被引用但在子程序里还没有被重定义;
● 在子程序里被引用之前已经定义;
● 在重定义之前被引用。
相应的,INTENT也有三种形式,分别用于上面的哑元运用的三种情形:
● IN;
● OUT;
● INOUT。
如果变量的意向属性是IN,那么子程序不能改变变量的值,也不能成为无定义变量。
如果变量的意向属性是OUT,那么子程序在变量被定义之前都不能使用该变量,并且该变量必须是可定义的。
如果变量的意向属性是INOUT,那么变量就可以用于和子程序通讯,并且返回信息。在进入子程序时,它必须是已经定义好了的,而且必须的可定义的。
如果不使用INTENT属性,那么哑元的使用由相关联的实元决定,实元所受到的限制同样是哑元的限制。
例如如果实元是一个常量,那么相应的哑元就只能被引用,而不能被定义。
附加INTENT的类型声明语句的形式为:
type-specification,INTENT(intent- specification) [ ,attribute-list]::&
dummy-argument-name-list
其中的INTENT的属性是IN,OUOT,INOUT之一。
与INTENT兼容的其他属性有:
DIMENSION
OPTIONAL
TARGET
单独的INTENT语句也可以给出INTENT属性,其句法形式(R520)为:
INTENT(intent- specification) [ ::] dummy-argument-name-list
其中的INTENT的属性是IN,OUOT,INOUT之一。
INTENT语句的一般规则如下:
● INTENT属性只能用于哑元。
● INTENT语句只能在子程序的说明部分或界面体出现。
● INTENT属性不能应用于作为哑过程的哑元,因为哑过程的定义是无法改变的,而且INTENT属性也不能应用于哑指针,因为无法明确INTENT属性是应用于指针还是应用于指针的目标。
● 如果哑元属于给出了默认初始化是数据类型,则只有当该哑元具有INTENT(OUT)属性的时候,才获得初始化定义。
【例7-44】 下面是主要说明数据项的例子:
SUBROUTING MOVE(FROM,TO)
USE PERSON_MODULE
TYPE(PERSON), INTENT(IN) :: FROM
TYPR(PERSON), INTENT(OUT) :: TO
…
SUBROUTING SUB (X, Y)
INTEGER, INTENT(INOUT) :: X, Y
…
【例7-45】 下面是主要说明属性的例子:
SUBROUTING MOVE(FROM,TO)
USE PERSON_MODULE
TYPE(PERSON) FROM, TO
INTENT(IN) FROM
INTENT(OUT) TO
…
SUBROUTING SUB (X, Y)
INTEGER X, Y
INTENT(INOUT) X, Y
…
使用OPTIONAL属性,可以使得具有该属性的哑元在包含该哑元的过程被引用的时候被省略掉,而直接采用默认值来代替那些被省略的哑元,这样就可以节省那些在被引用后并不需要使用变量形式的哑元。这就意味在该过程在某个特定的引用里,减少了变量的数目,这样就能够有效地改善程序的执行效率。
之所以引入这样一种属性,是根据科学计算的实际需要。因为经常会出现这样一种情况:对于一个专用的过程,为了能够对付尽可能多的调用需求,往往要设置足够多的哑元,但是在该过程得到某个具体调用时,往往发现在一个具体的问题当中,其中很多哑元并没有起到变量的作用,而是一直取常量,因此可以在引用该过程时,一开始就省略不起作用的哑元,直接采用默认常量替代它,这样能够很大的节省程序运行的资源消耗。
因此在调用某个过程之前,我们可以根据实际情况,给其中的某些哑元附加OPTIONAL属性,使得该过程进入调用后,自动省略这些哑元,而直接采用默认值。
附加OPTIONAL属性说明的类型声明语句的形式为:
type-specification,OPTIONAL [ ,attribute-list]::dummy-argument-list
与OPTIONAL属性兼容的其他属性有:
DIMENSION
EXTERNAL
INTENT
POINTER
TARGET
单独的OPTIONAL语句也可以给出OPTIONAL属性,其句法形式(R521)为:
OPTIONAL [ ::] dummy-argument-name-list
OPTIONAL语句的一般规则如下:
● OPTIONAL属性只能用于哑元。
● OPTIONAL语句只能在子程序的作用域单位或界面体出现。
【例7-46】 下面是主要说明数据项的一个程序片断例子:
CALL SORT_X (X= VECTOR_A)
…
SUBROUTING SORT_X (X,SIZEX, FAST)
REAL, INTENT(INOUT) :: X(:)
INTEGER, INTENT(IN),OPTIONAL :: SIZEX
LOGICAL, INTENT(IN), OPTIONAL :: FAST
…
INTEGER TSIZE
…
IF (PRESENT (SIZEX)) THEN
TSIZE= SIZEX
ELSE
TSIZE= SIZE(X)
END IF
IF (.NOT. PRESENT (FAST) .AND. TSIZE > 100) THEN
CALL QUICK_SORT(X)
ELSE
CALL BUBBLE_SORT(X)
END IF
…
【例7-47】 下面是主要说明属性的例子:
SUBROUTING SORT_X (X,SIZEX, FAST)
REAL X ( : )
INTENT (INOUT) X
INTEGER SIZEX
LOGICAL FAST
INTENT (IN) SIZEX, FAST
OPTIONAL SIZEX, FAST
…
INTEGER TSIZE
…
当子程序完成运行之后,其中定义的具有SAVE属性的变量能够保持它的值,它的定义,它的关联与分配状态。而不具有SAVE属性的变量则不能保证能够保持它们的值,定义和关联分配状态,尽管在一些FORTRAN的实现里,所有的局部变量和公用块被认为是具有SAVE属性。保险而言,如果一定要求对象或对象的公用块能够保持它的值,定义以及状态,那么就一定要事先赋予其SAVE属性。
在模块里声明的对象也可以赋予SAVE属性,这样它将在使用该模块的过程完成运行之后,还能保持其值,定义以及状态。
递归子程序里的对象也可以赋予SAVE属性,该对象能够被子程序的所有分支共享。
任何获得数据初始化的对象都默认为具有SAVE属性,但是一个派生数据类型的结构,即使它的所有成员都具有默认的初始化,它本身并不认为具有SAVE属性。
附加SAVE属性说明的类型声明语句的形式为:
type-specification,SAVE [ ,attribute-list]::entity-list
与SAVE属性兼容的其他属性有:
initialization
ALLOCATABLE
DIMENSION
POINTER
TARGET
PUBLIC
PRIVATE
由于具有PARAMETER属性的对象为命名常量,因此对于它也就无所谓保持值定义以及状态的不变,没必要再去赋予它SAVE属性,实际上也不允许对具有PARAMETER属性的对象赋予SAVE属性。
单独的SAVE语句也可以给出SAVE属性,其句法形式(R524)为:
SAVE [[ ::] saved- entity-list]
其中保留项(saved- entity)的形式(R525)为下面二种:
data-object-name
/ common-block-name /
SAVE属性的一般规则如下:
● 一个不包含保留项列表的SAVE语句意味着在作用域内的所有项都具有保留性质,而在这个作用域内再也不能出现SAVE语句。
● 如果SAVE作为属性或语句出现在主程序,那它是无效的。
● 以下数据对象不能被保留:
•函数结果
•哑元
•动态数据对象
•公用块里的对象
● 公用块里的变量不能单独地赋予SAVE属性,如果整个公用块被赋予SAVE属性,那么它的所有变量都具有了SAVE属性。
● 如果一个公用块在一个程序的某个作用域里被赋予了SAVE属性,那么它在自己被定义的程序的所有作用域里,都具有SAVE属性(除了主程序)。
● 如果一个命名公用块是在主程序里给出的,那么程序的任何作用域都可以访问它,这样它就不需要被赋予SAVE属性了。
【例7-48】 下面的声明语句主要说明数据项:
REAL,DIMENSION(10),SAVE::A
【例7-49】 下面的声明语句主要说明属性:
REAL A
DIMENSION A(10)
SAVE A
【例7-50】 下面的声明语句说明保留对象及公用块:
SAVE X,Y,/BLOCKA/,Z,/BLOCKB/
我们上面讨论的一些属性已经不限于描述数据对象,也可以用来描述模块,过程等程序单位,例如PUBLIC,PRIVATE,SAVE等。本节我们讨论两个只针对函数与程序单位的属性:EXTERNAL和INTRINSIC。
● 如果一个哑过程或一个外部过程是一个子程序的实元,那么该过程名称就必须声明为具有EXTERNAL属性;
● 如果一个外部过程与某个固有过程拥有相同的名称,那么就需要对该名称赋予EXTERNAL属性,同时具有这个名称的固有过程就不能被该程序单位访问了。
● 如果一个固有过程是一个实元,那么它的名称就必须声明为INTRINSIC。
由于只有函数具有类型的定义,而子例行程序不具有类型的定义,因此作为类型声明的附加属性的EXTERNAL和INTRINSIC只能用于函数,而对于子例行程序,则需要使用语句形式的EXTERNAL和INTRINSIC。
下面我们分别讨论这两个属性与相应语句。
EXTERNAL属性用于描述一个名称是一个外部函数,或一个哑函数的名称,并且该名称可以用于作为实元。
附加EXTERNAL属性说明的类型声明语句的形式为:
type-specification,EXTERNAL [ ,attribute-list]::function-name-list
与EXTERNAL属性兼容的其他属性有:
OPTIONAL
PUBLIC
PRIVATE
如果一个函数返回一个数组或指针,那么该函数的界面必定是显形的,所谓界面块就是用来描述外部函数的界面的,如果一个函数被描述为界面块,那么它已经具有默认的外部属性,因此也就没必要再去显形地赋予它EXTERNAL属性。
单独的EXTERNAL语句可以声明子例行程序与数据块程序单位具有外部属性,其句法形式(R1208)为:
EXTERNAL[ ::] external-name-list
EXTERNAL属性的一般规则如下:
● 每个外部名称必须是一个外部过程,或一个哑元,或一个数据块程序单位的名称。
● 如果一个哑变量被描述为EXTERNAL的,那么该哑变量是一个哑过程。
● 除了在块内具有MODULE PROCEDURE语句的模块过程之外,一个界面块对于其中的所有过程具有外部属性。
【例7-51】 下面是主要说明数据项的例子:
SUBROUTING SUB(DESIGN)
INTEGER,EXTERNAL::DESIGN
LOGICAL,EXTERNAL::SIN
【例7-52】 下面是相应的主要说明属性的例子:
SUBROUTING SUB(DESIGN)
INTEGER DESIGN
LOGICAL SIN
EXTERNAL DESIGN,SIN
INTRINSIC属性用于描述一个名称是一个固有函数的名称,并且该名称可以用于作为实元。
附加INTRINSIC属性说明的类型声明语句的形式为:
type-specification,INTRINSIC [ ,attribute-list]::intrinsic-function-name-list
与INTRINSIC属性兼容的其他属性有:
PUBLIC
PRIVATE
单独的INTRINSIC语句可以用来声明固有子例行程序,其句法形式(R1209)为:
INTRINSIC [ ::] intrinsic-procedure-name-list
INTRINSIC属性的一般规则如下:
● 每个固有过程名称必定是一个固有过程的名称。
● 在一个作用域内,一个名称只能声明INTRINSIC一次。
● 在一个作用域内,一个名称不能既声明为INTRINSIC,又声明为 EXTERNAL。
● 一种具体的编译器可能支持FORTRAN标准之外的更多的固有过程,当然在相应编译器看来,这些固有过程与标准固有过程具有相同的地位,但是使用了这样的过程的程序可能导致程序的可移植性变差。
【例7-53】 下面是主要说明数据项的例子:
REAL,INTRINSIC::SIN,COS
【例7-54】 下面是相应的主要说明属性的例子:
REAL SIN,COS
INTRINSIC SIN,COS
属性兼容性表7-3如下:如果两个属性可以同时在一个声明语句当中赋予,则用“可”表示,否则,用“否”表示。
表7-3各种属性的相互兼容性
|
ALLOCATABLE |
DIMENSION |
POINTER |
TARGET |
PARAMETER |
PUBLIC |
PRIVATE |
INTENT |
OPTIONAL |
SAVE |
EXTERNAL |
INTRINSIC |
INITIALIZATION |
否 |
是 |
是 |
是 |
是 |
是 |
是 |
否 |
否 |
是 |
否 |
否 |
ALLOCATABLE |
|
是 |
否 |
是 |
否 |
是 |
是 |
否 |
否 |
是 |
否 |
否 |
DIMENSION |
|
|
是 |
是 |
是 |
是 |
是 |
是 |
是 |
是 |
否 |
否 |
POINTER |
|
|
|
否 |
否 |
是 |
是 |
否 |
是 |
是 |
否 |
否 |
TARGET |
|
|
|
|
否 |
是 |
是 |
是 |
是 |
是 |
否 |
否 |
PARAMETER |
|
|
|
|
|
是 |
是 |
否 |
否 |
否 |
否 |
否 |
PUBLIC |
|
|
|
|
|
|
否 |
否 |
否 |
是 |
是 |
是 |
PRIVATE |
|
|
|
|
|
|
|
否 |
否 |
是 |
是 |
是 |
INTENT |
|
|
|
|
|
|
|
|
是 |
否 |
否 |
否 |
OPTIONAL |
|
|
|
|
|
|
|
|
|
否 |
是 |
否 |
SAVE |
|
|
|
|
|
|
|
|
|
|
否 |
否 |
EXTERNAL |
|
|
|
|
|
|
|
|
|
|
|
否 |
有两类数据对象在确定其存储空间时,会产生非常复杂的现象,这就是数组和字符型对象。因为这两种数据对象在某些情况下,不能预先确定它的存储空间,为了解决这个问题,FORTRAN干脆提供一种动态数据对象的概念,可以专门用来描述在特定情形下,存储空间总是发生变化的数据对象。
动态数据对象可以只是在过程或过程界面声明,这样只有在过程被进入的时候,动态数据对象的存储空间才开辟出来,而当过程完成运行后,它们所占据的存储空间就消失了,它们可以被开辟成和过程里的变量一样大的空间,就能在每次调用时量体裁衣,获取适当大小的存储空间。这样既能满足这些对象对于存储空间的需要,又能在不使用它们的时候,有效地释放空间。
两种动态数据对象为:
● 任意类型的动态数组
对于数组来说,发生动态变化的是每个维度上的宽度,所采取的表达形式是把数组每个维度上的界表示为一个包含变量的表达式,这样当数组取可分配数组或指针数组的形式时,这样当程序运行进入到其所在过程时,它的界才被确定下来。
● 字符型对象
对于字符型对象来说,发生动态变化的是它的字符长度,所采取的表达形式是把字符型对象的长度表示为一个包含变量的表达式,这样当程序运行进入到其所在过程时,它的长度才被确定下来。注意作为动态数据对象的字符型对象不是哑变量。
除了在类型声明当中包含的默认初始化之外,动态数据对象不能被初始化或被保留。
【例7-55】
SUBROUTING SWAP_ARRAYS (X, Y, X_NAME, Y –NAME)
REAL, DIMENSION(:), INTENT(INOUT) :: X,Y
CHARACTER (LEN= *), INTENT(IN) :: X_NAME, Y_NAME
REAL C (SIZE(X))
CHARACTER (LEN= LEN(X_NAME) + LEN(Y_NAME) +20) MESSAGE
C=X
X=Y
Y=C
MESSAGE = X_NAME // “and” //Y_NAME// “ are swapped.”
PRINT *, MESSAGE
END SUBROUTING SWAP_ARRAYS
其中C是一个动态数组,而MESSAGE是一个动态字符型对象.
NAMELIST语句能够用来给一系列对象的集合命名,然后就可以在一些输入输出语句当中通过引用该名称而达到引用相应的对象集合的目的。
NAMELIST语句的句法形式(R544)为:
NAMELIST / namelist-group-name / variable-name-list &
[[ ,]/ namelist-group-name / variable-name-list ]…
NAMELIST语句的一般规则如下:
● 变量名称列表(variable-name-list)当中的变量不能是如下几种情形:
•带非常量界的数组哑变量
•具有待定字符长度的变量
•动态对象
•指针
•在任意层次上包含指针成员的派生类型对象
•可分配数组
•以上对象的任意子对象
● 如果整个名称列表集合的名称具有PUBLIC属性,那么它的每一个元素都不能具有PRIVATE属性,每个元素也不能包含具有PRIVATE属性的成员。
● 在NAMELIST里的对象的排列顺序,决定了在输出时值的排列顺序。
● 一个名称列表集合名称可以出现在一个作用域单位里的多条NAMELIST语句里面。
● 一个变量可以是多个名称列表集合的元素。
● 一个变量在进入变量名称列表之前,必须在同一个作用域里被说明了类型,种别参数以及形状,或者是通过隐式规则被默认具有相应的类型属性,而当它在随后的可能的类型说明语句当中出现时,必须与其默认隐式规则所规定的类型属性保持一致。
【例7-56】
NAMELIST / X_LIST / I, J, K, L, M, N
NAMELIST / Y_LIST / A, B, C
对于FORTRAN这样的高级语言而言,一般说来很少提供对程序的物理底层的控制,但是由于数据对象的物理存储空间的问题,对于程序的空间利用效率有很大的影响,所以FORTRAN还是提供了一些语句可以用来控制数据对象的物理存储。
FORTRAN里面用来控制物理存储单位,存储的顺序,以及对存储单位的共享的语句有:
● COMMON
● EQUIVALENCE
● SEQUENCE
在FORTRAN的早期版本里,COMMON和EQUIVALENCE这两个语句提供了足够强大的控制存储空间的功能。COMMON专门用来处理不同程序单位之间的数据共享问题,而EQUIVALENCE则用来处理多个对象共享一个存储空间的问题。但是正是因为这两个语句的功能过于强大,使得它们的滥用会导致对程序的理解和维护变得非常困难。
在FORTRAN的现代版本里,为了解决这个问题,使用了更加明确而专门的方法来获得对存储空间的控制,诸如模块,指针,可分配数组,动态数据对象都可以非常有效而清晰地进行存储空间管理,因此上面的两条语句的作用逐渐减小。同时在FORTRAN90里面,还引入了SEQUENCE语句,用来定义结构的成员存储顺序。
关于存储单位与存储顺序的一个中心概念就是存储关联,这个概念能让我们理解COMMON和EQUIVALENCE这两个语句的运行机制。但是FORTRAN的存储关联并不对实际的物理内存分配作出要求,只是在功能层面的描述。
在早期的FORTRAN版本里,只存在两种非常明确的存储单位,即用于存储数值的存储单位,和用于存储字符的存储单位,那时侯任何的数据对象都被归结为这两种存储形式。
在FORTRAN的现代版本里,引入了很多非默认类型,派生类型以及指针,显然这些数据对象已经无法只使用两种存储单位了,它们的结构的复杂性甚至使得靠增加存储单位的种类也无法很好地满足这些经常表现出动态性质的数据对象对存储的要求。因此FORTRAN的解决方法就是提出一种统一的“不定存储空间”,每一个非传统对象都可以认为是占据了一个“不定存储空间”,但是实际上对于不同的种类,它们的物理存储空间是非常不同的。
对于那些非传统数据对象,根据它们的类型定义是否包含SEQUENCE语句,而分为两种:
● 非序列结构。
具有这种结构的对象占据一个单独的不定存储空间单位,这种单位的实际物理存储空间常常对于不同的对象都是非常不同的。例如一个十六位精度的实型数值与一个具有最小长度的逻辑型数据,尽管它们都声称占据了一个单独的不定存储空间单位,但它们的实际物理空间的大小是非常不同的。
● 序列结构。
所谓序列结构是由一系列具有明确的类型与属性的数据结构组成的序列,其中的每个成员都占据了一个存储单位。
序列结构分为三种:
·数值序列结构。
只包含默认类型的数值和逻辑型对象。
·字符序列结构。
只包含默认类型的字符型对象。
·序列结构。
包含各种成员的混合结构,例如占据数值存储单位,字符存储单位,不定存储空间单位的各种成员。
下面的表7-4列出了各种不同的类型与属性的数据对象所占据的存储单位。
表7-4不同的类型与属性的数据对象所占据的存储单位
对象的类型与属性 |
存储单位 |
默认整型 |
1个数值存储单位 |
默认实型 |
1个数值存储单位 |
默认逻辑型 |
1个数值存储单位 |
双精度 |
2个数值存储单位 |
默认复型 |
2个数值存储单位 |
长度1的默认字符型 |
1个字符存储单位 |
长度s的默认字符型 |
S个字符存储单位 |
非默认整型 |
1个不定存储单位 |
默认实型与双精度实型之外的实型 |
1个不定存储单位 |
非默认逻辑型 |
1个不定存储单位 |
非默认复型 |
1个不定存储单位 |
长度1的非默认字符型 |
1个不定存储单位 |
长度s的非默认字符型 |
S个不定存储单位 |
非序列结构 |
1个不定存储单位 |
数值序列结构 |
n个数值存储单位(包含n个成员) |
字符序列结构 |
n个字符存储单位(包含n个成员) |
序列结构 |
1个不定存储单位 |
具有指针属性的任意类型 |
1个不定存储单位 |
具有维度属性的任意固有或序列类型 |
数组的尺度乘该类型存储单位的数目(按照数组元素顺序) |
具有维度属性的任意非固有或非序列类型 |
非指定个不定存储单位 |
具有指针属性与维度属性的任意类型 |
1个不定存储单位 |
所谓存储序列就是一个存储单位的排序。一个存储单位可以是数组的一个元素,字符变量里的字符,序列结构里的成员,或者是公用块里的变量。一个存储序列的序列构成一个复合的存储序列。这个复合存储序列里面存储单位的排序保持每个层次的序列顺序不变,同时忽略0长度的序列。
当两个不同对象共享了部分存储时,被称为存储关联。通过关联就可以使得多个变量共享存储空间。
如果两个对象具有相同的存储序列,那么它们称为完全关联。
如果两个对象共享部分存储空间而不是完全关联,那么它们称为部分关联。