与 c 语言只有函数不同,Fortran 提供了子程序 subroutine,函数
function,模块 module 等多种结构。
子程序 subroutine
首先,回顾之前的 HelloWorld
1 2 3 4 program main implicit none write (*,*) "hello,world!" end program main
其中使用了名称为 main 的主程序 program,一个可执行程序只允许有一个
program,为了分离和复用某个功能,可以使用子程序 subroutine。
1 2 3 4 5 6 7 8 9 10 11 12 program main implicit none write (*,*) "hello,world!" call hello() end program mainsubroutine hello() implicit none write (*,*) "hello,subroutine!" end subroutine hello
运行结果
1 2 hello,world! hello,subroutine!
主程序和子程序的相对位置无关,子程序不需要写在 call
调用它之前,主程序也不需要写在开头。子程序可以 call
调用其他子程序,也可以递归调用自身。子程序可以用 return
语句结束,或者遇到 end subroutine 自动结束。
子程序拥有绝对独立的变量和运行环境等,与主程序完全隔离,不存在变量覆盖或者重名等问题。因此在子程序中也需要
implicit none
语句。当然子程序需要和主程序产生交互,主程序可以传递指定的变量给子程序,形参和实参可以名称各异,注意形参同样需要声明。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 program main implicit none real :: a=1.0 write (*,*) "hello,world!" write (*,*) "a=" ,a call hello(a) write (*,*) "a=" ,a end program mainsubroutine hello(b) implicit none real :: b write (*,*) "hello,subroutine!" b = 2.0 end subroutine hello
注意 Fortran
传递参数默认使用传址调用,因此对于参数变量,主程序和子程序共享一个内存,修改可以反馈给主程序,这也是子程序存在的意义。 运行结果:
1 2 3 4 hello,world! a= 1.00000000 hello,subroutine! a= 2.00000000
因为默认使用了传址调用,但子程序的实参允许使用字面值常量,如果在子程序中尝试进行了修改,程序会直接崩溃。
形参属性
为了明确形参的使用意图,可以在形参声明时指定参数属性,与属性冲突时编译会警告或报错。
1 2 3 real ,intent (in ) :: a real ,intent (out ) :: b real ,intent (inout) :: c
形参缺省
1 2 3 4 5 6 7 8 9 10 subroutine test(a,b) implicit none integer :: a integer , optional :: b if (present (b)) then end if end subroutine test
对于这种特殊情形,在使用前必须要使用 interface
更具体地描述接口,见下文。
变量生存周期
通常情况下,子程序结束时变量全部清除,但是在变量声明时使用 save
可以设置局部静态变量,变量会保留到下一次调用。
1 integer ,save :: count = 1
注意:有的编译器可能默认对所有的局部变量都视作局部静态的。
函数 function
与子程序主要有两点不同:函数在主程序内进行使用之前需要声明,函数有一个返回值。似乎
subroutine 的应用比 function 更广泛。作为子程序 subroutine 和函数
function 的对比,建议在 function 中不对形参做任何处理。在 function
中也需要使用 implicit none。
第一种,标准形式,返回值和函数同名。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 program main implicit none real (kind =4 ) :: a=1.0 ,b=2.0 real ,external :: add write (*,*) "hello,world!" ,a,b write (*,*) "a+b=" , add(a,b) end program mainfunction add(a,b) implicit none real :: add real :: a,b add = a+b return end
这里 external 表明是一个自定义函数,类似地可以用关键词 intrinsic
表明是一个库函数。
1 2 real , intrinsic :: sin real , external :: mysin
第二种,函数在定义时直接指出返回值类型。
1 2 3 4 5 6 real function add(a,b) implicit none real :: a,b add = a+b return end
第三种,简短的函数定义,只能在当前区域被使用。
1 2 3 4 5 6 7 8 9 10 11 12 program main implicit none real (kind =4 ) :: a=1.0 ,b=2.0 real :: add add(a,b) = a+b write (*,*) "hello,world!" ,a,b write (*,*) "a+b=" , add(a,b) end program main
第四种,在函数内部指明返回值的名称,对于调用方则没有任何影响。
1 2 3 4 5 6 7 real function add(a,b) result(ans) implicit none real :: a,b ans = a+b return end
在递归函数中,必须要把返回值和函数名分开,同时还需要 recursive
关键词。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 program main implicit none integer :: n=5 , result integer , external :: fact result = fact(n) write (*,*) "10!=" ,result end program mainrecursive integer function fact(n) result(ans) implicit none integer ,intent (in ) :: n if (n < 0 ) then ans = -1 else if (n <= 1 ) then ans = 1 else ans = n * fact(n-1 ) end if return end
第五种,内部函数:为了限制子程序/自定义函数的应用范围,可以使用
contains 命令把定义直接附在主程序内的最后部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 program main implicit none integer :: m=5 , result result = fact(m) write (*,*) "10!=" ,result contains recursive integer function fact(n) result(ans) implicit none integer ,intent (in ) :: n if (n < 0 ) then ans = -1 else if (n <= 1 ) then ans = 1 else ans = n * fact(n-1 ) end if return end end program main
模块 module
模块 module 最基本的用法是封装与共享全局静态变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 module global implicit none integer , save :: a,b end module globalprogram main use global implicit none a = 1 b = 2 call sub() end program mainsubroutine sub() use global implicit none write (*,*) a,b return end subroutine sub
这里名为 global 的模块 module 需要在源文件的开头部分,在 use global
之前。主程序和子程序无须声明变量即可使用,基于 save 属性(或者
common)可以实现更好的全局静态变量的效果。如果不使用 save
则不能共享变量。
为了更全面地了解 module,必须结合 Fortran 的面向对象进行,module
内部的变量和函数/子程序表现的非常像类的结构。(Fortran
甚至还可以进行简单的运算符重载,函数重载和 module
继承,是为了得到面向对象风格进行的尝试,但可能存在各种麻烦,不去管它们)
module 实例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 module bank implicit none private :: money public :: LoadMoney, SaveMoney, Report integer :: money = 100 contains subroutine LoadMoney(num) implicit none integer :: num money = money - num return end subroutine subroutine SaveMoney(num) implicit none integer :: num money = money + num return end subroutine subroutine Report() implicit none write (*,*) "now money=" , money return end subroutine end module bankprogram main use bank implicit none call LoadMoney(10 ) call Report() call SaveMoney(100 ) call Report() stop end program main
运行结果为
1 2 now money= 90 now money= 190
private 和
public:需要使用单独的语句强调变量或者子程序/函数是私有还是公开的,默认是
public 的。
单行的 public/private
描述符是针对全局的,除非显式声明为另一种,下文只有 c
是公开的,对于函数同样适用。
1 2 3 4 5 6 7 8 module hello implicit none private real :: a integer :: b public real :: c end module hello
use 命令
上述的 module
还是非常鸡肋的面向对象效果,因为没有实现类和对象的分层,use bank
就会直接地使用它。module 内部 use 其他模块,但是不建议使用。
重名变量的改名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 module A implicit none integer :: va end module Amodule B implicit none integer :: va end module Bprogram main use A use B, bva=>va end program main
如上,模块 A 和 B 同时被使用,则产生了 va
重名的问题,可以改名加以区分。
变量的限制导入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 module A implicit none integer :: va,vb end module Amodule B implicit none integer :: va,vb,vc end module Bprogram main use A use B, only : vc end program main
建议使用下列语法进行安全的 use
1 2 3 use A, only Ax=>xuse A, only Ay=>y
补充
有几种过时的语法,提一句:
common 声明变量是全局的,在多个主程序/子程序中都使用 common
对同一个变量进行声明,则可以共享同一个全局变量。
data 和 block data 用来批量地赋初值。
多文件编程
现代 Fortran 建议以主程序 program 和多个 module 组成。所有的函数
function 和子程序 subroutine 应该封装在 module
中。这样既可以避免同名函数导致的链接错误,更能使编译器在链接时帮忙检查参数列表。
include 命令
相当于 c 语言的 include,是简单直接的代码注入,例如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 program main implicit none call sub() stop end program maininclude 'sub.f90' subroutine sub() write (*,*) "hello ,world" return end subroutine
此时不能把 main.f90 和 sub.f90
一起编译,否则会产生重复定义的问题,只能对 main.f90 进行编译,与 c
语言类似。这种 include 方式一般在 Fortran 77 中使用,在 Fortran 90
之后,建议基于 module 进行多文件编程。
module 多文件组织
建议除了主程序源文件包含一个 program 之外,其他单个源文件都由一个
module 组成,把子程序和函数都封装在 module
里,上例重写如下,此时两个源文件都需要参与编译。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 program main use hello implicit none call sub() stop end program mainmodule hello implicit none contains subroutine sub() write (*,*) "hello ,world" return end subroutine end module hello
注意,编译会在目录中产生 hello.mod 文件,这是由 hello
这个模块产生的二进制文件。