完成了数据描述的任务之后,一个程序就进入了数据处理的阶段。也就是一系列的以数据为对象的操作语句,访问经过声明的数据对象,施加计算处理之后,获得新的数据结果。
FORTRAN语言里面访问数据对象然后返回值的一个基本指令形式是表达式,而通过表达式所得到的计算结果被用来赋值给某个变量,完成这个操作的是赋值语句。本章与下一章我们将讨论表达式与赋值语句这两个相续的改变机器的数据状态的基本操作。
一个科学计算程序的基本任务就是进行计算,而计算的最基本的单位就是表达式。FORTRAN作为一门最早的高级语言,其初衷就是公式翻译(FORMULA TRANSLATION),即希望运用这个语言,能够把数学公式最好是原封不动地搬到FORTRAN语言的环境里,而计算机能够忠实地理解公式的含义,并进行公式所要求的运算。
当然,要求把能够把数学公式原封不动地搬到FORTRAN语言的环境里,就能够被FORTRAN理解是不可能的,因为首先是公式的输入受到键盘的限制,然后作为一种程序语言,我们在第4章已经讨论过,它所使用的字符集必定是有限的,而数学符号的集合则基本上是开放性的。
而从计算的实质上来考虑,我们知道计算机在物理层面所能够做到的计算其实是非常简单的加减法而已,因此我们希望运用计算机来完成任意的计算任务,所面临的挑战,正是如何把我们的实际计算任务分解为等价的简单计算过程的组合,FORTRAN作为一种语言,显然只能在很初步的层次上完成这个任务,即直接在语言里使用数学基本运算,例如:加法,减法,乘法,幂运算等,然后在此基础上,再定义一些基本的数学函数作为语言的固有函数,可以供人直接引用,对它的解释则完全交给FORTRAN编译器。FORTRAN所能够做到的基本上也就到此为止了。更进一步的计算的分解任务,则必须由程序来完成。因此FORTRAN能够做到的就是尽量以符合数学的书写习惯的方式来规范表达式的格式。
一般说来,我们把表达式理解为语句(甚至是程序)的语法结构的单位。在其他的语言里,表达式同样具有非常关键的语法地位,例如在C语言中,表达式组成了允许每一语句改变机器状态的基本操作。在LISP语言这样的高级应用语言中,表达式构成了驱动程序执行的基本顺序控制。而在FORTRAN这样的以科学计算为主要目的的语言当中,表达式更是构成了完成计算任务的基本成分。
本章的讨论就是围绕着表达式的构造如何贴切地合乎我们对于计算任务的要求。分为三个部分:
● 有效合法的表达式的格式。
也就是构造表达式的语法。按照我们在前面经常根据语法规则来构造语言所获得的经验,合乎格式的语句不一定是有实际意义的语句,因此我们接着就需要讨论对表达式的理解问题,即:
● 对表达式的解释。
可以理解为表达式的语义,也就是一种规范,按照这种规范,计算机读入合法的表达式之后,能够无歧义地转化为一系列指令,从而采取相应的动作。
计算机对表达式的含义的理解建立在表达式的构造语法的规范性上,一旦完成这个步骤,接着就是完成表达式的最终任务:
● 表达式的计算过程。
计算机对于表达式的实际计算过程未必与人的计算过程一致,对于计算机来说,最为重要的是根据机器计算的特点,对计算过程进行优化,相应的作为程序作者,我们需要了解机器的优化原则,以便在程序写作与算法设计上配合机器的计算优化能力。
FORTRAN表达式的构造,需要解决两大问题:
● 构造算元与算符的规则;
● 运算优先级的规则。
在构造合法的FORTRAN表达式的过程中,最主要的问题就是如何在FORTRAN有限的构造表达式的规则下,能够构造具有开放性的数学公式。
由于一个表达式的基本成分就是算元,算符和括号,因此上面的问题可以分为两个部分:
·在固有算元类型的基础上,能够构造派生类型的算元;
·在固有算符的基础上,能够扩展固有算符的适用范围,能够构造派生类型的算符。
因此所谓构造表达式的规则,除了规定固有算元与固有算符之外,还必须给出对固有算元与固有算符的扩展与派生定义的规则。
接下来需要考虑的问题,就是如何表达运算优先级,也就是括号所具有的功能。
在数学运算的过程当中,运算优先级是一个很自然的问题,在数学公式的写作中,我们使用了很多隐含的规则,基本能够保证数学公式的读者不会产生歧义,把数学公式转换到FORTRAN语言当中,就不能够再依靠那些隐含的规则,而是需要把那些规则明确的形式化出来,有时候还得制定适应于机器语言的独特规则。
尽管我们可以使用尽可能多的括号来做到明确的优先级描述,但是从方便写作的角度来看,在不导致歧义的前提下,可以通过形式规则来省略很多括号。
本节可以说是书写表达式的语法。从语言的角度来看,我们前面章节所讨论的数据对象,可以说是语言的词汇,而且是语法意义上比较简单词汇,即使很多数据对象可以具有非常复杂的数据结构,但在语法意义上,则仍然可以看成是单词。但表达式就不同了,它所隐含的指令性质使得它可以看成是复合性质的词汇。实际上表达式是访问程序中的数据对象并返回值的函数。这就要求它具有比数据对象更为复杂的语法结构。
表达式包含三个基本成分:
● 算元;
● 算符;
● 括号。
其中算元具有最基本的实质含义,因此最简单的表达式就是一个常量,或一个变量。
【例8-1】 下面的例子都可以看成是最简单的表达式:
2.7183 !一个实型常数
A !一个标量变量
X !一个数组变量
.TURE. !一个逻辑型常量
X(I) !作为数组X的元素的变量
X(10:100:5) !作为一个数组片断的变量
A%B !作为结构A的成员的变量
X(I)(K:K+100) !作为数组元素X(I)的子串的变量
遵循变量的数学精神,我们可以把表达式看成是一个递归结构,即表达式里的变量总是可以代表任意的由变量构成的结构,或者说表达式,因此可以如下规定算元的构成。
一个算元可以是如下形式的表达式:
● 一个常量或常量的子对象;
● 一个变量;
● 一个数组构造器;
● 一个结构构造器;
● 一个函数引用;
● 另一个括号里的表达式。
注意另一个表达式!,如果使用同一个表达式,必然导致死循环。
【例8-2】 下面是算元的例子:
X !
Y(3)
Z(10:100)
(X+SIN(Y))
(/5.52,4031/)
EULER(5,9)
RT
根据算符所作用的算元数目,算符分为一元算符与二元算符。
下面的表8-1给出了FORTRAN的固有算符,除此之外,用户可以运用函数子程序来给出自定义算符,然后使用一个名称来标志该自定义算符,在FORTRAN语言里,则还需要在名称的两边分别加上一个句点,以便FORTRAN辨识为自定义算符。
无论是固有算符还是自定义算符,总是分为一元算符与二元算符。
一元算符作用于一个算元,形式为:
operator x
即算符写在算元之前。
【例8-3】
+ A
- G
.NOT. B
上面的一元算符里面,+和-都可以成为二元算符,而.NOT.则是唯一的只能作一元算符的固有算符。
加号+运算实际上并不影响算元的值。
二元算符作用于2个算元,形式为:
x operator y
即算符写在2个算元之间。
【例8-4】
A+B
D**3
X .AND. Y
A .GT. B
C .NE. D
PROGRAMER // CALCULATE
A == B
C-D
算符分为两类:
● 固有算符。
所谓固有算符就是直接使用相应符号,能够被FORTRAN编译器所辨识的算符。
下面的表8-1给出了FORTRAN的固有算符以及相应的算元类型。
表8-1 FORTRAN的固有算符以及相应的算元类型
算符类别 |
固有算符 |
算元类型 |
算术运算 |
+,-(一元算符) **,*,/,+,-(二元算符) |
任意数值类型与任意种别参数的数值的组合 |
字符运算 |
// |
具有相同种别参数的任意长度的字符串 |
关系运算 |
.EQ.,.NE., ==,/= |
两个算元或者同时是任意数值类型与任意种别参数的数值,或者同时是具有相同种别参数的任意长度的字符串 |
关系运算 |
.GT.,.GE.,.LT.,.LE., >,>=,<,<= |
两个算元或者同时是除了复型之外的任意数值类型与任意种别参数的数值,或者同时是具有相同种别参数的任意长度的字符串 |
逻辑运算 |
.NOT.(一元算符) .AND.,.OR.,.EQV.,NEQV. (二元算符) |
同时是任意种别参数的逻辑型数据的组合 |
关系算符==,/=,>,>=,<,<=是为了向数学公式的写法靠近,而在FORTRAN里面最新引入的算符写法,分别等价于.EQ.,.NE.,.GT.,.GE.,.LT.,.LE.。
● 自定义算符。
显然上面那些固有算符不够用,所以为了获得算符表示的开放性,FORTRAN约定了通过函数子程序定义自定义算符的规则。
首先自定义算符必须拥有一个名称,而这个名称的写法必须满足一定的形式规则,以便FORTRAN编译器的辨识。
自定义算符的形式为:
.XXX… .
即在两个句点之间有n个字母,n不大于31。
中间的字母串最好是一个表达该运算含义的英文单词。这个单词不能与固有运算符或者逻辑常量里面已经使用了的单词重复。
自定义算符可以是基于已经定义的算符和固有算符,完全重新定义的算符,因此需要给出一个新的名称,也可以是对固有算符的作用算元的类型的扩展,这时仍然可以使用固有算符的符号,但是扩展定义需要预先给出。
一元自定义算符具有最高的运算优先级,而二元自定义算符具有最低的运算优先级。
所谓构造表达式的规则,就是一系列的语句规则,按照这个规则把算元和算符组合起来得到的就是合法的表达式。当然这里包含了一定的任意性,即只要按照这个规则构造出来的表达式都是被FORTRAN认可的。
语法规则除了给出算元与算符的顺序之外,还能够表达运算顺序,而这个运算顺序是以运算优先级形式给出的,当然FORTRAN表达式的运算优先级必须和我们的数学公式里的运算优先级一致。
直观来看,表达式就是变量的运算结构,而变量本身具有递归的含义,因此表达式的定义必然是具有递归的结构。
也就是首先我们定义最底层的基元,即最简单的表达式,然后对基元的运算构成更加复杂的表达式,而递归的体现就是在基元里面包括括号里面的表达式。
基元的形式定义(R701)为:
constant
constant-subobject
variable
array-constructor
structure-constructor
function-reference
(expression)
以上任何一种对象都可以成为基元。
所谓常量子对象(constant-subobject),就是一个父结构为常量的子对象。
作为基元的变量,不能是哑尺度数组,或者一个哑尺度数组的片断,除非数组的最后一个下标已经明确给出为一个标量下标,或上界确定的下标三元组,或下标向量。
【例8-5】 基元的例子如下:
3.1416 |
实型常量 |
PI |
命名常量 |
LENGTH |
命名常量 |
‘WHOLE’(I:I) |
常量子对象 |
X |
变量 |
X(K) |
数组元素 |
X(:,1:K) |
在最后一维具有上界的哑尺度数组 |
STRING(I:J) |
子串 |
(/I,5,100/) |
数组构造器 |
BESSEL1(A,B) |
结构构造器 |
F(X) |
函数引用 |
(‘WHOLE’(I:I)//BUT) |
括号里的表达式 |
作为一个命名常量,PI明显地表示圆周率,使用这类含义明确的英文单词来表示常量是一个很好的习惯。
当LENGTH具有PARAMETER属性,或出现在PARAMETER语句当中时,同样是一个命名常量。
‘WHOLE’(I:I)实际上是一个常量子对象,尽管其中I可能是一个变量,但它的父结构是一个常量。BESSEL1(A,B), F(X)都属于自定义类型对象,它们在程序里出现时,都需要预先加以定义。
一个已经自定义的一元算符后接一个算元构成一个自定义一元表达式,句法形式(R703)为:
[defined-operetor]primary
其中自定义算符(defined-operetor)的句法形式(R704)为:
.letter[letter]… .
注意自定义算符是可选的,即单独的基元也可以构成一元表达式。
构成自定义算符的字符数目不能够超过31。
自定义算符不能与固有算符以及逻辑字面常量雷同。
【例8-6】
.ABEL.G |
自定义一元表达式,.ABEL.为自定义算符 |
G |
单独的基元也可以构成一元表达式 |
幂运算表达式的句法形式(R705)为:
defined-unary-expression[**exponentiation-expression]
以上形式表明了幂运算表达式(exponentiation-expression)是右边递归的,即幂运算算符右边的幂运算表达式正是上面所定义的表达式本身,这意味着会出现多个幂运算连在一起的情形,这时的优先级是从右到左,即X**Y**Z被解释为X**(Y**Z)。
注意其中的幂运算表达式与幂运算算符都是可选的,即单独的自定义一元表达式从语法角度而言,也是幂运算表达式。
【例8-7】
X**Y |
幂运算表达式 |
X**Y**Z |
表示从右到左优先级的幂运算表达式 |
.ABEL.G |
自定义一元表达式也是幂运算表达式 |
G |
单独的基元也可以构成幂运算表达式 |
乘法表达式的算元为*和/,句法形式(R706)为:
[multiplication-expression*]exponentiation-expression
multiplication-expression / exponentiation-expression
以上形式表明了乘法表达式(multiplication-expression)是左边递归的,即乘法算符左边的乘法表达式正是上面所定义的表达式本身,这意味着会出现多个乘法运算连在一起的情形,这时的优先级顺序是从左到右,即X*Y*Z被解释为(X*Y)*Z。
在以后的二元运算里,除了关系运算,同级优先级顺序都是从左到右。
注意使用算符*的乘法运算表达式与乘法运算算符都是可选的,即单独的幂运算表达式从语法角度而言,也是幂运算表达式。
【例8-8】
X*Y |
乘法运算表达式 |
X*Y*Z |
表示从左到右优先级顺序的乘法运算表达式 |
X / Y |
乘法运算表达式 |
X / Y / Z |
表示从左到右优先级顺序的乘法运算表达式 |
X**Y |
幂运算表达式也是乘法运算表达式 |
.ABEL.G |
自定义一元表达式也是乘法运算表达式 |
G |
单独的基元也可以构成乘法运算表达式 |
加法表达式的算元为+和-,句法形式(R707)为:
[summation-expression + ] multiplication -expression
summation-expression - multiplication –expression
+ multiplication -expression
- multiplication –expression
多个加法运算的优先级顺序是从左到右,例如X+Y+Z被解释为(X+Y)+Z。
注意使用算符+的加法运算表达式与加法运算算符都是可选的,即单独的乘法运算表达式从语法角度而言,也是加法运算表达式。
【例8-9】
X+Y |
加法表达式 |
X-Y+Z |
表示从左到右优先级顺序的加法表达式 |
-X-Y-Z |
表示从左到右优先级顺序的加法表达式 |
-X |
使用一元算符-的加法表达式 |
+X |
使用一元算符+的加法表达式 |
X*Y |
加法表达式 |
X / Y / Z |
加法表达式 |
X**Y |
幂运算表达式也是加法表达式 |
.ABEL.G |
自定义一元表达式也是加法表达式 |
G |
单独的基元也可以构成加法表达式 |
串联表达式的算元为//,句法形式(R711)为:
[concatenation-expression // ] summation-expression
【例8-10】
X//Y |
串联表达式 |
X//Y//Z |
表示从左到右优先级顺序的串联表达式 |
X+Y |
加法表达式也是串联表达式 |
-X-Y-Z |
串联表达式 |
-X |
串联表达式 |
X*Y |
串联表达式 |
X / Y / Z |
串联表达式 |
X**Y |
幂运算表达式也是串联表达式 |
.ABEL.G |
自定义一元表达式也是串联表达式 |
G |
单独的基元也可以构成串联表达式 |
比较表达式的算元为关系算符,句法形式(R713)为:
[concatenation-expression relation-operator ] concatenation-expression
关系算符==,/=,>,>=,<,<=在任何情况下总是分别等价于
.EQ.,.NE.,.GT.,.GE.,.LT.,.LE.。
注意关系表达式的定义不具有递归性,即关系表达式本身不出现在上面的形式定义当中,所以不会出现多个关系算符连在一起的情形。
【例8-11】
X .NE. Y |
关系表达式 |
X <=Y |
关系表达式 |
X//Y |
关系表达式 |
X+Y |
加法表达式也是关系表达式 |
-X |
关系表达式 |
X / Y / Z |
关系表达式 |
X**Y |
幂运算表达式也是关系表达式 |
.ABEL.G |
自定义一元表达式也是关系表达式 |
G |
单独的基元也可以构成关系表达式 |
非运算表达式的算元为.NOT.,是一元算符,句法形式(R715)为:
[.NOT. ] comparision-expression
注意非运算表达式的定义不具有递归性,即非运算表达式本身不出现在上面的形式定义当中,所以不会出现多个算符.NOT.连在一起的情形。
【例8-12】
.NOT. X |
非运算表达式 |
X .NE. Y |
关系表达式也是非运算表达式 |
X <=Y |
非运算表达式 |
X//Y |
非运算表达式 |
X+Y |
加法表达式也是非运算表达式 |
X / Y / Z |
非运算表达式 |
X**Y |
幂运算表达式也是非运算表达式 |
.ABEL.G |
自定义一元表达式也是非运算表达式 |
G |
单独的基元也可以构成非运算表达式 |
与运算表达式的算元为.AND.,是二元算符,句法形式(R716)为:
[conjunct-expression .NOT. ] not-expression
注意与运算表达式的定义具有左边递归性,即与运算表达式本身出现在上面的形式定义当中,所以会出现多个算符.AND.连在一起的情形,这时的优先顺序是从左到右。即X .AND. Y.AND. Z的优先顺序是(X .AND. Y).AND. Z。
【例8-13】
X .AND. Y |
与运算表达式 |
X .AND. Y.AND. Z |
优先顺序是从左到右的与运算表达式 |
.NOT. X |
非运算表达式也是与运算表达式 |
X .NE. Y |
关系表达式也是与运算表达式 |
X <=Y |
与运算表达式 |
X//Y |
与运算表达式 |
X+Y |
加法表达式也是与运算表达式 |
X / Y / Z |
与运算表达式 |
X**Y |
幂运算表达式也是与运算表达式 |
.ABEL.G |
自定义一元表达式也是与运算表达式 |
G |
单独的基元也可以构成与运算表达式 |
或运算表达式的算元为.OR.,是二元算符,句法形式(R717)为:
[disjunct-expression .OR. ] conjunct -expression
注意或运算表达式的定义具有左边递归性,即或运算表达式本身出现在上面的式定义当中,所以会出现多个算符.OR.连在一起的情形,这时的优先顺序是从左到右。即X.OR. Y.OR. Z的优先顺序是(X.OR. Y).OR. Z。
【例8-14】
X.OR. Y |
或运算表达式 |
X.OR. Y.OR. Z |
优先顺序是从左到右的或运算表达式 |
X .AND. Y |
与运算表达式也是或运算表达式 |
.NOT. X |
非运算表达式也是或运算表达式 |
X .NE. Y |
关系表达式也是或运算表达式 |
X <=Y |
或运算表达式 |
X//Y |
或运算表达式 |
X+Y |
加法表达式也是或运算表达式 |
X / Y / Z |
或运算表达式 |
X**Y |
幂运算表达式也是或运算表达式 |
.ABEL.G |
自定义一元表达式也是或运算表达式 |
G |
单独的基元也可以构成或运算表达式 |
等价运算表达式的算元为.EQV.或者是.NEQV.,是二元算符,句法形式(R718)为:
[equivalence-expression .EQV. ] disjunct -expression
equivalence-expression .NEQV. disjunct –expression
注意等价运算表达式的定义具有左边递归性,即等价运算表达式本身出现在上面的形式定义当中,所以会出现多个算符.EQV. 或与.NEQV.连在一起的情形,这时的优先顺序是从左到右。即X.NEQV. Y.EQV. Z的优先顺序是(X.NEQV. Y).EQV. Z。
【例8-15】
Y.EQV. Z |
等价运算表达式 |
X.NEQV. Y |
等价运算表达式 |
X.NEQV. Y.EQV. Z |
优先顺序是从左到右的等价运算表达式 |
X.OR. Y |
或运算表达式也是等价运算表达式 |
X .AND. Y |
与运算表达式也是等价运算表达式 |
.NOT. X |
非运算表达式也是等价运算表达式 |
X .NE. Y |
关系表达式也是等价运算表达式 |
X <=Y |
等价运算表达式 |
X//Y |
等价运算表达式 |
X+Y |
加法表达式也是等价运算表达式 |
X / Y / Z |
等价运算表达式 |
X**Y |
幂运算表达式也是等价运算表达式 |
.ABEL.G |
自定义一元表达式也是等价运算表达式 |
G |
单独的基元也可以构成等价运算表达式 |
最一般的表达式的句法形式(R723)为:
[ expression defined-operator ] equivalence-expression
注意表达式(expression)的定义具有左边递归性,即表达式本身出现在上面的形式定义当中,所以会出现多个相同优先级的算符连在一起的情形,这时的优先顺序是从左到右。
【例8-16】
X .CROSS. Y |
表达式 |
X .CROSS. Y .CROSS. Z |
优先顺序是从左到右的表达式 |
Y.EQV. Z |
等价运算表达式也是表达式 |
X.NEQV. Y |
等价运算表达式也是表达式 |
X.NEQV. Y.EQV. Z |
等价运算表达式也是表达式 |
X.OR. Y |
或运算表达式也是表达式 |
X .AND. Y |
与运算表达式也是表达式 |
.NOT. X |
非运算表达式也是表达式 |
X .NE. Y |
关系表达式也是表达式 |
X <=Y |
表达式 |
X//Y |
表达式 |
X+Y |
加法表达式也是表达式 |
X / Y / Z |
表达式 |
X**Y |
幂运算表达式也是表达式 |
.ABEL.G |
自定义一元表达式也是表达式 |
G |
单独的基元也可以构成表达式 |
上面我们已经完备地给出了表达式的语法形式,可以明显地看到从最简单的表达式到更为复杂的表达式,具有一种层次结构,这种依据优先级顺序给出的层次结构是我们构造复杂表达式所必须遵循的规则,也是计算机理解表达式时所依据的规则。下面我们给出总结。
【例8-17】下面是一个表达式里的运算优先级降低的例子:
X !基元
.INVERSE. X !自定义一元运算
X**Y !幂运算表达式
X*Y !乘法运算表达式
-X !加法运算表达式
X//Y !连接运算表达式
X .EQ. Y !比较运算表达式
.NOT. X !非运算表达式
X .AND. Y !并运算表达式
X .OR. Y !或运算表达式
X .EQV. Y !等价运算表达式
X .CROSS. Y !一般表达式
下表8-2给出各种一般形式的表达式的运算优先级:
表8-2一般形式的表达式的运算优先级
优先级向下递降的运算项 |
运算的定义 |
优先级 |
基元 |
常量 常量子对象 变量 数组构造器 函数引用 (表达式) |
高
|
自定义一元运算表达式 |
[自定义算符]运算元 |
|
幂运算表达式 |
自定义一元运算表达式[**幂运算表达式] |
|
乘法运算表达式 |
[乘法运算表达式*]幂运算表达式 乘法运算表达式 /幂运算表达式 |
|
加法运算表达式 |
[加法运算表达式+]乘法运算表达式 加法运算表达式 - 乘法运算表达式 + 加法运算表达式 - 加法运算表达式 |
|
连接运算表达式 |
[连接运算表达式 //]加法运算表达式 |
|
比较运算表达式 |
[比较运算表达式关系算符] 比较运算表达式 |
|
非运算表达式 |
[.NOT.] 比较运算表达式 |
|
并运算表达式 |
[并运算表达式 .AND.] 非运算表达式 |
|
或运算表达式 |
[或运算表达式 .OR.] 并运算表达式 |
|
等价运算表达式 |
[等价运算表达式 .EQV.] 或运算表达式 等价运算表达式 .NEQV. 或运算表达式 |
|
一般表达式 |
[表达式自定义算符] 等价运算表达式 |
低 |
下表8-3给出各类算符在同样的优先级的情形下的运算优先级顺序:
表8-3各类算符在同样的优先级的情形下的运算优先级顺序
算符类型 |
算符 |
同优先级下的运算顺序 |
优先级 |
自定义 |
一元自定义算符 |
无 |
最高 |
数值运算 |
** |
右到左 |
|
数值运算 |
*或 / |
左到右 |
|
数值运算 |
一元算符+或- |
无 |
|
数值运算 |
二元算符+或- |
左到右 |
|
字符运算 |
// |
左到右 |
|
关系运算
|
.EQ.,.NE.,.LT.,.LE.,.GT.,.GE., ==,/=,<,<=,>,>= |
无 |
|
逻辑运算 |
.NOT. |
无 |
|
逻辑运算 |
.AND. |
左到右 |
|
逻辑运算 |
.OR. |
左到右 |
|
逻辑运算 |
.EQV.或.NEQV. |
左到右 |
|
自定义 |
二元自定义算符 |
左到右 |
最低 |
表8-3
自定义一元算符在所有算符与所有同优先级算符当中,具有最高的优先级。而自定义二元算符在所有算符与所有同优先级算符当中,具有最低的优先级。
表中的“无”表示该算符不会连续出现,可以看到具有这个特点的是自定义一元算符,关系算符,和逻辑非算符,它们的优先级顺序只能使用括号来说明。
固有运算的完整含义是,除了算符是属于固有算符,因此可以直接使用相应符号而不需要预先说明之外,算符所作用的算元也必须属于固有数据类型。不同的算符所能够作用的算元类型是不同的。下表8-4给出了不同的算符所能够作用的算元类型:
表8-4 不同的算符所能够作用的算元类型
固有算符 |
算元1的类型 |
算元2的类型 |
计算结果的类型 |
一元算符+,- |
|
I,R,Z |
I,R,Z |
二元算符+,-,*,/,** |
I R Z |
I,R,Z I,R,Z I,R,Z |
I,R,Z R,R,Z Z,Z,Z |
串联算符// |
C |
C |
C |
.EQ.,.NE., ==,/= |
I R Z C |
I,R,Z I,R,Z I,R,Z C |
L,L,L L,L,L L,L,L L |
.LT.,.LE.,.GT.,.GE., ==,/=,<,<=,>,>= |
I R C |
I,R I,R C |
L,L L,L L |
.NOT. |
|
L |
L |
.AND.,.OR.,.EQV.,NEQV. |
L |
L |
L |
表中I表示整型,R表示实型,Z表示复型,C表示字符型,L表示逻辑型。
自定义运算的完整含义是:
● 如果算符形式上是固有算符,那么它的算元必定不属于表8-3里面所规定的类型;
● 所含有的算符为自定义算符。
自定义运算的一般句法形式(R703,R723)为:
intrinsic-unary-operator y
defined-operator y
x intrinsic-binary-operator y
x defined-operator y
其中自定义算符(defined-operator)需要在一个具有通用标识符OPERATOR的界面块里用一个函数子程序来加以单独的解释与计算。
如果自定义运算使用了固有算符的符号,意味着扩展了其中固有算符所作用的算元的类型范围,它的通用性需要在界面块里说明。
如果自定义运算使用了自定义算符,那么该自定义运算被称为扩展运算,所使用的自定义算符称为扩展算符。
作为表达式的计算结果,可以说表达式终究是有它的数据类型,种别参数,以及形状这些数据对象所具有的属性,那么表达式的这些属性终究是由它的基元的数据属性决定的。
由基元的数据属性最终得到表达式的数据属性,这中间需要完成全部的运算,每经过一次运算,结果的数据属性都可能随着新的算元的加入而发生改变,唯一可以确认的是运算的顺序,是严格按照算符的优先级顺序进行的,鉴于基元本身的可能的数据属性的多样化,下面分小节讨论表达式的的数据属性。
基元的数据属性如果已经直截了当地在声明语句当中给予了说明,那就不是我们在这里讨论的中心。
【例8-18】
INTEGER I(100,20)
REAL (8.5,6,4.4)
它们的数据属性已经显式地给出。
下面我们要讨论的基元类型是:
● 指针;
● 数组构造器;
● 结构构造器;
● 函数。
指针变量的类型,种别参数和秩都会在指针变量的声明语句当中予以说明。但是当指针为待定形数组时,它的形状就是由它的目标来决定。
【例8-19】
INTEGER,POINTER::X(:,:)
INTEGER,TARGET::Y(100,200)
其中X的形状就是由Y决定的,即(100,200)。
如果对指针使用固有函数NULL,那么它会返回一个非关联指针,而非关联指针没有形状,但是有秩。
当NULL()不带变量时,固有函数NULL返回的结果的类型,种别和秩由与结果关联的指针决定,跟NULL出现的场合有关,详见下表8-5:
表8-5固有函数NULL返回的结果
NULL()出现的场合 |
决定结果的类型,种别参数和秩的对象 |
指针赋值语句的右边 |
左边的对象 |
声明语句当中一个对象的初始化 |
该对象 |
一个成员的默认初始化 |
该成员 |
结构构造器 |
相应的成员 |
实元 |
相应的哑元 |
DATA语句 |
相应的对象 |
【例8-20】 下面是NULL后接变量的例子:
INTERFACE ADD
SUBROUTING S1(J,PI)
INTEGER J
INTEGER,POINTER::PI
END SUBROUTING S1
SUBROUTING S2(K,PR)
INTEGER K
REAL,POINTER::PR
END SUBROUTING S2
END INTERFACE
REAL,POINTER::REAL_KER
CALL ADD(10, NULL(REAL_KER))
…
数组构造器的类型,种别和形状由构造器的形式决定。
它的秩为1,而尺度等于其中元素的个数,它的类型和种别与其中任意一个元素的一致,因为它的所有元素的类型和种别都必定是相同的。
【例8-21】 给出一个构造器:
(/2.4_2, 5.9_1, 9.4_1, 6.4_1)
为实型,种别参数为2,尺度为4.
结构构造器的类型就是派生类型的类型名称,一个结构构造器总是标量,而结构不具有种别参数。
【例8-22】 给出下面的结构构造器:
SAMPLE(5.0, “ALPHA”)
它的类型就是SAMPLE.
函数的类型,种别和形状的决定有以下几种情形:
● 在引用函数的程序单位里对函数进行了隐式声明;
● 在引用函数的程序单位里对函数进行了显式声明;
● 函数具有显式界面(如果界面不是显式的,那么函数或者是外部函数,或者是语句函数)。
如果界面是显式的,属性的决定有如下几种情形:
● 引用函数的程序单位里的界面块里,有对函数的类型声明以及其他说明语句;
● 对内部过程或模块过程的类型声明以及其他说明语句说明了函数;
● 被引用的特定的固有函数的描述。
注意,由于固有函数和带界面块的函数可能是通用的,因此它们的属性由特定函数引用的实元决定。
【例8-23】
REAL FUNCTION FUN(A)
DIMENSION FUN(20,50)
上面的语句是给出了内部函数FUN的程序单位的一部分,那么要引用函数FUN(8.1),它的类型就是默认实型,形状为(20,50)。
【例8-24】
REAL(DOUBLE)A(30,50)
…
…COS(A)…
函数COS的界面在固有函数COS的定义里给出,这里引用的函数COS(A)为双精度实型,形状为(30,50)。
当函数是外部函数或为语句函数时,界面就是隐式的,这时形状总是标量,而其他属性则由默认隐式类型声明所规定,或者是由语句函数的显式声明决定。
【例8-25】
IMPLICIT REAL (SINGLE)(A)
…
… FUN(X)…
其中的FUN(X)就是一个单精度实型标量。
如果变量或函数是属于待定形或哑形数组,会有以下几种情形:
● 对于待定形数组,它的秩已经被声明,但是它的每个维度的尺度需要在运行ALLOCATE语句或指针赋值语句之后才能确定。
● 对于哑形数组,它的秩已经被声明,但是它的形状需要子程序运行之后给出。
● 对于指针来说,则需要由被关联到指针的目标来确定形状。
因此一般说来待定形或哑形数组,只能在运行时间才能确定形状。
对固有类型算元进行固有算符的运算的结果的类型,已经在表8-3给出,因此现在只需要讨论决定种别参数的规则。
运算分为两类:
● 非数值运算。
● 数值运算。
对于非数值运算有如下情形:
● 固有关系运算:种别参数为默认逻辑型。
● 固有逻辑运算:如果算元具有相同的种别参数,则结果与之保持一致;如果算元具有不同的种别参数,则由编译器决定。
● 固有字符运算: 算元与结果的种别参数必定保持一致,长度参数则是两个算元的长度的和。
对于数值运算,有如下情形:
● 一元运算:保持算元的种别参数不变。
● 二元运算:两个算元如果是不同数据类型,则按照表8-3决定;两个算元如果是相同数据类型与种别参数,则结果保持不变;如果两个算元都是整型,种别参数不同,那么结果取幂次范围大的,如果幂次范围一样大,则由编译器决定;如果两个算元都是实型或复型,种别参数不同,那么结果取精度大的,如果精度一样大,则由编译器决定。
对于二元数值运算,基本的规则可以理解为:如果把从整数到最高精度的复数按照集合包含关系排列起来,那么只要两个算元的种别参数不同,结果总是取所属集合更大的算元的种别参数。
上面讨论的是针对固有类型数据的固有运算,对于自定义运算,结果的类型以及种别参数由被引用的运算的界面块给出,也即在界面块里说明的作为自定义运算的函数名的类型以及种别参数。
【例8-26】
INTERFACE OPERATOR(.PLUS.)
TYPE(SET)FCN_SET_PLUS(X, Y)
TYPE(SET), INTENT(IN)::X,Y
END FUNCTION FCN_SET_PLUS
TYPE(ADD) FCN_SET_PLUS(X, Y)
TYPE(ADD) , INTENT(IN)::X,Y
END FUNCTION FCN_SET_PLUS
END INTERFACE
在得到上面的算符定义之后,如果取类型SET的对象A和B,那么表达式A.PLUS.B的类型也为SET,无种别参数;如果取类型ADD的对象A和B,那么表达式A.PLUS.B的类型也为ADD,无种别参数。
表达式形状的决定同样是从基元开始,按照算符的优先级顺序,逐步从底层到高层而决定的。
但是形状的决定比类型与种别参数的决定要简单,因为二元运算的2个算元的形状要求具有一定的关系:
● 二元固有运算的2个算元必须保证形状的一致性,也就是两个算元或者是相同形状的数组,或者至少有一个算元是标量。
● 对于二元自定义运算,则2个算元或者与定义的函数的相应哑元保持一致,或者相互也保持一致。其结果的形状自然也保持不变。
因此结果的形状的决定也就相应比较简单了。
● 一元运算不改变算元的形状。
● 如果基元是常量,变量,构造器,或函数,则结果的形状分别就是常量,变量,构造器,或函数名称。
● 如果基元是对固有函数NULL的引用,那么结果的形状就与基元无关。而由被关联到结果的指针决定。
● 结构构造器总是标量。
● 数组构造器总是秩为1,尺度等于元素数目的数组。
● 二元固有运算的算元只要有一个是数组,结果就是相应的数组,否则就是标量。
● 对于自定义运算来说,如果算元的形状与哑元保持一致,结果的形状就是定义运算的函数名称,或者如果定义的函数是基元性的,则结果的形状就是数组算元的形状。
【例8-27】
INTERFACE OPERATOR(//)
FUNCTION FCN_CONCAT(A,B)
CHARACTER (*,1) A
CHARACTER (*,2) B
CHARACTER (LEN(A)+LEN(B),2) FCN_CONCAT (SIZE(B))
END FUNCTION FCN_CONCAT
END INTERFACE
设X是种别参数为1,长度为30的字符型标量,Y是种别参数为2,长度为50的字符型数组,其形状为(25)。那么考虑X//Y的属性:由于算符//的界面里的FCN_CONCAT的类型声明就决定了表达式X//Y的结果为种别参数2的字符型,结果的长度为2个算元的长度之和,即80,结果的形状是秩为1,而尺度等于相应于哑元B的实元Y的尺度,也就是形状为(25)。
在大多数情形下,数组表达式的宽度并不需要特别的考虑,因为一致性的要求只涉及到每个维度的尺度。不过如果数组表达式是固有函数LBOUND和UBOUND的ARRAY变量的,数组表达式的宽度就需要加以考虑了。‘
固有函数LBOUND和UBOUND含有两个关键词变量:
● ARRAY,为数组表达式。
● 可选的DIM,为整型表达式。
如果变量DIM给出的话,那么函数LBOUND和UBOUND就分别返回由变量DIM决定的维度的下界与上界。
如果变量DIM不给出的话,那么函数LBOUND和UBOUND就分别返回由变量ARRAY决定的所有维度的下界与上界组成的秩为1的数组。
表达式除了用于赋值语句之外,还可能出现在许多特殊的场合,在那些特殊场合里的表达式是受到了各种限制的,比如:
● 出现在PARAMETER语句当中的表达式就被限制为常量表达式,起到初始化的作用。
这类表达式称为常量表达式;
● 在许多说明语句当中,充当数组界或者是字符长度值的也可能是一些整型表达式,它们在程序单位运行的时候,就会执行计算过程,给出数组界的值或字符长度值。这类表达式称为说明表达式。
本节我们就是分小节讨论这2类特殊的表达式的特殊用途。
常量表达式就是扩展常量或扩展常量通过固有算符构成的表达式。
所谓扩展常量的含义有如下几种情形:
● 字面常量,命名常量,常量子对象,其中子对象的每个下标,或片断下标,或子串范围的始点与终点都是常量表达式。
● 一个数组构造器,它的每个子表达式的基元都是常量表达式,或者是数组构造器的隐式do变量。
● 每个成员都是常量表达式的结构构造器。
● 在汇编时被计算的固有函数引用。
● 括号里的常量表达式。
要求固有函数引用在汇编时被计算,就排除了使用固有函数PRESENT,ASSOCIATED和ALLOCATED,并且要求函数引用的每个变量都是常量,或者至少在编译时,变量的种别参数与界已知。
例如在所有维度具有显式界的数组A,对于A的查询函数SIZE(A)在编译时是可以计算的,那么SIZE(A)*3就是一个常量表达式,可以作为扩展常量。
这样一个限制就排除了属于待定形数组,哑尺度数组,指针数组和可分配数组这类对象的变量。
有了以上限制之后,常量表达式就可以应用于任何可执行语句,而普通表达式是不能出现在任何可执行语句当中的。
【例8-28】 下面是常量表达式的合法例子:
3.141 |
实型字面常量 |
45 |
整型字面常量 |
-5.0_QUAD |
实型字面常量,其中QUAD为命名整型常量 |
5_SHORT |
整型字面常量,其中SHORT为命名整型常量 |
(/(K,K=0,100),50) |
数组构造器 |
BESSEL1(3.1, J) |
结构构造器,其中BESSEL为派生类型,J为命名整型常量 |
UBOUND(X,1)-10 |
固有查询函数的引用,X为显形数组 |
COS(X) |
固有函数,其中X为命名常量 |
KIND(X) |
固有函数,其中X为具有已知种别参数的实型变量 |
REAL(K-2) |
固有函数,其中K为命名整型常量 |
COUNT(X) |
固有函数,其中X为命名逻辑型常量 |
SIN(3.3) |
固有函数 |
SUM(X) |
固有变换函数, 其中X为命名整型数组常量 |
3*I+J**4/2.5 |
数值表达式,其中I,J为命名整型常量 |
所谓初始化表达式是具有如下限制的常量表达式:
● 幂函数的幂次只能是整数。
● 下标,片断下标,子串范围的始点与终点,结构构造器的成员,以及固有函数的变量都必须是初始化表达式。
● 数组构造器的元素都必须是初始化表达式,或者是隐式do对象,其中数组构造器值和隐式do参数是基元为初始化表达式或隐式do变量的表达式。
● 初始化表达式里的基本固有函数必须包含整型或字符型变量,并且必须返回整型或字符型结果。
● 初始化表达式里的固有变换函数必须是如下几种固有变换函数之一:
NULL,REPEAT,RESHAPE,
SELECTED_INT_KIND,SELECTED_REAL_KIND,TRANSFER, TRIM。
其中除了NULL之外,它们的变量都必须是初始化表达式。
这样实际上就是排除了以下的变换函数:
ALL,ANY,COUNT,CSHIFT,DOT_PRODUCT,EOSHIFT,MATMUL,MAXLOC,
MAXVAL,MINLOC,MINVAL,PACK,PRODUCT,SPREAD,SUM,TRANSPOSE,
UNPACK。
● 初始化表达式不能通过ALLOCATE语句给出,也不能通过指针赋值给出。允许使用固有查询函数,除非它的变量或者是初始化表达式或者变量的种别与界不能够通过查询获得。
● 括号里的任意子表达式都必须是初始化表达式。
在8.2.9.1节里的常量表达式例子里面,除了最后5个例子外,其他都是初始化表达式。最后5个例子之所以不是初始化表达式,是因为初始化表达式不允许包含返回实型结果的函数,不允许引用某些特定的变换函数,也不允许幂运算的幂次是非整数。
【例8-29】 下面是初始化表达式的其他形式的例子:
SIZE(2,X)+10 |
整型表达式,其中X为显形数组 |
KIND(5.0E+01) |
变量为常量的查询函数 |
SELECTED_REAL_KIND(33,100) |
变量为常量的查询函数 |
SELECTED_INT_KIND(10**K) |
变量为初始化表达式的查询函数,其中K为预先声明过的整型常量 |
初始化表达式使用在如下场合:
● 在PARAMETER语句或者附加了PARAMETER属性的类型声明语句当中,作为初始值跟在等号的后面。
● 在类型声明语句里,作为某项的初始值跟在等号的后面。
● 在DATA语句的取值列表里作为结构构造器的表达式。
● 在类型声明语句当中,作为种别参数值,这时它必须取整型标量。
● 作为如下固有转换函数的KIND哑元的实元:
AINT,ANINT,CHAR,CMPLX,INT,LOGICAL,NINT,REAL。
这时它也必须取整型标量。
● 在CASE语句当中作为情形值,这时它必须为整型,逻辑型或字符型标量。
● 在EQUIVALENCE语句当中,作为等价对象的下标或子串范围表达式,这时它必须取整型标量。
初始化表达式必须用于在编译时必须获得表达式的值的场合。
初始化表达式不能包含返回实型,复型以及逻辑型的结果的固有函数,也不能包含实型,复型以及逻辑型的变量。
首先我们给出限制表达式的定义。
作为一种说明,一个限制表达式就是它的每个算符都是固有的或者是由纯粹函数定义的自定义算符,而每个基元都是如下类型之一:
● 常量或常量子对象。
● 作为哑元的变量。
● 在公用块里的变量。
● 可从模块访问的变量。
● 来自主程序的变量。
● 一个数组构造器,它的每个表达式的基元都是限制表达式或者是数组构造器的隐式do变量。
● 每个成员都是限制表达式的结构构造器。
● 一个基本固有函数,它的结果为整型或字符型,它的变量是任意整型或字符型的限制表达式。
● 初始化表达式里的固有变换函数必须是如下几种固有变换函数之一:
NULL,REPEAT,RESHAPE,
SELECTED_INT_KIND,SELECTED_REAL_KIND,TRANSFER, TRIM。
其中除了NULL之外,它们的变量都必须是初始化表达式。
这样实际上就是排除了以下的变换函数:
ALL,ANY,COUNT,CSHIFT,DOT_PRODUCT,EOSHIFT,MATMUL,MAXLOC,
MAXVAL,MINLOC,MINVAL,PACK,PRODUCT,SPREAD,SUM,TRANSPOSE,
UNPACK。
● 自定义的纯粹函数。
● 除了PRESENT,ALLOCATED,ASSOCIATED之外的固有查询函数,并且它的变量必须是:
·限制表达式;
·不能查询种别参数与界的变量,不能由ALLOCATE语句定义,也不能由指针赋值语句定义。
这样任何下标,片断下标,以及子串范围的始点和终点都是限制表达式。
所谓说明表达式就是一个限制表达式,同时它必须取整型标量值。之所以称为说明表达式,就是因为说明表达式用于在类型声明,属性说明,维度声明以及其他说明性语句当中,表示数组的界,或者字符型对象的长度参数值。
在说明性语句当中使用初始化表达式和说明表达式的规则如下:
● 在这些表达式里的变量或命名常量的类型和种别参数,都必须在同一个作用域范围内,或者是主作用域单位里,或者是在一个可以访问当前作用域单位的模块作用域单位里,预先加以说明,或者是通过隐式默认规则得到说明。如果这些变量和常量在随后的显式类型声明里得到属性说明,则必须与当前的属性保持一致。
● 如果表达式里引用了一个数组的元素,那么该数组的界必须预先得到说明。
● 如果说明表达式同时包含了一个变量与它的值,那么该表达式必须出现在子程序的说明部分,而不能出现在主程序里。
【例8-30】 考虑下面程序片断里的变量I:
INTEGER I
COMMON I
REAL X(I)
I的值用来决定数组X的尺度,那么这个程序片断就不能出现在主程序,而只能出现在一个子程序的说明部分。
所谓预先说明,也可以是指在同一个说明语句当中,但必须是出现在引用的左边。
【例8-31】 下面的说明是有效的:
INTEGER,DIMENSION(4),PARAMETER::X=(/5,7,2,9/)
REAL,DIMENSION(X(2))::Y,Z(SIZE(Y))
由于常量数组X的第二个元素为7,因此Y,Z的尺度都是7。
【例8-32】 类似的以下声明却是无效的:
REAL,DIMENSION(X(2))::Z(SIZE(Y)),Y !无效声明!
因为SIZE(Y)在Y之前出现了。
【例8-33】 这个例子使用了说明表达式来声明一个带有相同形状的哑元数组的工作数组:
SUBROUTING X(A)
REAL::A(:,:)
REAL::WORK(SIZE(A,DIM=1),SIZE(A,DIM=2))
…
END SUBROUTING X
在FORTRAN 95标准里面,纯粹用户定义函数可以用于计算界,然后这些函数还可以用于其他声明语句当中。
【例8-34】
SUBROUTING S(A)
IMPLICIT NONE
REAL::A(:,:)
REAL::TAMP(RN(A),CN(A))
REAL::TTAMP(CN(A),RN(A))
…
CONTAINS
PURE FUNCTION RN(X)
INTEGER::RN
REAL,INTENT(IN)::X(:,:)
RN=SIZE(X,DIM=1)
END FUNCTION RN
PURE FUNCTION CN(X)
INTEGER::CN
REAL,INTENT(IN)::X(:,:)
CN=SIZE(X,DIM=2)
END FUNCTION CN
END SUBROUTING S
本小节,我们总结一下前面所讨论的各种表达式。
上面所讨论的一般表达式,限制表达式,说明表达式,常量表达式,以及初始化表达式相互之间具有一定的包含关系,下面的图8-1给出了它们的集合包含关系的示意图:
图8-1各种表达式集合之间的关系图
对于初始化表达式与说明表达式的异同由下表8-6给出:
表8-6初始化表达式与说明表达式的异同
性质 |
初始化表达式 |
说明表达式
|
字符型结果 |
有 |
无* |
整型结果 |
有 |
有 |
标量结果 |
有 |
有 |
数组结果 |
有 |
无 |
作为基元的变量(限于哑元,公用对象,主对象,模块对象) |
|
有 |
作为基元的整型与字符型基本固有函数 |
有 |
有* |
作为基元的整型与字符型的基本的与纯粹自定义的函数 |
无 |
有* |
作为基元的实型,复型,逻辑型与派生类型的基本固有函数 |
无 |
无 |
作为基元的实型,复型,逻辑型与派生类型的基本的与纯粹自定义的函数 |
无 |
无 |
基元只是常量 |
有 |
无 |
下标,步长,字符长度都是常量 |
有 |
无 |
以下固有变换函数可以作基元: REPEAT,RESHAPE, SELECTED_INT_KIND,SELECTED_REAL_KIND,TRANSFER, TRIM |
有 |
有 |
查询函数可以作基元(不包括ALLOCATE,ASSOCIATED,PRESENT) |
有 |
有 |
*字符型的表达式结果如果是某些固有函数的变量,则是许可的。
下表8-7总结了表达式的种类和它们各自的用途:
表8-7表达式的种类以及用途
表达式可能的出现的场合 |
限制表达式 |
初始化表达式 |
说明表达式 |
类型1 |
秩2 |
声明语句里的界3 |
否 |
否 |
可 |
I |
标量 |
声明语句里的长度4 |
否 |
否 |
可 |
I |
标量 |
EQUIVALENCE语句里的下标和子串范围 |
否 |
可 |
否 |
I |
标量 |
CASE语句里的值 |
否 |
可 |
否 |
I.L.C |
标量 |
声明语句里的种别参数 |
否 |
可 |
否 |
I |
标量 |
固有函数里的类型变量 |
否 |
可 |
否 |
I |
标量 |
PARAMETER语句和类型声明语句里的初始值 |
否 |
可 |
否 |
任意 |
任意 |
附加PARAMETER属性的类型声明语句里的初始值5 |
否 |
可 |
否 |
任意 |
任意 |
成员声明里的初始值 |
否 |
可 |
否 |
任意 |
任意 |
隐式do数据对象的参数 |
否 |
限制5 |
否 |
I |
标量 |
赋值 |
可 |
可 |
可 |
任意 |
任意 |
可执行语句的下标 |
可 |
可 |
可 |
I |
〈=1 |
可执行语句的步长 |
可 |
可 |
可 |
I |
标量 |
可执行语句的子串范围 |
可 |
可 |
可 |
I |
标量 |
SELECT CASE里的表达式 |
可 |
可 |
可 |
I,L,C |
标量 |
IF-THEN语句 |
可 |
可 |
可 |
L |
标量 |
ELSE-IF语句 |
可 |
可 |
可 |
L |
标量 |
IF语句 |
可 |
可 |
可 |
L |
标量 |
算术IF语句 |
可 |
可 |
可 |
I,R |
标量 |
DO语句 |
可 |
可 |
可 |
I |
标量 |
FORALL的指标参数 |
可 |
可 |
可 |
I |
标量 |
FORALL里的过滤 |
可 |
可 |
可 |
L |
标量 |
WHERE语句里的过滤 |
可 |
可 |
可 |
L |
标量 |
WHERE结构了的过滤 |
可 |
可 |
可 |
L |
标量 |
输出项列表 |
可 |
可 |
可 |
任意 |
任意 |
除了FMT=说明符之外的I/O说明符值 |
可 |
可 |
可 |
I,C |
标量 |
I/OFMT=说明符值 |
可 |
可 |
可 |
C |
任意 |
RETURN语句 |
可 |
可 |
可 |
I |
标量 |
完成计算的GO TO语句 |
可 |
可 |
可 |
I |
标量 |
隐式do数组构造器参数 |
可 |
可 |
可 |
I |
标量 |
隐式doI/O参数 |
可 |
可 |
可 |
I |
标量 |
实元 |
可 |
可 |
可 |
任意 |
任意 |
语句函数定义里的表达式 |
可 |
可 |
可 |
任意 |
标量 |
注释1:本栏里的任意指任意固有或派生类型。
注释2:本栏里的任意指结果可以是标量或任意秩(小于8)的数组。
注释3:相关声明语句包括类型声明,成员定义,DIMENSION,TARGET,COMMON
语句。
注释4:相关声明语句包括类型声明,成员定义,IMPLICIT,FUNCTION语句。
注释5:隐式do数据参数可以是一个以固有算符作用于常量或任意包含隐式do的do变量的表达式。
可以说,上节只是完备地说明了一个合法的表达式应该是什么样的,从形式的角度来看,表达式的分类,以及每种表达式能够担当什么样的语法作用。按照所有那些语法规则写完表达式之后,相当于当编译器读完源码的表达式之后,判断相关源码是否出现语法错误,例如表达式的形式是否完整无误,表达式是出现的场合,即它在它的上下文里面是否恰当,甚至还可以根据表达式里所涉及的一切对象所表明的属性,来给它们安排存储空间;依据其中的初始化语句对相关对象进行初始化取值等。这一切妥当之后,程序并没有开始运行。因为程序的运行,必定是从初始化取值开始,逐步地对每个变量对象求值,而这个求值过程依赖于计算机对于表达式的理解,也就是对于计算机而言,表达式的任意成员究竟应该翻译成什么指令。因此我们说,这是一个给形式语言赋予语义的过程。
接下来,我们就来讨论FORTRAN对于表达式所规定的语义,当然所有的语义的实现都是由编译系统完成的,并不需要程序作者的干预,我们只是需要知道如何使用FORTRAN所提供的语言要素,来表达我们的计算任务。
由于表达式所具有的层次性结构,对表达式的解释过程是与对表达式的构造过程相平行的。即从基元的取值出发,按照运算优先级规则,基元经过一层一层的算符的作用,而逐步得到表达式在每个层次,每个部分的取值,最终,得到整个表达式的取值,从而完成了对表达式的解释过程。
与表达式的构造过程类似,对表达式的解释分为两类:
● 对于固有运算的解释;
● 对于自定义运算的解释。
由于实质上计算机只能处理有限数值,因此除了对于整数的除法之外,FORTRAN的固有运算都是与通常的数学含义一致的,只是任何具体的数值,在计算机里的表示都必须以有限数值的形式出现。至于整数除法的特殊性,来源于FORTRAN运算对于算元与结果的数据属性的一致性的要求,两个整型数值的商还必须保持为整型数值,因此必须给出特别的约定,才能解决两个整数不能整除的问题。
对于自定义运算的解释,则是在定义该算符的函数所在的,具有OPERATOR形式的通用说明符的界面块里完成的,那里实际上涉及的是对于一个程序的解释,而不只是对于表达式的解释。
从对表达式的解释过程可以看出,对表达式的解释是独立于表达式的上下文的,因为从底层开始进行的解释过程,根本不需要考虑它的上下文。但是这种独立性并不表明同样的算符作用于同样的算元,最后的值处处都是一样的。这种现象的根源在于计算机对于数值的表示是有限形式的,因而计算机的计算总是某种近似计算,也就是说总是会产生误差,而这种误差并不是可以得到精确控制的。
例如实型表达式X-Y,它在赋值过程Z=X-Y里,与它在表达式X-Y.EQ.Z里,不一定会得到相同的取值。
基于表达式的构造,可以把对于固有运算的解释分为如下部分:
● 对于固有数值运算的解释。
● 对于非数值固有运算的解释。
● 对于具有数组算元的固有运算的解释。
● 对于具有指针算元的固有运算的解释。
● 除了整数幂次的幂次运算之外,固有数值运算总是首先按照前面的算元与结果的数据属性一致性规则,把算元的数据类型与种别都转换为与结果一致的形式,然后再开始按照通常的属性含义进行运算。至于整数幂次的幂次运算,则完全不必转换整数幂次的类型,因为乘方实际上就是转换为乘法运算,整数幂次的的乘方,就是非常直接的乘法。
● 对于两个整数除法,由于一致性的要求,商也必须是整数,对于无法整除的情形,规定取离小数形式的商最近的整数值作为计算结果。
● 对于幂次运算
X**Y
如果Y是负数,那么表达式就转换为
1/(X**(-Y))
注意,这里引入除法之后,就在幂次运算里增加除法的特殊性。
【例8-35】 下面表达式
5**(-2)
的结果等于0。
如果X为负数,而Y为实型,则表达式无意义。
而当Y为实型或复型,则返回值是数学函数XY的基本值
完整的对固有运算的解释见表8-7。
表8-8固有运算的解释:
运算 |
运算的解释 |
|||
X |
** |
Y |
X的Y次乘方 |
|
X |
/ |
Y |
X被Y除 |
|
X |
* |
Y |
X乘Y |
|
X |
- |
Y |
X减Y |
|
|
- |
Y |
Y反号 |
|
X |
+ |
Y |
X加Y |
|
|
+ |
Y |
Y不变 |
|
X |
// |
Y |
X后面连接Y |
|
X |
.LT. |
Y |
如果X小于Y则为TRUE |
|
X |
< |
Y |
如果X小于Y则为TRUE |
|
X |
.LE. |
Y |
如果X小于等于Y则为TRUE |
|
X |
<= |
Y |
如果X小于等于Y则为TRUE |
|
X |
.GT. |
Y |
如果X大于Y则为TRUE |
|
X |
> |
Y |
如果X大于Y则为TRUE |
|
X |
.GE. |
Y |
如果X大于等于Y则为TRUE |
|
X |
>= |
Y |
如果X大于等于Y则为TRUE |
|
X |
.EQ. |
Y |
如果X等于Y则为TRUE |
|
X |
== |
Y |
如果X等于Y则为TRUE |
|
X |
.NE. |
Y |
如果X不等于Y则为TRUE |
|
X |
/= |
Y |
如果X不等于Y则为TRUE |
|
|
.NOT. |
Y |
如果Y为FALSE则为TRUE |
|
X |
.AND. |
Y |
如果X与Y都是TRUE则为TRUE |
|
X |
.OR. |
Y |
如果X或Y或都是TURE则为TRUE |
|
X |
.EQV. |
Y |
如果X与Y都是TURE或都是FALSE则为TRUE |
|
X |
.NEQV. |
Y |
如果X和Y一个为TURE,一个为FALSE则为TRUE |
|
● 字符型数据的运算就是字符的串联,按照从左到右的顺序把右边的算元接在左边的算元的右边即可。
● 关系运算按照算元分三类:
·算元都是数值。
这时就是一般的数学意义上的大小与相等的概念,如果它们的类型或种别不同,则先转换为它们的和的类型或种别,再进行比较。
对于复数,只能说是否相等,不能比较大小。
结果都是默认逻辑型。
·算元都是字符型。
算元的长度可以不同,但是类型必须相同。
如果长度不同,则在短字符串的右边添加空格符,使得两者长度一致,再从左边第一个字符开始作比较,如果相同,则比较第二个字符,直到出现不相同的情况,这时就可以得到是否相等的判断,进一步根据第一个不相同的字符在编译器规则的字符顺序表里的位置前后,来决定它们的大小。
因此字符型数据的大小关系是依赖于编译系统的,而是否相等则不依赖。
·算元都是逻辑型。
逻辑型运算的取值表见表8-9。
表8-9固有逻辑运算的值
X |
Y |
.NOT.Y |
X.AND.Y |
X.OR.Y |
X.EQV.Y |
X.NEQV.Y |
TURE |
TURE |
FALSE |
TURE |
TURE |
TURE |
FALSE |
FALSE |
TURE |
FALSE |
FALSE |
TURE |
FALSE |
TURE |
TURE |
FALSE |
TURE |
FALSE |
TURE |
FALSE |
TURE |
FALSE |
FALSE |
TURE |
FALSE |
FALSE |
TURE |
FALSE |
对于二元固有运算来说,基于一致性的要求,数组作为算元只会出现两者情况:
● 两个算元都是数组,而且形状必定相同。
● 一个算元是数组,另一个是标量。
这时,由于两个算元形状不同,如果导致特定的运算无法进行,则把标量扩充为一个与数组算元具有相同形状的数组,该数组的所有元素都等于该标量。
数组的运算,无论是一元还是二元运算,基本的规则就是对所有的元素进行相同的运算。
例如数组运算A+B的结果是一个与A,B相同形状的数组,该数组的每个元素都等于相应位置上的A与B的元素的和。
而数组运算-A的结果是一个与A相同形状的数组,该数组的每个元素都等于相应位置上的A的元素的反号结果。
注意这里的运算解释不同于数学意义上的矩阵的运算。
如果固有运算出现在过滤赋值语句当中,那么运算就只针对满足过滤条件的元素。实际上,在机器内部,所有元素的计算都执行了,但是不满足过滤条件的元素的计算并不影响最终的结果,因为不满足过滤条件的元素再也不会在随后的程序当中出现,也就不会导致运行时错误。
数组元素的分别计算并不存在顺序,对于处理器来说,可以是任意顺序。实际上不管是向量处理器还是标量处理器,都有可能是所有元素的计算一次同时完成。
固有运算的算元也有可能是指针,这时指针必须已经关联到一个已经定义好了的目标上,然后把目标的取值代入指针,再按照前面的解释进行运算。
如果运算的算元是一个同时也是指针的结构变量的成员,那么它的取值就是和该结构变量关联的作为目标的结构的相应命名成员。
【例8-36】
TYPE(ADD)
I,J::INTEGER
END TYPE
TYPE(ADD),POINTER::PTR
TYPE(ADD),TARGET::T
这里PTR被关联到T,如果PTR%I成为了一个算元,那么该算元的取值就是目标T的成员I,即T%I。
对自定义运算的解释由带有OPERATOR界面的函数子程序提供。
如果同一个OPERATOR界面包含多个函数,那么由所含哑元的类型,种别,秩等一致的函数提供解释。
【例8-37】 设有自定义运算.ADD.,而派生类型RATIONAL对象A和B构成如下表达式:
A.ADD.B
其中的自定义运算.ADD.来自如下OPERATOR界面块,里面包含了2个函数:
INTERFACE OPERATOR(.ADD.)
FUNCTION RATIONAL_ADD(L, R)
USE RATIONAL_MODULE
TYPE (RATIONAL),INTENT(IN)::L,R
TYPE (RATIONAL) ::RATIONAL_ADD
END FUNCTION RATIONAL_ADD
FUNCTION LOGICAL_ADD(L, R)
LOGICAL, INTENT(IN)::L, R
LOGICAL ::LOGICAL_ADD
END FUNCTION LOGICAL_ADD
END INTERFACE
显然.ADD.由函数RATIONAL_ADD给出解释.
下面是自定义运算的说明:
● 自定义运算由包含1个或2个哑元的函数定义。
● 函数哑元代表了运算的算元。如果只有1个哑元,则为一元运算;如果有2个哑元,则为二元运算,其中第一个哑元为左算元,第二个哑元为右算元。
● 必定存在一个具有通用说明符OPERATOR的界面块定义函数。
● 表达式里的算元的数据属性必须与函数里的哑元保持一致。
● 自定义运算的算符不能和任何固有运算的算符相雷同。
● 如果函数不是基本的,那么算元的秩必须与函数里的哑元保持一致;
● 如果函数是基本的,算元可以是数组,如果包含2个算元,它们必须保持一致性。
● 或者有一个哑元是派生类型,或者都是固有类型,但是与表8-3里的固有运算不保持一致。
● 如果存在两个界面都满足给一个自定义运算提供解释的条件,例如:
·一个界面包含1或2个满足条件的哑元;
·另一个函数界面是基本的,同样包含1或2个满足条件的标量哑元。
那么引用非基本函数的那个。
【例8-38】 设有如下界面块:
INTERFACE OPERATOR(.ADD.)
ELEMENTAL FUNCTION ELEM(X,Y)
REAL,INTENT(IN)::X,Y
END FUNCTION ELEM
FUNCTION NOELEM(X,Y)
REAL,INTENT(IN),DIMENSION(20)::X,Y
REAL NONELEM(20)
END FUNCTION NONELEM
END INTERFACE
然后在以下程序当中引用运算A.ADD.B
REAL A(20),B(20),C(20)
…
C=A.ADD.B
基于以上的规则,这里执行的函数是NONELEM,而不是ELEM。
注意自定义运算的算元不需要在形状上保持一致性,因为函数可能是非基本的。
对于自定义运算必须注意避免产生如下的异常运算:
● 由固有运算或固有函数导致的整数溢出。
● 整数除法0/0。
● 固有数值运算导致的浮点溢出。
● 0**0
● 浮点运算导致的浮点下溢。
● 浮点舍入错误。
● X**Y,X为实型,Y为负数。
基于表达式的语义,一个计算过程得以开始的前提是每个算元都已经定义好,或者说具有具有确定的定义状态(参见第17章通讯)。而一旦开始计算,机器的计算过程,或者说计算途径并不是唯一的,因为要唯一确定表达式的计算过程,就必须把括号全都写出来,实际上我们省略了很多括号,这就使得编译系统的计算可以在许多不同的途径当中进行优化。这种优化的实质,就是给出表达式的等价表达式,选择更加有效率的表达式进行实际的计算。
所谓两个表达式等价,是指如果它们的算元取所有可能的值时都结果一样。在这样的定义之下,似乎只要两个表达式具有数学上的等价性即可,然而由于在数值计算方面,计算机具有本质上的局限性,使得FORTRAN语言里的表达式的等价与数学意义上的表达式的等价并不完全一致,这点主要表现在如下方面:
● 因为按照FORTRAN语言的规定,括号的位置是绝对不可变更的,因此对表达式的等价变换受到了一定的限制。即在对一个表达式进行等价变换时,显式的括号位置不能改变,不能减少,只能增加。
● 当遵循交换律,结合律和分配律来进行等价变换时,必须考虑到所涉及的算元的取值精度是否会因此而发生改变。因为所谓等价变换,实际上就是改变表达式里的各种算符的计算顺序,甚至有可能省略掉本来有的算符,由于各种运算对于精度的影响是不同的,有的运算总是绝对精确的,有的运算,或者在有的时候,会产生一定的误差,这样的运算在被改变运算顺序之后,在数值上就不会是等价的了。
在下面的表8-10和表8-11里面我们分别给出有效的等价代换与无效的等价代换的例子:
表8-10:表达式计算的有效代换
表达式 |
等价的表达式 |
X+Y |
Y+X |
X*Y |
Y*X |
-X+Y |
Y-X |
X+Y+Z |
X+(Y+Z) |
X-Y+Z |
X-(Y-Z) |
X*Y/Z |
X*(Y/Z) |
X*Y-X*Z |
X*(Y-Z) |
X/Y/Z |
X/(Y*Z) |
X/5.0 |
0.2*X |
X>Y |
(X-Y)>0 |
X.OR.Y.OR.Z |
X.OR.(Y.OR.Z) |
X.AND.X |
X |
X=Y//Z |
X=Y !LEN(X)<=LEN(Y) |
表8-11无效的表达式代换的例子:
表达式 |
不允许的代换表达式 |
I/2 |
0.5*I |
X*I/J |
X*(I/J) |
I/J/X |
I/(J*X) |
(X+Y)+Z |
X+(Y+Z) |
(X*Y)-(X*Z) |
X*(Y-Z) |
X*(Y-Z) |
X*Y-X*Z |
注意在改变运算顺序之后有可能发生的舍入误差,导致计算结果的不一致。例如X-Y-Z和X-(Y+Z)就有可能具有不同的数值结果。
需要注意在表达式没有进行计算的部分,有可能包含具有在引用运行后,能够改变程序状态的副作用。