《Fortran 95 程序设计》阅读笔记三

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

	! 把0360的角度转换成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'
但其实这种方法并不很推荐,因为这样的速度还是很慢,还是相当于加载所有的文件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值