模块

模块也是一种在主程序单元之外独立编写的程序单元。它有独特的形式,即模块程序单元内没有可执行语句。除了说明语句外,最多包含内部过程。模块的主要作用是供其它程序单元引用。一个程序单元如果引用模块,实际上就是把该模块内的全部语句复制到本程序单元中,并且所有与模块中的名字相同的变量等,彼此取值相通、共享存贮单元。所以引用模块起两个作用:共享与复制。

如果模块程序单元中包含有内部过程,这些过程也可供其它程序单元使用。

6.3.1 数据共享

不同的程序单元是各个分别编译的,编译工作中很重要的一件事是给程序单元中各变量分配内部存贮单元(内存单元)。由于是分别编译的,程序单元A中的变量X所占存贮单元与程序单元B中变量X所占存贮单元不可能是在同一个存贮单元中,因此,程序单元中同名的变量名并不能相互传递数值。

要想使两个不同的程序单元间的变量相互传递数值有两个办法,一个是哑实结合,一个就是数据共享。

a)    共享方式

共享就是让编译系统把两个变量名分配在同一个内存单元中。已知访问一个变量名,实际上就是访问它所分配的内存单元中存贮的值。既然A程序单元中的变量XB程序单元中的变量Y分配在同一存贮单元中,访问X或访问Y都是访问同一个内存单元,取得的数也是相同的。

如果两个变量共享存贮单元,当A程序单元中X=2时,B程序单元中Y也是2。如果A程序单元中执行语句X=X+1,则B程序单元中Y的值也变为3,反之也然。当然,前提是XY的类型必须一致。如果类型不一致,其结果将很难预料。

共享方式有以下几种:使用COMMON语句和EQUIVALENCE语句(F77),使用模块(F90)。另外,使用INCLUDE语句可进行语句段复制。

b)    COMMON语句

F77中不同程序单元间数据的共享通常是用COMMONEQUIVALENCE语句,使用这些语句共享数据的效率不高,编程时容易出错。但F90仍保留了这两个语句以兼容以前的标准。

COMMON语句用于在不同程序单元之间进行实体的数据批量传递,它比哑实结合的方法进行数据传递的速度要快。其方法是开辟一个公共块,公共块可以是无名的(只能有一个),也可以是有名称的,其一般形式为:

COMMON [/[公共块名1]/]变量名表1[[,]/[公共块名2]/变量名表2]...

其中的公共块名可以和变量名相同,变量实体名不得是哑元、可分配数组、自动对象、函数名或函数结果名或ENTRY名,并且不得有PARAMETER属性。不同程序单元中相同公用名下的变量名可以是不同的。相同公用名中的变量在不同程序单元中,按位置一一对应共享同一存储单元中的数值。由于COMMON语句是说明语句,它的位置必须在可执行语句之前,通常是紧跟在程序单元的起始句之后,一个程序单元可以有多条COMMON语句。例如:下面的COMMON语句段

COMMON/happy/we,you,they

COMMON/     /our,your,their

COMMON/happy/i,he,she

COMMON/angry/dog,cat,mouse

COMMON my,his,her

等价于语句段,

COMMON/happy/we,you,they,i,he,she

COMMON/angry/dog,cat,mouse

COMMON/     /our,your,their,my,his,her

其中,有两条有名公共块语句,而‘//’是无名的。

由于各个程序单元中的变量名是独立的,它们并不会因为名字相同而建立起数值的联系。例如,主程序中名为X的变量和子程序中的X变量虽然同名,但它们各有自己的存储单元,互不相关。但如果我们在主程序和子程序的说明部分各自都增加一条无名共用区语句:COMMON X,则FORTRAN编译程序在存储区中开辟了一个公用数据区,主程序和子程序中的COMMON语句中的第一个变量共同占用公共块的第一个存储单元,达到数据共享。

例如:主程序中的语句

COMMON X,Y,Z(3),I

和子程序中的语句

COMMON A,B,C(3),J

使得无名公共块中变量XAYB,数组ZCIJ分别被分配在相同的存储单元中。占同一个存储单元的那些变量在不同的程序单位中,它们的名字不需要相同。

COMMON语句还可用来声明数组,例如:

COMMON /food/restruant(100),McDonald(10)

这条语句已经按I-N规则声明了实型数组和整型数组,无需用DIMENSION语句或属性对数组名重新说明。如果要重新定义类型的话,则数组大小不得在COMMON语句和类型说明语句中重复出现。例如:

SUBROUTINE unit1

REAL(8)      x(5)

INTEGER      J

CHARACTER    str*12

TYPE(member) club(50)

COMMON/blocka/x,j,str,club

...

 

SUBROUTINE unit2

REAL(8)      z(5)

INTEGER      m

CHARACTER    chr*12

TYPE(member) myclub(50)

COMMON/blocka/z,m,chr,myclub

c)    EQUIVALENCE语句

EQUIVALENCE语句是说明语句,它必须出现在程序单元的执行语句之前。它的作用是使同一个程序单元中的两个或更多的变量共用同一个存储单元,以节省内存。这里特别需要强调的是同一个程序单元。因此,主程序和过程之间以及过程相互之间不同变量不能用EQUIVALENCE语句来指定共用存储单元。等价语句的另一种用途是允许用两个或更多的变量名代表同一个量。等价语句的形式为:

EQUIVALENCE (变量名表1),(变量名表2),

等价语句后面的每一对括号内的变量表中,可以是变量名、数组名或数组元素等,但至少应该有两个变量名,之间用逗号分开。例如,EQUIVALENCE (A,B)语句指定本程序单位中的变量AB同占一个存储单元,也就是说这两个变量都从同一个存储单元中取值,只要其中一个变量得到某个值,其它一个变量也就必须具有相同的值。但必须强调这不是数学上的等值,而是由于共享一个存储单元而得到的方便。

如果数组出现在变量名表中,则它们的大小必须相同,等价时按数组元素的排列顺序一一对应。

d)    INCLUDE语句

可以用INCLUDE语句将另一个文件中的原程序段包括进来,实现复制功能。比如,可以将程序中多处引用的同一段语句块放入一个文件中,这样在调用时可以保持语句的形式和文字是完全相同的。INCLUDE语句的功能是让编译器停止读取当前文件而从一个文件中读取语句,读完后再继续读取当前文件中的下一条语句。它的一般形式是:

INCLUDE '文件名[/[NO]LIST]'

文件名是一被当前使用的操作系统认可的字符串,/[NO]LIST选项指明包含文件中的代码是否出现在编译源程序的列单中,缺省值是/NOLIST。包含文件中可以有其它的INCLUDE语句,但不能是递归的,否则将层层套圈直到耗尽系统资源。

例:主程序

PROGRAM

INCLUDE 'COMMON.FOR'

REAL,DIMENSION(M) :: Z

CALL CUBE

DO I=1,M

Z(I)=X(I)+SQRT(Y(I))

...

END DO

END

 

SUBROUTINE CUBE

INCLUDE 'COMMON.FOR'

DO I=1,M

X(I)=Y(I)**3

END DO

RETURN

END

包含文件COMMON.FOR是:

INTEGER,PARAMETER :: M=100

REAL,DIMENSION(M) :: X,Y

e)    模块

模块是F90中新增加的、使数据共享的最现代的手段。只要是出现在模块中的变量,都能与引用该模块的程序单元中的变量共享。模块中如果有内部过程,这些过程也可为各引用该模块的程序单元所共用,因而又起了过程库的作用。模块的共享关系示意图如下,双向箭头表示数据可存可取,单根连线表示模块内部过程供下面外部过程调用。

模块的功能是提供一种方便有效的常量、变量、类型定义及过程的共享途径。它可代替COMMONEQUIVALENCEINCLUDE语句的功能。模块的用途主要有:

*          包含通常使用的过程

*          声明全局变量和派生类型

*          声明外部过程的接口块

*          初始化全局数据和全局可分配数组

*          封装数据和处理数据的过程

模块程序单元有它独特的形式,即单元内没有可执行语句,除了说明语句外,最多包含内部过程。模块这种程序单元不能被直接执行,必须用USE语句引用。

存储在公共块COMMON中的变量可以被摘录和保存到模块之中。这样,通过引用模块,其它程序单元就可使用这些变量而不必包括COMMON块,从而不必在多个程序单元中声明变量。还可以保证在所有使用这些变量的程序单元中,变量声明是一致的,且是用同一个值初始化的。如果一个程序单元中,只使用模块中的部分变量或需要重新命名部分变量,该单元可以指定这些变量具有ONLY属性。

在程序中使用模块和使用INCLUDE语句很类似。每种情况下,都由某个独立文件中内容提供程序运行所需的信息。使用模块比使用INCLUDE配语句的优势在于:

*          封装数据和操作这些数据的过程:模块提供了封装相关定义和操作的简便方法,可以把多个相关但不同的文件中的信息结合到一个模块中,而不需使用多个INCLUDE语句。

*          变量、常数和模块过程的命名控制:允许临时重命名常数、变量甚至过程。

*          隐式接口:模块过程的参数和返回值对于主调程序是可知的,并且和调用过程匹配。

*          数据类型和操作符定义:允许为派生类型定义特殊操作符,还可以把内在操作符扩展到其它数据类型。

*          只在程序中一个位置指定信息:这样能保证使用该信息的不同的程序单元对信息的解释是一致的。例如,某个程序单元中的一条IMPLICITEXPLICIT语句可能会引起对一个包括文件的解释和其它包括这个文件的程序单元的解释不同。

6.3.2 模块的用法

a)    定义模块

模块单元的一般形式是:

MODULE 模块名

类型说明部分

[CONTAINS

内部过程

[内部过程]]

END MODULE [模块名]

MODULE语句下面写各种变量、数组等实体的类型说明语句,以及派生类型定义及接口块。注意到其中只有说明部分,没有执行部分。自CONTAINS语句开始连同它后面的各内部过程是可选的,一般不用。通常在为某一个派生类型规定新的操作符时,就把实现这些新操作的过程作为模块的内部过程放在CONTAINS后面,以便把这种操作定义供各外部过程共享。当模块有内部过程时,必须把整个过程完整地写入。各内部过程(可以是函数或子程序)次序可以任意。

模块程序单元可以不至一个,每个模块都独立编写,而后与主程序单元、外部过程单元一起输入机中编译、连接,才可以运行。

例:    MODULE DATA_MODULE

REAL, DIMENSION(1:10) :: A

INTEGER, PARAMETER :: I=15

INTEGER :: B=5

END MODULE DATA_MODULE

这个例子中,模块的实体只有类型说明语句,没有内部过程。如果有某一个外部过程引用了这一模块,则相当于把其中的三条类型说明语句移到该外部过程的说明部分中,IB的初值传递给外部过程中同名的实体。

例:    MODULE STUDENT_MODULE

TYPE STUDENT_TYPE

CHARACTER(LEN=20) :: NAME

INTEGER :: SCORE

END TYPE STUDENT_TYPE

END MODULE STUDENT_MODULE

该模块内把学生数据结构定义成一个派生类型。对于每个引用这个模块的外部过程而言,相当于把这一派生类型的定义移到自己的说明部分中,因而可以使用这个类型来说明程序体内的变量名。

例:    MODULE MY_MODULE

REAL, PARAMETER :: Pi=3.141592654

CONTAINS

SUBROUTINE SWAP(X,Y)

REAL :: TEMP,X,Y

TEMP=X

X=Y

Y=TEMP

END SUBROUTINE SWAP

END MODULE MY_MODULE

该模块内有一内部过程SWAP,引用这个模块的外部过程都将包含有此内部过程。

b)    引用模块

任何程序单元,要共享模块程序单元内的内容,只需引用该模块名,引用方法是在本程序单元说明部分的最前面加上USE语句。通过模块共享可以取代各程序单元间哑实结合,使有哑元的过程改为无哑元的过程。USE语句的一般形式为:

USE 模块名1, 模块名2, … 模块名n

USE语句表示在本程序单元中引用了模块1、模块2、…、模块n,即相当于把这些模块中的语句内容都移植到本程序单元内。使之其中的全部公用实体成为可访问的。USE语句还可以有多种引用方式,可视不同需要灵活引用模块,譬如可以只对模块中一部分变量共享等。它包括两方面:模块定义时规定只有哪些内容允许共享、引用模块时只要求共享哪些内容。

*        模块的PRIVATE属性

当定义派生类型的TYPE块写在模块中时,可以限制该派生类型定义的使用范围,以及类型定义内各成员的使用范围。譬如规定模块内的该派生类型或派生类型内的成员只供本模块内引用,不许模块外程序单元引用。其形式是:

TYPE,PRIVATE :: 派生类型名

成员1类型说明

……

成员n类型说明

END TYPE [派生类型名]

使用PRIVATE专用特性后,可以禁止一切外部过程(包括主程序)访问派生类型的内部成员,而只是把派生定义类型作为一个整体黑箱使用。以后如果派生类型内部成员要改动,只需改写派生类型部分,引用模块的各程序单元可不必改动。

*        模块内的变量改名

如果需要对多个模块进行访问,而在不同的模块中可能用到了相同的名字,因此允许USE语句对被访问的实体重新命名,以解决局部实体和模块中访问实体之间的名字冲突问题。要重新命名时,USE语句具有下列形式:

USE 模块名 [,改名列表]

其中,改名列表的形式为:局部名1=>块中名1, 局部名2=>块中名2, 。其中,局部名是使用USE语句的程序单元中用的名字,块中名是待改的模块内部使用的变量名。

如模块中定义的变量名是AB,程序单元中同名变量AB与之共享,但若要在程序单元中把变量名改为CD,则只需在单元内把引用语句写成:

USE模块名, C=>A, D=>B

即可,而无需修改模块。

*        ONLY选项

可以规定只取模块中一部分变量与本程序单元中实体共享,即只需要使用模块中的部分实体,其它实体没有共享关系。这时可在USE语句中使用ONLY选项。这时USE语句具有下列形式:

USE模块名, ONLY : [,仅用名列表]

其中,仅用名列表的形式为:[局部名1=>]块中名1, [局部名2=>]块中名2,

6.3.3 模块的应用

引入模块给程序设计带来了诸多方便,模块可应用于以下几个方面。

a)    全局数据

如果数据是在整个可执行程序中都要用到的全局数据,可以把它们放在一个模块中统一说明,在需要这些数据的程序单元内使用USE语句导出它们即可。例如:

MODULE DATA_MODULE

SAVE

REAL :: A(10), B, C(50,10)

INTEGER,PARAMETER :: I=10

COMPLEX D(I,I)

END MODULE DATA_MODULE

如果要在程序单元中访问模块中定义的全部数据对象,可使用语句:

USE DATA_MODULE

如果只需访问其中的AD,则可使用语句:

USE DATA_MODULE, ONLY : A,D

如果要避免名字冲突而改名的话,则可使用语句:

USE DATA_MODULE, ONLY : A_MODULE=>A, D_MODULE=>D

b)    过程共享

利用模块,还可以把整个可执行程序中都要用到的全局过程封装在一起,即把这些过程放在一个模块中统一说明,而在需要这些过程的程序单元内使用USE语句导出它们即可,这就是模块过程。使用的方式是,首先在模块中把待用的全局过程定义为模块的内部过程,即写在CONTAINS语句之后,在调用过程单元中写上USE语句以调用此模块,再写上接口块,接口块的实体是模块过程语句:

USE MODULE PROCEDURE 模块过程名表

例:    PROGRAM CHANGE_KIND

USE Module1

INTERFACE DEFAULT

MODULE PROCEDURE Sub1, Sub2

END INTERFACE

 

integer(2) in

integer indef

indef = DEFAULT(in)

END PROGRAM

 

MODULE Module1

CONTAINS

FUNCTION Sub1(y)

REAL(8) y

sub1 = REAL(y)

END FUNCTION

FUNCTION Sub2(z)

INTEGER(2) z

sub2 = INT(z)

END FUNCTION

END MODULE

c)    公用派生类型

模块还可用来封装一些派生类型,供其它程序单元公用。例如:

MODULE SPARSE_MATRIX

TYPE NONEZERO

REAL A

INTEGER I,J

END TYPE

END MODULE SPARE_MATRIX

定义了一个由实数和两个整数构成的数据类型,用以表示稀疏矩阵的非零元素。其中的实数表示元素的值,两个整数表示该元素的下标。于是,派生类型NONZERO就可被其他单元通过USE语句来共用。

d)    全局可分配数组

在许多程序中需要一些全局公用的可分配数组,它们的大小在程序执行前是不知道的,这时也可用模块来实现。例如:

PROGRAM MAIN

CAIL CONFIGURE_ARRAYS   !分配数组

CALL COMPUTE            !用分配好的数组进行计算

END PROGRAM MAIN

 

MODULE WORK_ARRAYS

INTEGER N

REAL,ALLOATABLE,SAVE :: A(:),B(:,:),C(:,:,:)

END MODULE WORK_ARRAYS

 

SUBROUTINE CONFIGURE_ARRAYS

USE WORK_ARRAYS

READ *,N

ALLOCATE(A(N),B(N,N),C(N,N,2*N))

END SUBROUTINE CONFIGURE_ARRAYS

 

SUBROUTINE COMPUTE

USE WORK_ARRAYS

END SUBROUTINE COMPUTE

e)    抽象数据类型和超载运算

可以用模块来自定义抽象数据类型,为此只需在模块中先定义派生类型,再随后定义可在这种类型值上进行的运算即可。例如:

MODULE INTERVAL_ARITHMETIC

 

TYPE INTERVAL

REAL LOWER,UPPER

END TYPE INTERVAL

 

INTERFACE OPERATOR(+)

MODULE PROCEDURE COMB_INTERVALS

END INTERFACE

 

INTERFACE OPERATOR(*)

MODULE PROCEDURE INTERSECTION_INTERVALS

END INTERFACE

 

CONTAINS

FUNCTION COMB_INTERVALS(A,B)

TYPE(INTERVAL) COMB_INTERVALS,A,B

COMB_INTERVALS%LOWER=MIN(A&LOWER,B%LOWER)

COMB_INTERVALS%UPPER=MAX(A&UPPER,B%UPPER)

END FUNCTION

 

FUNCTION INTERSECTION_INTERVALS(A,B)

TYPE(INTERVAL) INTERSECTION_INTERVALS,A,B

INTERSECTION_INTERVALS%LOWER=MAX(A&LOWER,B%LOWER)

INTERSECTION_INTERVALS%UPPER=MIN(A&UPPER,B%UPPER)

END FUNCTION

END MODULE INTERVAL_ARITHMETIC

模块中定义了派生类型INTERVAL,它表示一个实数区间,通过接口块说明定义的运算符‘+’和‘*’号,它们分别是求两个去见的并计和交集。

例:求π的计算。[pi.f90]

【作业】

[6.1] 设函数

要求编写成函数子程序及主程序,求X值分别取值0.5sqrt(15)sin(0.3)时的函数值,并打印输出。

[6.2] 编写函数子程序,求数组的积。在主程序中求

[6.3] 使用内部过程,由主程序读入10个整数,再进行从小到大排列,然后由主程序输出。