从计算机的实际状态来看,给出表达式并不意味着计算的开始,真正能够驱动计算的是可执行语句,而最直接驱动表达式的计算过程的就是赋值语句,完成一个赋值步骤,就意味着机器的状态的局部或全局发生了一个根本的变化。
从语法的角度来看,一个表达式还只是一个比较复杂的复合词汇,还不能构成一个完整的语句,而赋值语句则是一个在语法意义上的完整的语句,表达式在赋值语句中扮演关键的语法作用。
从计算问题的角度来看,赋值可以说是基本的计算步骤,考虑一下我们人工计算的过程就可以发现,任何一个完整的计算步骤,都可以说就是一个赋值步骤,特别是公式演算和数值计算,完成一个局部运算过程的标志,往往就是求出某个中间变量的数值或表达式。
FORTRAN语言作为一种以公式翻译为初衷的高级语言,它的赋值语句的一般形式就是一个数学等式,当然不是那种公式恒等变换得到的恒等式,而是要把运算的结果赋予一个变量,因此它的一般句法形式为:
variable = expression
variable => expression
可以看到一个赋值语句作为一个完整的语句,分为三个部分:
● 被赋值的变量
● 赋值符号
● 计算(表达式)
其中被赋值的变量,通过赋值,就拥有了明确的取值形式,如果该变量含有下标,片断下标或子串范围,则赋值的前提是它们都已经获得具体的取值。
基于表达式结果的不同种类,赋值语句分为:
● 固有赋值:固有赋值是把任意类型的值赋予一个非指针变量,或把一个指针变量赋予一个与之相关联的目标。
● 自定义赋值:自定义赋值得以构成的前提是存在一个可访问的子例行程序,它包含一个具有ASSIGNMENT形式的赋值界面,其属性与自定义赋值语句里的变量和表达式的属性保持一致。
● 指针赋值:指针赋值把一个指针变量关联到它的目标对象,或者说把一个目标对象赋予到一个指针变量。在形式上它使用符号=>。
● 过滤数组赋值:这个赋值过程是对满足一定条件的数组元素进行赋值,而不是对数组整体进行赋值。
● 并行指标数组赋值。这种赋值形式给出了一种有效的并行机制,能够大规模地对多重指标变量进行赋值。
下面我们将分节讨论这5类赋值形式,首先给出一些例子如下:
【例9-1】
X=X-2 |
实型的固有赋值 |
CHAR(5:8)=“M_30” |
逻辑型的固有赋值 |
SAMPLE=NOTE_2 |
结构的固有赋值 |
STRING=”MYSAMPLE” |
可变串结构的自定义赋值 |
WHERE(X/=0.0) A=B/X END WHERE |
过滤数组赋值 |
FORALL(I=0:N,J=1:N) A(I,J)=3.0/(I-J+2) B(I,J)%PTR=>C(I:N,J:N) END FORALL
PTR=>X |
并行指标结构 并行指标赋值 并行指标指针赋值
指针赋值 |
所谓固有赋值的主要特征就是被赋值的变量不能是指针变量,赋值语句本身就是对变量的定义或重定义,变量的取值就是直接计算赋值语句右边的表达式所得。不需要额外的子程序来加以说明。
下面说明固有赋值语句的用法:
● 固有赋值中变量与表达式所能够具有的类型,以及它们的一致性要求见表9-1。
表9-1固有赋值中变量与表达式的类型
变量的类型 |
表达式的类型 |
INTEGER |
INTEGER,REAL,COMPLEX |
REAL |
INTEGER,REAL,COMPLEX |
COMPLEX |
INTEGER,REAL,COMPLEX |
CHARACTER |
变量为具有相同种别参数的CHARACTER |
LOGICAL |
LOGICAL |
派生类型 |
变量为相同的派生类型 |
● 如果变量是标量,那么表达式也必须是标量。
● 如果变量是数组,那么表达式或者是标量,或者是相同形状的数组。
● 如果变量是显形数组,那么变量的形状可以通过说明语句说明。
● 如果变量是待定形数组,那么它的形状由ALLOCATE语句,或指针赋值语句确定。
● 如果变量是哑形数组,那么它的形状由变量里的片断下标,或实元确定。变量不能是哑尺度数组,除非存在含有最后一个维度上的上界的片断下标,或数组的下标向量,或者标量下标。
● 表达式的形状由算元的形状,表达式里的运算,以及其中的函数引用决定。
● 如果变量是一个指针,那么它必定已经关联到一个目标,赋值语句把表达式的值赋予指针的目标。指针所关联的目标可以是一个数组,指针决定数组的秩,而每个维度的宽度则由目标决定。
● 在赋值过程本身被执行之前,赋值语句右边表达式以及表达式和变量里面包含的下标与下标片断表达式,都必须预先求值完毕。
● 如果变量的类型以及种别参数和表达式的不一致,那么在执行赋值语句之前,还必须完成必要的针对表达式的类型转换,固有转换函数见下表9-2:
表9-2 固有转换函数
变量的类型 |
被赋的值 |
INTEGER |
INT(expression,KIND(variable)) |
REAL |
REAL(expression,KIND(variable)) |
COMPLEX |
CMPLX(expression,KIND(variable)) |
LOGICAL |
LOGICAL(expression,KIND(variable)) |
● 表达式里面可以使用赋值语句左边变量的部分值。
【例9-2】
DATE(5:10)=DATE(2:6)
注意这是FORTAN现代版本所增加的功能,在FORTRAN77里面是不允许的。
● 如果变量和表达式都是字符型的,那么它们必须具有相同的种别参数值。
● 如果变量和表达式是具有不同长度属性的字符型对象,那么赋值过程遵循以下规则:
● 如果变量的长度比表达式的短,那么把表达式的右边长出来的部分截除;
● 如果变量的长度比表达式的长,那么在表达式的右边添加空格,直到跟变量一样长。
● 如果在赋值语句左边的变量里面含有表达式,例如下标表达式,那么这个表达式的计算与取值完全与赋值语句右边的表达式无关,因为左边变量的任何表达式都必须是预先完成计算的。
● 如果把一个标量赋值给一个数组,为了保持形状的一致性,需要把该标量扩充为一个与左边数组同样形状的数组,该数组的每个元素都是那个标量。
【例9-3】
INTEGER X(100)
X=5.0
经过赋值后X的取值为含有100个元素的数组,每个元素都是5.0。
● 数组的赋值过程是按照元素位置一一对应进行的。
【例9-4】 设有两个相同形状的数组X和Y,有赋值语句:
X=Y
那么具体的赋值过程就是Y的元素赋予X的相同位置的元素,至于不同位置的元素是按照什么顺序来进行赋值的,并不需要考虑,可以认为所有元素的赋值是同时进行的。
● 对于派生类型的对象的固有赋值,变量与表达式必须是属于同一个派生类型,而赋值方法类似于数组,在相同位置的成员之间进行赋值,如果某个成员是指针,则该成员的赋值过程按照指针赋值的规则进行。
如果需要进行赋值的变量和表达式是不符合表9-1,表9-2所规定的一致性要求的固有类型或派生类型,那么就需要使用自定义赋值语句,它提供赋值界面与相应子例行程序以供访问,从而完成赋值。
自定义赋值是由具有赋值说明符ASSIGNMENT(=)的子例行程序来完成赋值操作的。用户可以在子例行程序当中通过定义新的规则,来扩充可以进行赋值的类型。
下面介绍自定义赋值运算的用法:
● 自定义赋值运算由具有两个哑元的子例行程序声明。(子例行程序可以是外部或模块子例行程序里的对象。)
● 子例行程序的哑元按照其出现的前后顺序,分别表示自定义赋值的变量与表达式。
其中第一个哑元必须具有INTENT的OUT或INOUT的属性,而第二个哑元必须具有INTENT的IN的属性。
● 具有ASSIGNMENT(=)形式的通用说明符的子例行程序必须带有界面块。
● 变量与表达式的类型以及种别参数必须与哑元保持一致。
● 对于一个非基本子例行程序,变量与表达式的秩必须与哑元的秩相同。
● 对于一个基本子例行程序,变量必须是数组,而表达式与之保持一致;或者两者都是标量。
● 如果变量和表达式同时与一个基本子例行程序和一个非基本子例行程序的界面匹配,那么赋值运算由非基本子例行程序定义。
● 两个哑元或者有一个是派生类型,或者都是固有类型,但不满足固有赋值的一致性条件。
● 对变量的自定义赋值的结果由所引用的子例行程序决定。
【例9-5】
INTERFACE ASSIGNMENT(=)
ELEMENTAL SUBROUTING RATIONAL_TO_REAL(L,R)
USE RATIONAL_MODULE
TYPE (RATIONAL), INTENT(IN) :: R
REAL, INTENT(OUT) ::L
END SUBROUTING RATIONAL_TO_REAL
ELEMENTAL SUBROUTING REAL_TO_RATIONAL (L,R)
USE RATIONAL_MODULE
REAL INTENT(IN) ::R
TYPE (RATIONAL), INTENT(OUT)::L
END SUBROUTING REAL_TO_RATIONAL
END INTERFACE
上面的界面块通过两个外部子例行程序给出了自定义赋值,一个把派生类型RATIONAL的对象赋值给实型对象,另一个把实型对象赋值给派生类型RATIONAL的对象。有了上面的界面块,就可以进行下面的自定义赋值:
REAL R_VALUE, R_ARRAY(20)
TYPE (RATIONAL) RAT_VALUE, RAT_ARRAY(20)
R_VALUE=RATIONAL(5,8)
RAT_VALUE=6.2
所谓指针赋值,实际上就是把指针变量关联到一个具有TARGET属性的对象上,即使得指针变量成为目标对象的一个“别名”。
如果指针所关联的目标的状态发生改变,例如去关联,或去定义,那么指针的状态也发生相应的改变。一旦指针赋值语句被执行之后,指针的关联状态就不会改变了,除非执行另外一个指针赋值语句来改变其状态,或执行ALLOCATE,DEALLOCATE,NULLIFY语句对指针重定义。
指针赋值的一般形式(R736)为:
pointer-object=> target
其中指针对象(pointer-object)的一般形式(R630)为:
variable-name
structure-component
而目标(target)的一般形式(R737)为:
variable
expression
下面说明指针赋值的用法:
● 如果指针对象为变量名称或结构成员,那么它们都必须具有POINTER属性。
● 如果目标是命名变量,那么它必须具有TARGET或POINTER属性。
● 如果目标是子对象指示符,那么它的父结构必须具有TARGET或POINTER属性,或者它必须是一个在最右边成员具有POINTER属性的结构引用。
● 目标与指针对象的类型,种别参数,长度参数,秩都必须一样。
● 如果=>右边的变量具有TARGET属性,那么=>左边的指针对象就被关联到该目标。
● 如果=>右边的变量具有POINTER属性,并且处于关联状态,那么=>左边的指针对象在指针赋值语句执行之后,也被关联到目标指针所关联的同一个数据对象。
● 如果=>右边的变量具有POINTER属性,并且处于去关联状态,或者右边的表达被引用到固有函数NULL,那么=>左边的指针对象也成为去关联状态。
● 如果=>右边的变量具有POINTER属性,并且处于去定义关联状态,那么=>左边的指针对象也成为去定义关联状态。
● 指针赋值语句终止了指针对象此前所具有的任何关联状态,从而产生一个新的关联状态。
● 如果指针对象是待定形数组,那么指针赋值语句就能够确立指针数组每个维度的宽度,即其目标的相应维度的宽度,除非目标本身是去关联指针或去定义指针。
【例9-6】 执行以下语句:
INTEGER,TARGET::T(11:20)
INTEGER,POINTER::P1(:),P2(:)
P1=>T
P2=>T(:)
得到P1的宽度是11和20,而P2的宽度是1和10。
● 目标不能是属于哑尺度数组的命名变量。
● 如果目标是一个哑尺度数组的数组片断,那么它必须具有下标,或者具有在最后一维给出了上界的下标三元组。
● 如果目标为数组片断,那么它不能具有下标向量。
● 如果目标是一个表达式,那么它必须给出一个指针结果,这意味着该表达式引用了固有函数NULL,或者是引用了能够得到指针结果的用户自定义函数或自定义运算。因为在固有函数或固有运算里,只有NULL得到指针结果。
● 如果一个指针的目标没有被引用或定义,那么该指针也不能被引用或定义。
● 如果一个结构的某些成员具有POINTER属性,而该结构被固有派生类型赋值语句进行了赋值,那么对于每个具有POINTER属性的成员,是运用指针赋值语句进行赋值的。
自定义赋值语句可以用来在结构的成员之间进行指针赋值。
【例9-7】
MONTH=>DAYS(1:30)
PTR=>X(:,5)
NUMBER=>INDEXONE%INDEXTWO
FIRST_RESLUT=>NULL()
【例9-8】 下面的目标也是指针:
REAL,POINTER::PTR,P
REAL,TARGET::X
REAL Y
X=5.7
P=>X
PTR=>P
Y=PTR-8.1
【例9-9】 下面的目标为表达式:
INTERFACE
FUNCTION POINTER_FCN(X)
REAL X
REAL,POINTER::POINTER_FCN
END FUNCTION
REAL,POINTER::P
REAL A
P=>POINTER_FCN(A)
注意以下情形:
● 在指针与目标之间建立关联,除了指针赋值语句之外,使用ALLOCATE语句也能做到,而使得指针去关联则也可使用DEALLOCATE或NULLIFY语句。
● 当在表达式或过程里引用指针时,会得到与该指针关联的目标的值,这点与在指针赋值语句里不同。
● 当指针是作为一个与具有POINTER属性的哑元相应的实元出现的时候,那么该引用是到达指针本身,而不是它的值。
对于数组的赋值,除了全数组的一一对应的赋值方式之外,很多时候,还需要做到在数组里挑选符合一定条件的某些元素,只对这些特定元素进行赋值。这时已有的赋值语句都无法做到,因此FORTRAN专门提供了过滤数组赋值方式,用来完成这类任务。
所谓过滤数组赋值,实际上就是建立WHERE块,或ELSEWHERE块,或者是WHERE语句,然后在其中使用固有赋值语句,它的变量为数组时,就可以对其元素进行选择性赋值。
在WHERE语句或WHERE结构里对数组元素进行过滤,是根据一个逻辑数组表达式的值来进行的,该逻辑数组表达式是从数组取值而得到逻辑型值的条件表达式,作用于:
● 基本固有运算;
● 基本固有函数引用;
● 基本自定义运算;
● 基本自定义函数引用;
● 固有赋值;
● 基本自定义赋值。
注意所谓WHERE语句或WHERE结构非常类似于选择控制结构IF结构或IF语句,但它们有一个主要的差别,就是在WHERE结构里,所有的赋值语句都是要执行的,即使是不满足条件的元素,它的赋值同样执行了,只是不满足条件的元素的赋值对随后的程序不其任何作用而已。但是在IF结构里,不满足条件的情形是不被执行的。
WHERE语句的句法形式(R738)为:
WHERE(logical-expression)array-assignment-statement
其中逻辑表达式(logical-expression)对所有数组元素起作用,就相当于一个过滤网,使得表达式结果为TURE的数组元素被赋值给变量,而表达式结果为FALSE的数组元素不被赋值给变量。
【例9-10】
WHERE(CURRENT>1.00)PASS=CURRENT
WHERE(TEMP<0.40)USEFULL=TEMP
WHERE结构的句法形式(R739)为:
[where-construct-name:] WHERE(logical-expression)
[where-body-construct]…
[ELSEWHERE(logical-expression)[where-construct-name]
[where-body-construct]…]…
[ELSEWHERE [where-construct-name]
[where-body-construct]…]
END WHERE [where-construct-name]
其中where体结构的句法形式(R741)为:
assignment-statement
where-construct
where-statement
在初始WHERE语句当中出现的逻辑表达式构成了一个过滤网,可以控制在WHERE体结构里给出的数组赋值语句的表达式取值和赋值。如果逻辑表达式在ELSEWHERE语句当中也出现,那么该语句就成为了过滤ELSEWHERE语句。
【例9-11】
INTEGER::N(9)=(/1,2,3,4,5,6,7,8,9/)
WHERE(MOD(N,2)==0)
N=2 !现在N=(/1,2,3,2,5,2,7,2,9/)
ELSEWHERE(MOD(N,3)==0)
N=3 !现在N=(/1,2,3,2,5,2,7,2,3/)
ELSEWHERE(MOD(N,5)==0)
N=5 !现在N=(/1,2,3,2,5,2,7,2,3/)
ELSEWHERE
N=0 !现在N=(/0,2,3,2,5,2,0,2,3/)
END WHERE
下面说明WHERE结构的用法:
● 一个结构名称不能单独出现在初始WHERE语句,或END WHERE语句里,必须成对出现。如果一个结构名称出现在ELSEWHERE语句里,或者是过滤ELSEWHERE语句里,那么它也必定出现在WHERE结构语句里。
● 在WHERE结构的每个赋值语句里,被定义的变量必须和过滤网具有相同的形状,如果在WHERE结构的其他位置也出现了过滤网,它们的形状必须都相同。
● WHERE结构里的语句按照其出现的顺序执行。
● 每个过滤网的有效取值只有一次,随后的逻辑表达式里的取值变化都对控制过滤网的值没有影响。
● WHERE结构里的自定义赋值必须由基本子例行程序定义。
● 只有在控制过滤网取真值时,赋值语句里的基本运算或函数才得到计算。
【例9-12】
REAL X(1,100)
…
WHERE(X>0.0)
SQRTX=SQRT(X)
END WHERE
● 如果在结构的逻辑表达式或赋值语句当中出现了数组构造器,那么它将不经过过滤而得到完全计算。
● 结构里的表达式或变量或逻辑表达式里的非基本函数引用将得到完全计算,尽管有可能结果数组的所有元素都用不上。
【例9-13】
REAL A(2,3),B(3,10),C(2,10),D(2,10)
IMTRINSIC MATMUL
…
WHERE (D<0.0)
C=MATMUL(A,B)
END WHERE
其中矩阵乘积的所有元素都得到了,但是只有小于0的元素才得到赋值。
● 在WHERE结构里,只有WHERE语句才能成为分支目标。
当WHERE结构开始运行的时候,其中的控制过滤网和候选过滤网就都已经建立了,而控制过滤网主宰了随后的语句块的执行。
● 如果WHERE结构不是嵌套WHERE结构,那么控制过滤mask取得逻辑表达式的值,而候选过滤网的值为.NOT.mask。
【例9-14】
WHERE(A1) !语句1
CALL B1 !块1
ELSEWHERE(A2) !语句2
CALL B2 !块2
ELSEWHERE !语句3
CALL B3 !块3
END WHERE
在执行语句1之后,控制过滤网的值为A1,而候选过滤网的值为.NOT.A1;
在执行语句2之后,控制过滤网的值为(.NOT.A1).AND.A2,而候选过滤网的值为(.NOT.A1).AND(.NOT..A2);
在执行语句3之后,控制过滤网的值为(.NOT.A1).AND(.NOT..A2)。
● 如果WHERE结构是嵌套WHERE结构,还是以上面的例子来说明运行过程:如果最外层主宰着块内的语句运行的初始控制过滤网是outer-mask,那么块1的控制过滤网取值为outer-mask.AND.A1,从这个初始状态开始,就可以得到向内嵌套过程的每个控制过滤网取值,即总是向内取逻辑与运算。到运行内部END WHERE语句时,控制过滤网就回复到outer-mask。
WHERE结构与控制结构的主要差别在前面9.4已经讨论过,除此之外,还有个差别,就是在WHERE结构里,不存在从WHERE块或ELSEWHERE块里转移出来的机制,当然除了使用函数引用之外。因为在这些块里都不允许分支语句。
WHERE结构还有一个特征,就是在结构前面块里的语句的执行,会影响后面块里的变量引用,因为所有块里的语句都会顺序得到执行。
在数学计算当中,经常会遇到如下的计算任务:
,对于i从1到n,j从1到m。
,对于i从1到n。
其中第一个计算可以使用嵌套DO循环描述如下:
DO J=1,M
DO I=1,N
X(I,J)=I**2-J**2
END DO
END DO
不过这种实现方法完全没有利用数组这种强大的数据结构,而在很多系统里面,通过利用数组,可以更加有效地完成上面类型的计算任务。
对于FORTRAN而言,除了运用DO循环之外,运用固有函数SPREAD,再采用数组赋值的形式,也可以完成上面的计算:
X=SPREAD((/(I,I=1,N)/),DIM=2,NCOPIES=M)**2-&
SPREAD((/(I,I=1,M)/),DIM=1,NCOPIES=N)
而在FORTRAN里,一种更加自然的计算方法是使用FORALL语句。该语句充分发挥了数组在数据结构方面的特点,使得这种计算更加有效率:
FORALL (I=1:N,J=1:M)X(I,J)=I**2-J**2
显然,在这种计算方法里面,FORALL能够直接把数组元素和片断引用到表达式里面,不仅使得计算过程一目了然,而且表现了并行计算的优越性。
对于上面的第二个数学计算任务,它不能使用数组片断来描述,但FORALL语句则能够把秩为1的数组Y的元素赋值给数组X的对角线:
FORALL(I:N)X(I,I)=Y(I)
如此就轻松自然地完成了计算任务。
在关键词FORALL后面的括号部分称为FORALL头,它能够对后面的多个语句发挥控制作用。
【例9-15】
FORALL (I=2:N-1,J=2:N-1)
A(I,J)=(A(I+1,J)+A(I-1,J)+A(I,J+1)+A(I,J-1))/10.0
B(I,J)=10.0/A(I+1,J+1)
END FORALL
在FORALL语句与END FORALL语句之间的部分,称为FORALL体,FORALL体里面可以包含如下内容:
● 赋值语句;
● 指针赋值语句;
● WHERE结构和语句;
● FORALL结构和语句。
FORALL体可以包含FORALL结构和语句,也就是说FORALL结构可以嵌套。
FORALL体的结构和语句的执行是顺序往下执行的,所有右边的表达式对所有的指标进行求值计算时,对于不同的指标的求值计算,则是按照任意次序,当这样的计算都完成之后,随即进行的赋值也是按照任意的次序。
所以尽管FORALL语句构造了一个循环结构,但是每当针对结构里的一个语句进行求值时,被认为是进行并行指标运算,每完成一个语句的所有指标的运算,才转到下一个语句的计算,因此具体的在计算每个语句的所有指标时,到底系统是按照什么顺序,是不需要规定的,完全依靠具体的系统的自身特定来实现,在FORTRAN语言层面上不加以规定,因此使得FORALL结构具有了与控制结构完全不同的特征,因为所谓控制结构,是对每一个指标的运算都加以控制的,因此在执行控制语句时,不得不制定不同指标的运算顺序。
FORALL的优越性正是体现在这里,因为语言没有规定的对指标的计算,完全可能在某些系统里面是并行进行的,这样的话,一下子就使得数组具有了并行计算的特征,从而可以大大地提高程序运行的效率。
有时,可能需要排除一些元素,这时,就需要在FORALL头里加入一个过滤表达式。
【例9-16】
FORALL(I=1:N,J-=1:M,A(I)<100.AND.B(I)<100)&
C(I,J)=A(I)+B(J)
在FORALL头里的说明三项列,再加上可选的过滤网,就可以控制FORALL体内的多种赋值语句,WHERE结构与语句,以及嵌套FORALL语句与结构。
下面我们分形式与运行两个部分来讨论FORALL结构。
FORALL结构的句法形式(R747)为:
[forall-construct-name:]FORALL(forall-triplet-specification-list &
[,scalar-logical-expression])
[forall-body-construct]…
END FORALL [forall-construct-name]
其中的forall说明三项(forall-triplet-specification)的句法形式(R750)为:
index-name = scalar-integer-expression:&
scalar-integer-expression [:scalar-integer-expression]
其中的forall体结构的句法形式(R751)为:
assignment-statement
pointer- assignment-statement
where-construct
where-statement
forall-construct
forall-statement
FORALL结构的一般规则如下:
● 结构名称必须在FORALL语句与END FORALL语句里成对出现,或者都不出现。
● 指标名称必须是一个标量整型变量的名称。该名称的作用域与FORALL结构的作用域一致,并且在该作用域内保持自己的属性不变。
【例9-17】
SUBROUTING OPE(I,X)
INTEGER,INTENT(IN)::I
INTEGER X(:)
…
FORALL(I=1:SIZE(X))
X(I)=I
END FORALL
…..
END SUBROUTING OPE
在执行了上面的FORALL结构之后,X取值为(1,2,…),而I保持它在刚进入子例行程序时的取值不变;在FORALL结构里对I的定义过程也不影响I的意向属性IN。
● 在说明三项里的表达式里面,不能引用它所在的说明三项列里的指标名称。
【例9-18】 下面的FORALL语句是错误的:
FORALL(I=1:J,J=1:M)A(I,J)=I*2+J*3
必须换一种写法如下,才是有效的:
FORALL(I=1:N,J=1:M,I<=J)A(I,J)=I*2+J*3
● 一个标量逻辑型表达式定义了一个过滤网,其中可以引用指标名称。
【例9-19】
FORALL(I=1:100,J=1:100,X(I)/=0.0 .AND. Y(J)>0.0)
…
END FORALL
● 在定义过滤网的逻辑表达式或FORALL体结构里的任意位置,要引用过程的话,该过程必须是纯粹过程。
● 在FORALL体结构里指标名称不能被加以赋值。
● FORALL结构里的单个语句不能进行多对一的赋值。
【例9-20】
FORALL(J=1:30)
X(INDEX(J))=Y
END FORALL
上面的赋值只有在INDEX(1:30)不包含重复值的情形下才能进行,不过在FORALL结构里,可以通过使用多个语句做到赋值或指针赋值到同一个数据。
● FORALL体结构不能是分支结构。
● 每个说明三项列里的指标名称,是可以出现在被赋值的变量的下标或片断下标列表里面。
● FORALL结构里的赋值语句可以是数组赋值语句。
【例9-21】
REAL X(100,100)
…
FORALL(I=1:N)
X(I,:)=1.0/REAL(I)
…
上面的赋值语句把一个标量值1.0/REAL(I)赋值到数组X的第2维的每个坐标上。
● FORALL结构里的赋值语句可以是指针赋值语句。
【例9-22】
TYPE SCREW
CHARACTER(20),POINTER::HEAD_TYPE
REAL LENGTH,THREAD
END TYPE SCREW
TYPE(SCREW) INVENTORY(500)
REAL THREAD(100)
CHARACTER(20), TARGET:: HEAD_TYPES(5)
…
FORALL (I=1:500, INVENTORY(I)%LENGTH> .05)
INVENTORY(I)% HEAD_TYPE=> HEAD_TYPES &
(MOD(I-1, 5)+1
! HEAD_TYPES的下标是1,2,3,4,5,1,2,3,4,5,…
INVENTORY(I)%THREAD=THREADS((I-1)/5+1)
! THREADS的下标是1,1,1,1,1,2,2,2,2,2,3,3,3,3,3,…
END FORALL
所谓三项说明实际上就是一个三元组,即:
scalar-integer-expression:scalar-integer-expression:scalar-integer-expression
可以理解为一个数学上常规的三元组,三元组它的取值的集合即对这3个表达式值的集合的笛卡儿乘积。
FORALL结构的运行分如下3个步骤:
● 确定指标名称变量的取值;
● 如果存在过滤网表达式的话,计算该表达式;
● 执行体结构。
1. 确定指标名称变量的取值
首先对指标名称变量求值,得到指标值的集合。
对三元组里的3个标量整型表达式的求值,可以是任意次序。
如果有必要的话,需要把结果值的类型转换为指标名称的类型。
设3个表达式分别为m1,m2,m3,那么指标值的数目由如下公式决定:
n=(m2-m1+m3)/m3
其中如果m3不存在的话,取默认值1。
如果n小于或等于0,那么整个结构的执行就到此为止,否则指标名称的值的集合就是
m1+(k-1)*m3
而指标值组合的集合由每个三项说明给出的值集合的笛卡儿乘积决定。
2. 计算过滤网表达式
过滤网逻辑表达式的结果为TURE时,就从上面步骤得到的指标值的组合的集合(一个笛卡儿乘积)里选择出一个子集合,被称为指标值的激活组合。
3. 执行体结构
FORALL体结构里的可能的执行单位类型包括:
● 赋值语句;
● 指针赋值语句;
● WHERE结构或语句;
● 嵌套FORALL结构或语句。
整个体结构的执行次序,就是按照各个执行单位出现的先后次序。当每个单位得到执行时,必须遍历所有的指标值的激活组合。
下面分类型予以说明:
● 赋值语句
赋值语句的一般形式为
variable = expression
因此赋值语句的执行包括两个部分,即表达式对指标的求值,和每得到一个表达式的值就进行相应的赋值。因此每个部分都必须遍历所有的指标值的激活组合,这种遍历是不规定次序的。也就是被称为并行指标计算的来源。
如果赋值过程为自定义赋值,那么定义该赋值的子例行程序不能包含对任何被赋值的变量,或被关联的指针的引用。
● 指针赋值语句
指针赋值语句的一般形式为
pointer-object=>target
因此与赋值语句类似,指针赋值过程也包括两个部分,即目标表达式对指标的求值,和每得到一个目标的值就进行相应的关联。因此每个部分都必须遍历所有的指标值的激活组合,这种遍历同样是不规定次序的。
● WHERE结构或语句;
WHERE结构里的语句都是按照出现的顺序执行的。
首先是WHERE语句或结构,以及ELSEWHERE语句里的过滤网对所有的由外部FORALL结构给出的指标值的激活组合进行过滤,如果WHERE结构嵌套的话,则需要一直过滤到最里层,然后其中的赋值语句再对所有经过过滤剩下的指标值的激活组合进行赋值操作。
【例9-23】
INTEGER X(5,4)
…
INT_WHERE: FORALL (I=1:5)
WHERE(X(I,:)==0) X(I,:)=I
END FORALL INT_WHERE
如果X取初始值:
那么运算的结果为:
● 嵌套FORALL结构或语句。
内部FORALL结构或语句的头里的三项列中的表达式的计算是对外部FORALL结构的指标值的激活组合进行的,每个表达式由此得到的值的集合组合起来,即得到该层FORALL结构的指标值组合集合。然后再通过其中的过滤网得到它的指标值的激活组合的集合,再用这个指标值集合来控制其中的赋值语句。
【例9-24】
INTEGER X(3,3)
…
OUTER:FORALL(I=1:N-1)
INTER:FORALL(J=I+1:N)
X(I,J)=X(J,I)
END FORALL INTER
END FORALL OUTER
如果N取值3,X取初始值为:
那么经过上面程序的运算,得到值:
FORALL语句通过一个指标值集合以及可选的过滤网,对单独一个赋值语句或指针赋值语句进行控制。
FORALL语句的一般形式(R754)为:
FORALL(forall-triplet-specification-list [,scalar-logical-expression])&
forall-assignment-statement
其中forall赋值语句的句法形式(R752)为:
assignment-statement
pointer- assignment-statement
FORALL语句实际上可以把它看成是只包含一个为赋值语句或指针赋值语句的体结构的FORALL结构,它的运行过程就是那个FORALL结构的运行过程,其中头里面的指标的作用域就是该语句自身。
上面有关嵌套FORALL结构的例子里面的嵌套FORALL结构的功能可以使用一个单独的FORALL语句来表达:
FORALL(I=1:N-1,J=1:N,J>I)X(I,J)=X(J,I)