Chapter 8 函数
子程序 SUBROUTINE
把经常需要用到的程序封装成为一个子程序,通过 call
进行调用 call sub1(para1, para2)
subroutine sub1()
......
return
end subroutine sub1 ! 这里的sub1作为子函数名也可以省去
其中最后一行一般是 return
指返回到先前调用子程序的位置,不写的话算作默认添加,当然return
也可以放在任何地方。其次,关于子程序的变量和行代码是与主程序相互独立的,利用行代码进行跳转的时候主程序只会跳转到主程序里相应行代码的位置,子程序亦然。
子程序传参用的是传址调用,传递出去的参数和接受的参数用的是同一个地址,在子程序中修改变量也会修改主程序的值
subroutine add(first,second)
implicit none
integer :: first, second
! 用来接受参数的变量还是要声明类型,变量的初值就等于传递进来的数值
自定义函数FUNCTION
自定义函数的用法与子程序基本一致,但有几点不一样:
- 调用自定义函数前要先声明
- 自定义函数执行后仅返回一个数值
- 不再需要用
call
来调用,可以直接调用并赋值
参考一段示例代码:
program ex0807
implicit none
real :: a=1
real :: b=2
real, external :: add
write(*,*) add(a,b)
stop
end
function add(a,b)
implicit none
real :: a,b ! 传入的参数
real :: add
! add跟函数名称一样,这边不是用来声明变量,
! 是声明这个函数会返回的数值类型
add = a+b
return
end
特别注意在主程序中需要用external
声明,然后在函数里面real :: add
表示返回值是real类型
全局变量 COMMON
全局变量只是定义了一块共享内存空间,取全局变量的时候是根据声明时候相对位置关系来做对应,而非用变量名称
program main
integer a,b,c,d,e,f
common a,b,c,d,e,f
......
end program main
subroutine sub()
integer n1,n2,n3,n4,n5,n6
common n1,n2,n3,n4,n5,n6
......
end subroutine sub
假如子程序仅用到第六个共享变量也必须要全部声明出来才行(STUPID!),为此给出一种对变量归类的方法简化:
integer :: a,b
common /group1/ a ! 将a放在group1这个区间
common /group2/ b ! 将b放在group2这个区间
! 调用时候
integer num1, num2
common /group1/ num1
common /group2/ num2
还有一种设置共享变量的方法就是用 BLOCK DATA
program ex0812
implicit none
integer :: a,b
common a,b ! a,b放在不具名的全局变量空间中
integer :: c,d
common /group1/ c,d ! c,d放在group1的全局变量空间中
integer :: e,f
common /group2/ e,f ! e,f放在group2的全局变量空间中
write(*,"(6I4)") a,b,c,d,e,f
stop
end
block data
implicit none
integer a,b
common a,b ! a,b放在不具名的全局变量空间中
data a,b /1,2/ ! 设定a,b的初值
integer c,d
common /group1/ c,d ! c,d放在group1的全局变量空间中
data c,d /3,4/ ! 设定c,d的初值
integer e,f
common /group2/ e,f ! e,f放在group2的全局变量空间中
data e,f /5,6/ ! 设定e,f的初值
end block data
BLOCK DATA
这一段程序很像子程序,可以独立运行,甚至在编译过程中是先于主程序运行的,不需要别人的调用即可执行,不过他的功能只在于设置共享变量的初值,不能有执行命令。还有就是,全局变量不能声明为常量,即不能有PARAMETER
出现。
最后还要强调的是,声明全局变量的时候不要改变他的数据类型,否则很容易出错。
函数中的变量
传递参数给函数时候最重要的是类型一定要正确,因为Fortran是传递变量的内存地址,不同数据类型在解读内存内容方法上会不一样。函数所接收的参数都是已经配置好内存空间的。
! 比如这里有一个数组 a
call Show(a) ! 输入a第1个元素内存地址
call Show(a(2)) ! 输入a第2个元素内存地址
在实际应用过程中,传递数组参数时候比较灵活,重要的还是程序员自己判断是否超过数组范围,传递后的数组可以灵活的更改数组维度、坐标范围。多维数组在传递的时候,只有最后一维数组可以不赋值,其他维度都必须赋值。这是因为在取用数组元素的时候,会根据坐标计算元素内存的所在位置,在计算过程中一般是不涉及最后一维的。比如假设数组声明为A(L,M,N),对于元素A(x,y,z)所在的位置就是1+(x-1)+(y-1)L+(z-1)LM。
一般传递的参数变量生存周期是随着子程序或者函数的结束而结束,但是如果需要继续保存数据的话可以用命令 SAVE
,比如下面这个计算调用多少次子程序的操作:
subroutine sub()
implicit none
integer :: count = 1
save count
count = count + 1
return
end subroutine sub
特别注意的是,在这里变量count
的初始化只进行一次(因为已经被save下来了),再次进入该子程序的时候并不会再进行初始化!!!
如果需要将函数作为参数传递的话,需要特别用external, intrinsic
声明一下:
......
real, external :: func ! 声明func是自定义函数
real, intrinsic :: sin ! 声明sin是库函数
call ExecFunc(func)
call ExecFunc(sin)
......
subroutine ExecFunc(f)
real, external :: f ! 声明参数f是个函数
return
end subroutine ExecFunc
特殊参数的使用方法
由于在传递参数的时候是传递地址,参数intent
可以确保该参数是不可以改变的:
integer, intent(in) :: a ! 指定a不可修改 只读参数
integer, intent(out) :: b ! 指定b可重新设置数值
INTREFACE
是一段程序模块,用于说清楚要调用的函数类型及返回值类型,在以下情况写这样的使用接口是必要的
- 函数返回值为数组
- 指定参数位置来传递参数
- 所调用的函数参数数目不固定
- 输入指标参数
- 函数返回值为指针
举个函数接口的例子:
Interface
function func
implicit none
real ...... ! 仅能说明参数或传回值类型
integer ......
end function func
subroutine sub
implicite none
integer ......
end subroutine sub
end interface
如果某个参数是不确定是否一定会传递的,即需要用到不定个数的参数传递,这时候可以声明时调用参数optional
说明该参数是可选项,另外可调用参数 present
确定该参数是否传递过来,返回bool类型。如果多个参数传递,需要确保每一个参数仅传给对应的变量,可以作特别指定:
integer, optional :: a,b,c,d,e,f
call sub(b=3,f=5) ! 仅给参数b f传递值
特殊的函数类型
在Fortran函数,需要递归使用的函数必须加上关键词recursive
recursive integer function fact(n) result(ans)
最后的result(ans)
是用来在程序代码中用另一个名字设置函数传回值,这里用ans来代替原来的fact,使用:ans = n*fact(n-1)
内部函数可以作为某个函数的归属,仅在这个函数内调用
program main
......
contains ! contains后声明的函数仅在里面使用
subroutine localsub
......
end subroutine localsub
function localfunc
......
end function localfunc
end program
MODULE
MODULE
一般用于把有相关功能的函数及变量封装在一起,比如可以把全局变量封装在一起,然后调用这个module即可:
moudle global
implicit none
integer a,b
common a,b
end module
program main
use global ! a b变量已经声明过不用再声明
a=1
b=2
......
end program
一般声明为module后的变量如果不是去全局变量的话只是在使用函数内的局部变量,如果需要传递参数则需要加上关键词SAVE
这样效果很全局变量差不多
moudle global
implicit none
integer,save :: a,b
end module
module
还能用于存放自定义类型的数据以及函数
module module_name
......
contains
subroutine sub
.......
end subroutine sub
function func
......
end function func
end module
一般在编写大型程序的时候都需要对程序做模块化处理,把特定功能的函数放在一个模块里,一个模块里有个好处就是变量是公用的,调用起来比较方便,且一个模块内的函数相互调用也不用经过声明。
module constant
implicit none
real, parameter :: PI = 3.14159
real, parameter :: G = 9.81
end module
module typedef
implicit none
type player
real :: angle
real :: speed
real :: distance
end type
end module
module shoot
use constant
use typedef
implicit none
contains
! 由角度、切线速度来计算投射距离
subroutine Get_Distance( person )
implicit none
type(player) :: person
real rad, Vx, time
rad = Angle_TO_Rad( person%angle ) ! 单位转换
Vx = person%speed * cos(rad) ! 水平方向速度
time = 2.0 * person%speed * sin(rad) / G ! 在空中飞行时间
person%distance = Vx * time ! 距离 = 水平方向速度 * 飞行时间
return
end subroutine
! 把0~360的角度转换成0~2PI的弧度
real function Angle_TO_Rad( angle )
implicit none
real angle
Angle_TO_Rad = angle*pi/180.0
return
end function
end module
program ex0837
use shoot
implicit none
integer, parameter :: players = 5
type(player) :: people(players) = (/ player(30.0, 25.0, 0.0),&
player(45.0, 20.0, 0.0),&
player(35.0, 21.0, 0.0),&
player(50.0, 27.0, 0.0),&
player(40.0, 22.0, 0.0) &
/)
integer :: I
do I=1, players
call Get_Distance( people(I) )
write(*,"('Player ',I1,' =',F8.2)") I, people(I)%distance
end do
stop
end
其他的功能
entry
能创建一个新的函数入口,调用入口时候会跳过进入点之前的程序。
program ex0838
implicit none
call sub()
call mid()
stop
end
subroutine sub()
implicit none
write(*,*) "Hello."
entry mid()
write(*,*) "Good morning."
return
end
运行结果是:
Hello.
Good morning.
Good morning.
第一次调用函数sub正常运行,出现两行输出;第二次调用mid则仅有后面那一行输出。
如果需要调用其他文件里面的内容(变量或者函数)可以用关键词 include
来引入新的文件,使用的位置比较随意,在编译的时候会在此处展开。
include 'ex0840.f90'
但其实这种方法并不很推荐,因为这样的速度还是很慢,还是相当于加载所有的文件