过程设计的基础内容,可参考:
过程设计之一(子例程SUBROUTINE)
过程设计之二(模块MODULE)
过程设计之三(函数FUNCTION)
1、 定义说明
当过程执行完毕后,其中的所有局部变量(或数组)的值都会清除,成为未定义的值,使得再次调用时与上次调用时的值可能一样也可能不一样。
在Fortran中,SAVE
能够保证过程在调用前后保存其中的局部变量(或数组)。
SAVE
属性:类型 , SAVE::变量1,变量2,...
,类型
是指各种数据类型;
SAVE
语句:SAVE [变量1,变量2,...]
,其中[]
表示可选。
2、注意事项:
SAVE
指定具体变量,则该变量连续调用时不会变,如果没有指定(仅一条SAVE
语句),则所有局部变量都会被保存起来;SAVE
属性,如REAL::a=1
等同于REAL,SAVE::a=1
,其中的SAVE
可加也可不加;SAVE
不能用于过程关联的形参或PARAMETER
中;SAVE
语句,确保模块中的数值在调用过程中保持完整。3、例子(取自《Fortran for Scientists and Engineers(4th) by Stephen J. Chapman》中的9-3例题,有少量修改,复制可运行)
程序的功能是:通过读取文件中的数据,返回不同情况下的平均值、标准差及当前计算所采用的数据个数。不同情况是指,当文件中有3个数时,逐一返回第1个、第1-2个,第1-3个的平均值、标准差和数据个数;当文件中有100个数时,逐一返回第1个、第1-2个,…,第1-100个的平均值、标准差和数据个数。因此反复调用计算平均值和标准差的子程序时,需要保存某些累计值。
代码中有解释,不过多解释。READ
的IOSTAT
子句参考具体使用方式。
主程序:
PROGRAM test_running_average
IMPLICIT NONE
INTEGER :: istat1 ! 文件打开状态
INTEGER :: istat2
REAL :: ave ! 平均值
REAL :: std_dev ! 标准差
CHARACTER(len=80) :: msg ! 文件打开错误返回信息
INTEGER :: nvals ! 数值的个数
REAL :: x ! 输入值
CHARACTER(len=20) :: file_name ! 文件名
CALL running_average ( 1., ave, std_dev, nvals, .TRUE. ) ! 清除累加值,这时候第一个参数可填入任意值,不影响结果(可对照子程序定义来看)
WRITE (*,*) ' 输入文件名: '
READ (*, 100 ) file_name
100 FORMAT(A20)
OPEN ( UNIT=11, FILE=file_name, STATUS='OLD', ACTION='READ', IOSTAT=istat1, IOMSG=msg ) ! 只读方式打开
openok: IF ( istat1 == 0 ) THEN ! 打开成功,则
calc: DO
READ (11,*,IOSTAT=istat2) x ! 获取数据
IF ( istat2 /= 0 ) EXIT ! 读取无效,则退出数据读取
CALL running_average ( x, ave, std_dev, nvals, .FALSE. ) ! 调用子程序计算
WRITE (*,200) 'Value = ', x, ' Ave = ', ave, ' Std_dev = ', std_dev, ' Nvals = ', nvals
200 FORMAT (3(A,F10.4),A,I6) ! 这里多了A
END DO calc
ELSE openok
WRITE (*,300) msg ! 文件打开失败,则
300 FORMAT (' 文件打开失败: ', A)
END IF openok
END PROGRAM test_running_average
子程序(子例程):
SUBROUTINE running_average ( x, ave, std_dev, nvals, reset )
IMPLICIT NONE
! 形参的定义
REAL, INTENT(IN) :: x ! 输入数据值
REAL, INTENT(OUT) :: ave ! 计算平均值
REAL, INTENT(OUT) :: std_dev ! 计算标准方差
INTEGER, INTENT(OUT) :: nvals ! 当前数值个数
LOGICAL, INTENT(IN) :: reset ! 复位标志,如果为真,清楚求出的和
! 局部变量的定义
INTEGER, SAVE :: n ! 输入值的个数
REAL, SAVE :: sum_x ! 输入值的和
REAL, SAVE :: sum_x2 ! 输入值的平方和
! REAL :: sum_x ! 输入值的和
! REAL :: sum_x2 ! 输入值的平方和 ! 如果是这样,由于没有保存,结果有问题
calc_sums: IF ( reset ) THEN ! 如果需要重置,则所有变量的值均为0,记住,只能将内部变量和输出值设为0
n = 0
sum_x = 0.
sum_x2 = 0.
ave = 0.
std_dev = 0.
nvals = 0
ELSE
! 不用重置,则计算
n = n + 1
sum_x = sum_x + x
sum_x2 = sum_x2 + x**2
ave = sum_x / REAL(n) ! 计算平均值
IF ( n >= 2 ) THEN
std_dev = SQRT( (REAL(n) * sum_x2 - sum_x**2) / (REAL(n) * REAL(n-1)) ) ! 计算标准差,标准公式拆成这样,注意是样本(分母为n-1),而不是总体
ELSE
std_dev = 0.
END IF
nvals = n ! 数据值的个数
END IF calc_sums
END SUBROUTINE running_average
需要打开的文件:
! read.txt
3.0
2.0
3.0
4.0
2.8
相应的运行结果为:
输入文件名:
read.txt
Value = 3.0000 Ave = 3.0000 Std_dev = 0.0000 Nvals = 1
Value = 2.0000 Ave = 2.5000 Std_dev = 0.7071 Nvals = 2
Value = 3.0000 Ave = 2.6667 Std_dev = 0.5774 Nvals = 3
Value = 4.0000 Ave = 3.0000 Std_dev = 0.8165 Nvals = 4
Value = 2.8000 Ave = 2.9600 Std_dev = 0.7127 Nvals = 5
是指没有任何负面影响的过程。换言之,调用纯过程不用担心输入参数会被修改,也不会修改在过程外部可见的其它数据(如模块中的数据)。
① 定义: 在过程定义语句前加上PURE
。
② 注意事项:
FORALL
结构,可以实现并行处理;SAVE
属性,也不能在类型声明中初始化局部变量(隐式SAVE
);STOP
语句;INTENT(IN)
;对于纯子例程,可以有INTENT(IN)
、INTENT(OUT)
和INTENT(INOUT)
。除此之外,函数和子例程所受的限制是相同的。③ 例子: 较简单,不过多解释。
PROGRAM func_test ! 主程序
IMPLICIT NONE
REAL :: rec_area_p ! 记得要声明函数名的类型,因为相当于是变量
REAL ::x1,y1
WRITE(*,*)'分别输入矩形两条边长:'
READ(*,*) x1,y1
WRITE(*,*) '矩形面积为:',rec_area_p(x1, y1) ! 调用函数
END PROGRAM func_test
PURE FUNCTION rec_area_p(x, y) ! 纯函数
IMPLICIT NONE
REAL, INTENT(IN) :: x, y
REAL :: rec_area_p
rec_area_p = x*y
END FUNCTION rec_area_p
相应的结果为:
分别输入矩形两条边长:
20,3
矩形面积为: 60.00000
逐元过程是为标量参数指定的过程,也适用于数组参数。参数与返回值具有相同的类型(详见例子)。
① 定义: 在过程定义语句前加上ELEMENTAL
。
② 注意事项:
③ 例子: 较简单,不过多解释。
PROGRAM func_test2 ! 主程序
IMPLICIT NONE
REAL :: rec_area_e ! 记得要声明函数名的类型,因为相当于是变量
REAL::x1,y1
REAL,DIMENSION(3) ::x2,y2
x1 = 2.
y1 = 4.
x2 = [1.,5.,3.]
y2 = [2.,2.,2.]
WRITE(*,100) rec_area_e(x1, y1) ! 调用函数
100 FORMAT('单个标量型:矩形面积为:',F5.1)
WRITE(*,200) rec_area_e(x2, y2) ! 调用函数
200 FORMAT('数组型:矩形面积分别为:',3F5.1)
END PROGRAM func_test2
ELEMENTAL FUNCTION rec_area_e(x, y) ! 逐元函数
IMPLICIT NONE
REAL, INTENT(IN) :: x, y
REAL :: rec_area_e
rec_area_e = x*y
END FUNCTION rec_area_e
相应的结果为:
单个标量型:矩形面积为: 8.0
数组型:矩形面积分别为: 2.0 10.0 6.0
在逐元过程基础上,新增条件使得可以修改形参(这样操作会改变外部的数据,详见例子),这类过程必须使用IMPURE
关键词声明,并且被修改的参数类型必须使用INTENT(INOUT)
来进行声明。
③ 例子: 较简单,不过多解释。
PROGRAM func_test3 ! 主程序
IMPLICIT NONE
REAL :: rec_area_ie ! 记得要声明函数名的类型,因为相当于是变量
REAL::x1,y1
REAL,DIMENSION(3) ::x2,y2
x1 = 2.
y1 = 4.
x2 = [1.,5.,3.]
y2 = [2.,2.,2.]
WRITE(*,100) rec_area_ie(x1, y1) ! 调用函数
100 FORMAT('单个标量型:矩形面积为:',F5.1)
WRITE(*,200) rec_area_ie(x2, y2) ! 调用函数
200 FORMAT('数组型:矩形面积分别为:',3F5.1)
END PROGRAM func_test3
IMPURE ELEMENTAL FUNCTION rec_area_ie(x, y) ! 不纯逐元函数
IMPLICIT NONE
REAL, INTENT(INOUT) :: x, y
REAL :: rec_area_ie
x = x + 1 ! 对形参进行修改,实参也会变化
y = y + 1
rec_area_ie = x*y
END FUNCTION rec_area_ie
相应的结果为:
单个标量型:矩形面积为: 15.0
数组型:矩形面积分别为: 6.0 18.0 12.0
除了外部过程(过程作为参数传递,如函数和子例程)和模块过程,在Fortran中还存在第三种过程——内部过程。
简单来说,内部过程是指程序单元A
(称为host过程
)中含有一个过程B
(可多个),但只能从程序单元A
中调用过程B
,外部过程不能直接调用过程B
。过程B
用CONTAINS
引入。(熟悉python
的同学可知,内部过程与if __name__ =='__main__'
具有相似效用)
一般来说,使用内部过程完成一些只需要在一个程序单元中执行,且必须重复执行的低级操作。
注意事项:
内部过程
只能被host过程
调用,其它过程不能直接访问该内部过程;内部过程
的名字不能作为命令行参数传递给其他的过程;内部过程
可以使用host过程
中定义的变量和参数;内部过程
中参数名和host过程
中参数名相同,如在内部过程
中是a=100
,在host过程
中是a=200
时,调用内部过程
时,a
的数据为100。例子: 可直接复制运行。
PROGRAM test ! 主程序
IMPLICIT NONE
REAL :: rec_area_test ! 记得要声明函数名的类型,因为相当于是变量
REAL::x1,y1
x1 = 2.
y1 = 4.
WRITE(*,100) rec_area_test(x1, y1) ! 调用函数
100 FORMAT('矩形面积为:',F5.1)
END PROGRAM test
FUNCTION rec_area_test(x, y) ! 调用的函数
IMPLICIT NONE
REAL, INTENT(IN) :: x, y
REAL :: rec_area_test
REAL,PARAMETER::a = 100.
rec_area_test = rec_area(x,y)
CONTAINS
REAL FUNCTION rec_area(x0, y0) ! 函数中的内部过程(函数)
REAL, INTENT(IN) :: x0 , y0
REAL,PARAMETER::a = 200. ! 如果将这句删去,则结果变成108.0
rec_area = x0*y0 + a
END FUNCTION rec_area
END FUNCTION rec_area_test
相应的结果为:
矩形面积为:208.0
如果在主程序PROGRAM
中直接调用内部函数rec_area
,则会报错:
error LNK2019: 无法解析的外部符号 _REC_AREA,该符号在函数 _MAIN__ 中被引用
对于模块过程,如果有多个过程,每个过程执行部分都很长,当对某一过程进行修改时,会不断地对模块进行修改,有时会不经意的将其它部分进行重写而不自知。
因此,Fortran提供了子模块功能,将模块中的过程拆分出来,形成新的模块(称为子模块)。
示例
MODULE name1 ! 模块
IMPLICIT NONE
INTERFACE ! 接口模块
MODULE SUBROUTINE name11(a, b, c) ! 子程序子模块
IMPLICIT NONE
REAL,INTENT(IN) :: a ! 形参定义
REAL,INTENT(IN) :: b
REAL,INTENT(OUT) :: c
END SUBROUTINE procedure1
MODULE REAL FUNCTION name12(a, b) ! 函数子模块
IMPLICIT NONE
REAL,INTENT(IN) :: a ! 形参定义
REAL,INTENT(IN) :: b
END FUNCTION func2
END INTERFACE
END MODULE name1
SUBMODULE (name1) name2 ! 子模块
IMPLICIT NONE
CONTAINS
MODULE PROCEDURE name11
... ! 局部变量定义、执行部分
END PROCEDURE name11
MODULE PROCEDURE name12
... ! 局部变量定义、执行部分
END PROCEDURE name12
END SUBMODULE name2
调用:
PROGRAM
USE name1 ! 调用模块
...
CALL name11 ! 调用子程序模块
...
END PROGRAM
注意事项:
子模块的功能相当于是把模块拆成两部分:
如果是接口需要更改,即更改调用参数,则模块及子模块都需要修改;如果是某个过程中执行部分需要修改,则仅子模块需要修改;
模块中包含了INTERFACE
块,每个过程的接口用MODULE
来引入;
模块中没有CONTAINS
语句,CONTAINS
语句在子模块中。