fortran95的使用方法4

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

第13章过程及其通讯
如果说一个语句可以看成是一条指令,那么在FORTRAN 95语言里,一个具有一定结构的计算任务可以对应的最小程序单位,就是一个过程。

从专致于科学计算的初衷出发,FORTRAN在语言的结构层面上,可以认为是面向过程的一种语言,尽管在编程语言流行面向对象的今天,面向过程显得有点落伍,但却有效地适用于描述计算任务,当然随着现代技术对于计算的要求越来越复杂与庞大,FORTRAN也不是一味地守旧,可以预计FORTRAN的下一个版本,就会具有适应大型软件工程的要求的面向对象的语言特性。

FORTRAN 95语言作为一种语言的主要特点,可以说就体现在它的过程这个主要的程序结构上。

而从我们程序编写者的角度来看,能否把一个完整的算法转写成一个完整的程序,关键也就在于能否构造出一些恰当的过程来作为程序的基本单位。

特别是对于大型的FORTRAN 95程序,需要把它分解为好几百个过程是很常见的,这时如何恰当地使用过程来构建整个程序,可以说是编写程序最主要的工作。

因此如何构建过程,如何根据需求运用过程,然后在不同的过程之间建立必要的通讯,是值得我们非常仔细地加以讨论的。

本章的主要任务即在于此。

13.1 过程的分类与性质
由于过程具有多方面的功能与属性,因此对于过程的分类可以有多种方式。

下面我们首先讨论过程的各种分类方式及其相应的分类意义,然后我们讨论有关过程引用的要点与相应概念。

13.1.1 过程的分类
过程可以有几个不同的分类方式,每种分类方式反映了过程的某个方面的特性。

下面分别予以讨论。

从形式上根据调用的方式的不同,FORTRAN的过程分为两类:
●函数;
函数返回一个可以供表达式使用的值。

因此函数的调用总是作为一个表达式的算元,函数的值也就是相应表达式算元的取值。

函数调用时直接使用函数的名称和它的变量,或者作为一个自定义运算,它返回值之后,它的功能就算完成,不对程序产生后效,当然,FORTRAN 标准也不绝对禁止使用产生一定后效的函数。

●子例行程序。

子例行程序的调用必须使用CALL语句,或者是作为一个赋值。

子例行程序的功能主要在于产生一定的后效,例如改写一些变元,以及全局变量,或者执行某些输入输出。

函数与子例行程序的一般规则如下:
● 函数与子例行程序在调用方式上的差别实际上来源于一个函数总是和一个相应的函数结果值相关联,只要运行或者调用一个函数,总会得到相应的函数结果值,而子例行程序却没有相应的概念,这就使得子例行程序只能依靠使用专门的CALL语句来调用,而既然函数总是能够给出一个函数值来,就可以直接把它作为表达式的算元来调用。

● 函数结果可以是任意的数据类型,例如派生类型,或者是取数组值都可以。

● 如果在FUNCTION语句当中使用RESULT属性,那么就可以给结果一个与函数定义里面的函数名称不同的名称,这主要应用于直接调用自身的递归函数。

● 在除了模块以及数据块程序单位之外的程序单位的说明部分还可以使用一种由一个语句组成的函数,称为语句函数,不过它的功能完全可以由下面的内部过程来实现,因此已经过时。

再从过程与其他程序单位的关系的角度来分类的话,过程也可以分为如下两大类:
●外部过程;
顾名思义,外部过程就是处于任何的程序单位的外部,它作为一个孤立的过程,可以单独构造,单独编译,单独使用,而可以完全独立于任何的过程与程序单位。

甚至还可以是用其他的语言编写的,例如常见的C语言。

当然,一个外部过程还是可以通过变元列表,模块,公用块等来共享数据与过程之类的信息。

●内部过程。

内部过程总是定义在一个程序单位内部,该程序单位就称为它的宿主。

当一个过程出现在它的宿主内部的时候,总是出现在宿主的CONTAINS语句和END 语句之间。

一个内部过程对于其宿主而言,总是局域的,而且通过宿主关联继承了宿主的数据环境。

特别的,如果一个内部过程的宿主是一个模块,那么这种内部过程被单独称为模块过程。

●模块过程。

模块过程同样必须出现在其宿主模块里面的CONTAINS语句和END语句之间。

一个模块同样可以通过宿主关联继承宿主模块的数据环境。

但与一般内部过程不同的是,模块内的模块过程可以具有PUBLIC属性,即从模块外部可以直接访问具有PUBLIC属性的过程;当然模块过程也可以具有PRIVATE属性,使得模块外部不能访问该过程。

注意在早期FORTRAN版本里面出现过语句函数的概念,它是出现在程序单位的说明部分的单语句定义的函数,不能够出现在模块或数据块程序单位当中。

由于它的功能可以完全由内部过程替代,因此语句函数的概念已经过时。

一般由多个过程组成过程子程序。

对于内部过程来说,组成过程子程序的多个过程可以通过宿主关联而获得对宿主数据环境的共享;而对于外部过程来说,当一些外部过程要组成过程子程序的时候,由于缺乏类似的关联机制,它们的数据共享问题是通过所谓过程登录机制来解决的。

所谓过程登录是通过运用ENTRY语句来说明一个过程与一个过程子程序的关联关系的。

每一个ENTRY语句就给出了一个过程,所有这些过程就可以共享该过程子程序的数据环境。

例如两个不同的函数SIN和COS就可以通过这种方式使用同一个外部子程序,因为SIN和COS具有如下的关系:
cos()sin()2x x π
=-。

尽管对于内部过程来说,过程登录是不必要的,但也可以应用于模块过程。

根据过程对于变元的作用效果来分类,则可以把过程分为纯过程与非纯过程。

所谓纯过程就是在执行对变元的作用之后,不会产生后效,这对于程序按照序列进行一般是没有什么影响的,但是对于程序的并行执行,却往往会给程序带来不确定性,因此就需要对一般的过程加以约束,使得其完全没有后效,从而得到纯过程。

例如过程A 和B ,都具有对X 进行赋值的后效,设A 把X 赋值为1.0,而B 把X 赋值为2.0,那么如果A 和B 这两个过程是并行完成的,那么X 的具体取值就变得不可预料,因此需要对过程A 和B 进行一定的约束,使得它们成为纯过程。

如果从过程作用于数据对象的方式来进行分类,那么过程可以分为逐元过程和变换过程。

● 逐元过程,即过程的本来属于标量形式的变元可以被赋予数组,这时过程的作用方式是对数组的每个元素进行单独的运算,得到的所有结果再构成一个与变元数组相同形状的数组,就称为该过程作用于数组后的结果。

● 变换过程,即过程的变元本来就是数组,对数组的作用方式是把数组作为一个整体进行变换,而不是逐个地针对元素进行。

因此逐元过程既可以标量变元来引用,也可以通过数组变元来引用,只是当通过数组变元来引用的时候,实际上就是通过标量数组元素来引用,只不过需要引用多次引用的次数等于数组元素的个数。

逐元过程总是纯过程,因为逐元过程不产生后效。

如果从过程的来源来看,过程还可以分为固有过程和自定义过程两类。

所谓固有过程,也称为内建过程,即FORTRAN 标准已经建立好了的过程,一共包括115个固有过程。

这意味着任何的FORTRAN 实现,都必须能够直接提供这些固有过程。

固有过程的名称在程序单位的任何地方都是可以直接引用的,唯一的例外,就是当使用了显式的EXTERNAL 语句或过程界面块的时候,固有过程的通用属性就可能被扩充,即固有过程的名称也可能用来引用非固有过程,这时该名称所表示的固有过程反而成了一种默认值。

大部分的固有过程同时还是逐元的,自定义过程也可以被定义为逐元过程,这样就极大地增强了FORTRAN 的数组计算能力。

13.1.2 过程的引用
所谓过程的引用就是使得过程的名称出现在程序的恰当位置,当程序运行到该位置时,过程名称能够导致程序转入执行该过程。

过程的引用也可以称为调用或访问,在本书当中,这几个词汇是根据一般用词习惯而交替使用的。

过程的引用分为两种形式,即子例行程序的引用和函数的引用。

● 子例行程序的引用是依靠一个单独的CALL 语句,某些时候也可以使用赋值语句的形式。

●函数的引用是作为表达式的一部分,即采用函数名称及其变元列表作为表达式的项
出现,某些时候,函数引用也可以采取一元运算或二元运算的形式,其变元作为表达式的算元。

过程的引用本质上就是通过变元来进行数据的传递或通讯,在进行过程的引用过程当中,变元根据它所行使的功能,还可以分为三种:实元,哑元,替代返回变元。

●实元
在过程的引用当中,所谓实元就是在程序执行的时候需要实际采用的数据对象。

例如实元在作为变量的时候,可以在每次引用当中取不同的值,因为作为实元,是直接面向具体取值的。

实元的取值可以是输入值,也可以是过程运行之后返回的值,或者两者都是。

●哑元
所谓哑元就是一个名称,通过该名称过程内部就能够访问到相应实元。

因此哑元在过程被定义的时候就已经给出,在过程进行计算时,它就充当变元名称。

一旦在程序运行时引用该过程,那么引用的实元就通过变元关联而与相应哑元建立关联关系。

如果过程界面是显式的,那么在引用过程时,就可以直接使用哑元名称作为实元关键词。

过程定义里面的哑元名称还可以用来指称一个过程,这实际上就是一个过程引用。

因为与该哑元具有变元关联关系的实元名称就是一个过程的名称,不过这个被关联的过程不能是内部过程,语句函数,已经具有类属性的过程。

●替代返回变元
所谓替代返回是只在子例行程序里面出现的一种变元的功能,通过替代返回,就可以中断程序的顺序执行次序,控制程序的执行分支到其他某个位置,替代返回里面的实元就是分支目标语句的标签,这种分支方式经常用于从子例行程序里面出错退出的情形,不过在现代的FORTRAN控制结构里面,具有更合理的控制方式,因此这种分支方式已经过时。

过程通过变元进行通讯的途径就是关联,过程关联的形式有4种:变元关联,宿主关联,使用关联,以及共享存储关联。

●变元关联
变元关联是过程之间进行数据通讯的主要方式。

在13.7节会详细地加以讨论。

●宿主关联
宿主关联是建立在一个过程与其所属的程序单位之间的数据关联关系。

一个宿主程序单位可以是一个主程序,模块程序单位,外部过程,模块过程。

这些宿主程序单位里面所有被允许访问的数据对象都可以通过宿主关联而被其包含的过程访问到。

对于宿主关联的详细讨论见第15章。

●使用关联
一个过程可以通过一个USE语句和该USE语句所在的程序单位建立使用关联。

通过使用管关联,过程可以和模块共享数据对象,也可以按照程序编写者的意愿,通过ONLY子句来隐藏某些数据对象。

对于使用关联的详细讨论见第12章。

●共享存储关联
有关存储关联的详细讨论见第7章和第15章。

在组织程序时,除了在不同过程之间进行通讯之外,过程自身还可以构成递归的结构。

即一个局部变量得到初始化之后,尽管它出现在递归的每一层里面,但是其初始化值只能够在该递归结构开始运行时有效地使用一次,因此实际上等价于对于变量使用了SAVE属性,使得每一层的递归都保留了它的变量值,可以说是一种数据的隐式保留属性。

一个过程无论是进行直接的递归还是进行间接的递归,都必须在过程定义里面的FUNCTION语句或SUBROUTINE语句里面加上关键词RECURSIVE,这个关键词的功能就是能够实现编译器对于过程调用的优化。

过程的引用并不涉及过程的具体功能,也就是对于过程的内部计算过程,在引用时是不需要考虑的,这样就可以把所有涉及到过程引用的信息集中在一起,包括过程及其变元的名称与属性,这就构成了所谓过程的界面。

顾名思义,也就是一个过程与外部进行通讯的层面。

如果一个过程的这些信息对于一个引用它的程序单位来说,不是显式的,那么该过程就被称为具有隐式界面。

这时,引用它的程序单位就只能依据过程名称以及过程引用里面的实元的属性来确定过程的有关信息,同时有关的哑元与实元的数据匹配都需要程序编写者预先规划好。

如果过程界面是显式,那么相关的数据匹配工作就可以完全由编译系统来完成检查。

在很多的引用情形下,都一定要求被引用的过程具有显式界面,例如:
●作为数组片断的实元;
●指针变元;
●可选变元;
●关键词调用;
●自定义运算;
●自定义赋值;
●自定义过程的逐元调用;
●自定义类过程。

●各种类型的过程的界面形式的显式或隐式的情况如下:
●固有函数,内部过程,模块过程都是显式的界面,自定义过程也可以具有显式界面。

所有固有函数的显式界面信息都包含在任何一个FORTRAN编译器内部,在对固有函数进行任何引用时,系统都可以根据实元的类型来检查与匹配相应的固有函数,从而得到一个正确的引用。

因此对固有函数的引用可以直接使用函数的类名称以及关键词。

内部过程和模块过程在它的定义里面就要求具有显式界面,因此对这两种过程的引用同样是安全可靠的。

●外部过程和语句函数都是隐式的界面。

不过在引用外部过程时,可以在引用程序单位里面为所引用的外部过程提供一个界面块,这样就等价于该外部过程具有了一个显式的界面。

有关界面的详细讨论见13.8节。

正是由于引用只与过程的界面有关,因此被引用的过程可以是使用其他语句编写的,例如C,C++等,不过使用非FORTRAN的过程可能移植性不太好,因为在不同的系统之间,其他的语言可能具有不同的过程通讯机制。

13.2 子例行程序的运用
一个子例行程序就是一个完备的能够执行一定的计算任务的程序单位。

它由以下五个部分构成:
●初始的SUBROUTINE语句;
●说明部分;
●计算执行部分;
●某些内部过程;
●END语句。

当一个子例行程序被调用时,从它的第一个可执行结构开始运行,它通过变元关联,使用关联,宿主关联以及存储关联与外部进行数据通讯。

下面分别讨论子例行程序的定义与引用。

13.2.1 子例行程序的定义
一个子例行程序,无论是内部的,外部的,还是模块的,都具有以下语法形式(R1221):
[subroutine-prefix] SUBROUTINE subroutine-name &
[([dummy-argument-list])]
[specification-part]
[execution-part]
[internal-subprogram-part]
END [ SUBROUTINE [subroutine-name]]
其中所谓子例行程序的前缀(subroutine-prefix)是如下的几个可选项:
RECURSIVE
PURE
ELEMENTAL
不过其中表示递归属性和逐元属性的RECURSIVE与ELEMENTAL不能同时作为同一个子例行程序的前缀。

其中哑元列表(dummy-argument-list)里面的哑元既可以是哑元名称,也可以是星号(*)。

星号表示替代返回变元。

当子例行程序被执行的时候,这些哑元就获得了同引用子例行程序时给出的实元的关联。

子例行程序的一般规则如下:
● 如果在END语句里面使用了子例行程序的名称,那么这个名称就必须和SUBROUTINE语句里面的名称一致。

● 一个内部子例行程序不能再包含一个内部子程序部分。

● 内部子例行程序不能包含ENTRY语句。

● 内部子例行程序与模块子例行程序的END语句必须是如下的形式:
END SUBROUTINE [subroutine-name]
即必须在END语句里面使用关键词SUBROUTINE。

● 如果子例行程序要实现递归结构,那么在SUBROUTINE语句里面必须加上前缀RECURSIVE。

● 在一个SUBROUTINE语句里面,不能重复使用某个前缀。

● 一个子例行程序不能同时是递归的和逐元的。

参见13.4节与13.5节。

● 每个哑元都是其所在是子例行程序的局部变元。

哑元可以在子例行程序里面获得显式说明,也可以在某些特定情况下获得隐式声明。

● 子例行程序里面的哑元都可以具有INTENT属性或OPTIONAL属性,但是哑指针和哑过程都不能具有INTENT属性。

● 在子例行程序里面不能使用PUBLIC属性和PRIVATE属性。

● 用于表示替代返回变元的星号*是一种过时的语言特征。

【例13-1】
下面是SUBROUTINE语句的几种形式的例子:
ELEMENTAL SUBROUTINE BESSELL1
SUBROUTINE IMBEDDING(X)
SUBROUTINE TASK()
SUBROUTINE PARTICAL(CURRENT,*)
SUBROUTINE COMPUT2
RECURSIVE SUBROUTINE WALLIS(X,Y)
【例13-2】
下面是一个子例行程序的例子:
SUBROUTINE WAVELET(A,B)
REAL A
INTEGER B
….
END SUBROUTINE WAVELET
13.2.2 子例行程序的引用
当程序的某个位置需要调用一个子例行程序时,就在该位置使用CALL语句或者是一个自定义赋值语句。

实际上对子例行程序的调用,就是给出子例行程序的名称和实元。

下面首先讨论使用CALL语句的引用方式。

CALL语句的语法形式(R1211)为:
CALL subroutine-name [([subroutine-actual-argument-list])]
其中的子例行程序的实元的语法形式(R1212)为:
[keyword=] subroutine-actual-argument
其中的的关键词是子例行程序的界面里面的某个哑元名称,而子例行程序的实元则具有以下几种形式(R1214)之一:
包含一个变量的表达式;
过程名称;
作为替代返回的*标签。

以上子例行程序的引用实际上就是建立一个哑元与实元的关联。

即或者是通过直接使用哑元名称作为关键词,给出相应的实元,或者是省略了关键词,但是实元在实元列表里面的
顺序对应着相应的子例行程序界面里面的哑元列表的顺序,从而为每个子例行程序的哑元指定了相应的实元。

作为实元的表达式的一个特殊情况就是只有一个变量自身,这时该变量作为实元可以与具有任意INTENT(IN,OUT,INOUT)属性的哑元相关联,但一个表达式就只能和具有INTENT(IN)属性的哑元相关联。

CALL语句的引用的一般规则如下:
● 根据变元列表位置来进行实元与哑元的关联的变元,必须放置在实元列表的前面部分,而一旦在该列表当中出现了关键词,那么该关键词后面的变元就必须全部是关键词。

● 在实元列表与哑元列表的对应当中,一个实元和与之对应的非可选哑元相关联,而对于可选哑元,相应的实元可省略。

● 关键词就是子例行程序的显式界面里面的哑元名称,如果在引用里面使用关键词,那么实元就与具有该名称的哑元相关联。

● 如果在一个实元列表当中,与某个实元相关联的关键词被省略了,那么在这个列里面该实元前面的所有关键词都必须省略;一个没有关键词出现的实元列表里面的所有实元,都是依据列表位置来确定它们相应的哑元的。

● 如果一个实元列表使用了多个关键词,那么关键词的顺序是无关紧要的,如果一个实元列表里面全是关键词,那么它们的顺序可以是任意,不需要与哑元列表里面的顺序有对应关系。

● 作为替代返回说明符的星号后面的标签,必须是与CALL语句同一个作用域单位的分支目标语句的标签。

● 实元不能是内部过程或语句函数的名称。

● 与一个哑过程相关联的实元必须是一个种过程的名称,如果一个过程的类名称与种过程相同,那么实际引用的是种过程。

● 对于某些固有函数来说,它们的种固有函数名称不能用来作为实元。

过程引用的另外一种形式就是使用自定义赋值语句。

可以通过自定义赋值形式得到引用的子例行程序必须具有一个ASSIGNMENT界面,在这个界面里面必须给出两个变元arg1和arg2,其中arg1具有INTENT(OUT)或INTENT(INOUT)属性,而arg2则具有INTENT(IN)属性。

自定义赋值引用的形式为:
arg1 = arg2
对于这种引用形式的详细讨论见13.8.5节。

【例13-3】下面是一个通过CALL语句引用子例行程序的例子。

CALL SUB1(100.01+X,*25) !引用SUBROUTINE SUB1

25 …!替代返回的目标语句

CALL SUB2(X=5.02,N=75) !引用SUBROUTINE SUB2(X,N)

【例13-4】下面是一个通过自定义赋值形式引用子例行程序的例子。

MODULE POLAR_COORDINATES
TYPE POLAR
REAL ::RHO THETA
END TYPE
INTERFACE ASSIGNMENT(=)
MODULE PROCEDURE ASSIGN_POLAR_TO_COMPLEX
END INTERFACE

SUBROUTINE ASSIGN_POLAR_TO_COMPLEX (Z, P)
COMPLEX INTENT ( OUT ) :: Z
TYPE (POLAR) INTENT (IN) :: P
Z= CMPLX (P%RHO * COS (P%THETA), &
P%RHO * SIN (P%THETA))
END SUBROUTINE ASSIGN_POLAR_TO_COMPLEX
USE POLAR_COORDINATES
COMPLEX ::CARTESIAN

CARTESIAN = POLAR(R,PI/6)
在上面最后语句里面,使用了一个赋值语句,实际上就等价于下面的CALL语句:CALL ASSIGN_POLAR_TO_COMPLEX(CARTESIAN,POLAR(R,PI/6))
13.3 函数
函数与子例行程序的最大区别就是函数可以用来作为表达式的项,而函数的定义的语法结构则与子例行程序类似。

函数的变元用来引入初始值,然后经过函数的计算,得到函数结果,作为表达式相应的项的输入值。

函数同样可以通过4种关联形式从函数外部获得数据。

13.3.1 函数的定义
一个函数子程序,不管它是外部子程序,内部子程序还是模块子程序,都具有如下的语法形式:
[function-prefix] simplest-function-statement [RESULT (result-name) ]
[specification-part]
[execution-part]
[internal-subprogram-part]
END [FUNCTION [function-name]]
其中的函数前缀(function-prefix)包括如下几种形式:
type-specification
RECURSIVE
PURE
ELEMENTAL
其中RECURSIVE和ELEMENTAL不能同时用于一个函数。

其中的最简单FUNCTION语句(simplest-function-statement)的语法形式为:FUNCTION function-name ([dummy-argument-name-list])
其中的哑元在函数运行时获得引用实元的变量关联。

因此函数语句可以有几种不同的形式,下面是一些例子。

【例13-5】
FUNCTION LAGRANGE1 (X,Y)
COMPLEX FUNCTION SHORDINGER2(X,T)
FUNCTION LATTICE(X,Y) RESULT(CROSS)
RECURSIVE REAL FUNCTION SUM1(N,K)RESULT(SUM2)
函数遵循如下规则:
●函数的类型既可以在函数语句当中予以说明,也可以使用单独的类型声明语句当中
予以说明,但是不能同时采用这两种说明方式。

如果这两种说明方式都没有出现,那么系统会认为是默认类型。

●如果函数值是数组或指针,那么函数声明必须给出其函数结果名称的相应属
性。

函数结果名称可以是显形数组或哑形数组。

如果为显形数组,那么它的非常数界可以是表达式形式。

●哑元的属性既可以在函数体当中显式说明,也可以隐式说明。

●哑元总是相应函数的局部变量,因此哑元名称在函数内部必须是唯一的。

●如果在END语句当中出现了函数名称,那么该名称必须和FUNCTION语句当中的
函数名称一样。

●一个内部函数不能再包含一个内部子程序。

●一个内部函数不能包含ENTRY语句。

●在一个函数语句当中,函数前缀不能重复出现。

●一个函数不能同时具有前缀ELEMENTAL和RECURSIVE。

参见13.4和13.5节。

●内部函数和模块函数的END语句必须是如下形式:
END FUNCTION [function-name]
即在END语句当中必须包含关键词FUNCTION。

●如果函数语句当中不出现结果子句,那么函数名称就用作结果变量的名称,则对函
数名称的引用实质上就是对函数结果变量的引用。

●如果函数语句当中出现结果子句,那么函数名称就不能用作结果变量的名称,则对
函数名称的引用就是函数引用,属于递归调用。

●如果函数语句当中出现结果子句,那么函数名称就不能出现在任何说明语句当中。

●如果函数的结果值不是一个指针,那么在函数执行完毕之前,它的结果值就必须完
全给定定义:如果结果值是数组,那么数组的所有元素都必须给出定义;如果结果值是结构,那么结构的所有成员都必须给出定义。

相关文档
最新文档