Skip to content

函数

目录

子程序

子程序 (SUBROUTINE):写程序时,可以把某一段常被使用、具有特定功能的程序代码封装成子程序。后续只需要通过该子程序的 CALL 命令,就能使用这段代码的功能,如:

Fortran
program array
  implicit none
  ! 通过 call 调用相应子程序
  call message("first")
  call message("second")
end program array

! 声明子程序时,同时包括参数的声明,尽管之后还需要进一步声明参数类型
subroutine message(hint)
  implicit none
  ! intent  关键字指定参数的输入输出类型
  ! * in    输入
  ! * out   输出
  ! * inout 输入输出(默认,不指明 intent 时)
  character(len=*), intent(in) :: hint
  ! ! 设置为字符串参数并具有输入类型时,应该用 character(len=*) 声明
  ! ! 反之会以固定长度读取缓存区的内容并输出,可能会出现错误
  write(*,*) "get message: ", hint
end subroutine message

程序输出为:

txt
get message: first
get message: second

子程序类似于 C/C++ 中的 void 函数,无返回值

  • intent(in):表示参数是输入参数。这意味着在子程序中,参数只能被读取,不能被修改。它通常用于传递数据给子程序进行计算,但不需要在子程序中修改参数的值。
  • intent(out):表示参数是输出参数。这意味着在子程序中,参数的初始值可能不重要,但在子程序执行完毕后,参数的值将被修改并传递给调用程序。在子程序中,可以对输出参数进行赋值或修改。
  • intent(inout):表示参数既是输入参数又是输出参数。这意味着在子程序中,参数的初始值对计算结果有影响,并且在子程序执行完毕后,参数的值将被修改并传递给调用程序。在子程序中,可以读取和修改输入/输出参数的值。

subroutine 错误样例

以下错误无法通过编译

Fortran
program array
  implicit none
  call message("first")
  call message("second")
end program array

subroutine message(hint)
  implicit none
  character(len=*), intent(in) :: hint
  write(6,*) "get message: ", hint
  hint = "finish"
  ! ^ 此处编译出错
  ! ^ 无法修改 intent(in) 参数的值
end subroutine message

以下错误均能通过编译但运行失败

Fortran
! 运行错误原因:数组越界访问
program array
  implicit none
  call message("first")
  call message("second")
end program array

subroutine message(hint)
  implicit none
  character(len=*), intent(inout) :: hint
  write(6,*) "get message: ", hint
  hint = "end"
end subroutine message

subroutine 小结

  • 参数传递通过传递地址实现,不传值
  • 子程序参数中的字符串应用 character(len=*) 声明
  • 应指明子程序参数中数组的维度和各维的起始和终止编号
  • 可以将数组的部分内容传递给子程序,此时编译器会分配存储部分数组内容的临时空间
  • 可以用 intent(in)、intent(out)、intent(inout) 来指定参数的输入输出属性,未指定时,按照 inout 方式对待
  • 直接调用子程序时,编译器并不检查调用者和子程序之间参数的一致性,可能出现数组大小不一致、数组维度信息不一致、变量类型不一致、参数个数与顺序不一致等会导致运行过程或结果有误的情况

自定义函数

自定义函数 (FUNCTION):与子程序类似(包括传参的功能特点),但是函数有返回值,而子程序没有返回值,如:

Fortran
program array
  implicit none
  integer, external :: sum
  integer :: i
  i = sum(1, 2)
  write(*,*) i
end program array

integer function sum(a, b)
  implicit none
  integer, intent(in) :: a, b
  ! 返回值
  sum = a + b
end function sum

函数类似于 C/C++ 中的有返回值的函数

当调用者中声明的数据类型与自定义函数返回值类型不同时,编译器不报错,但可能出现奇怪的错误运行结果:

Fortran
program array
  implicit none
  integer, external :: sum ! 此处声明的是整型
  integer :: i
  real :: r
  i = sum(1, 2)
  r = sum(1,2)
  write(*,*) i
end program array

real function sum(a, b) ! 此处定义的为浮点数
  implicit none
  real, intent(in) :: a, b
  sum = a + b
end function sum

上述函数在运行过程中,会因为编译器的种类和版本乃至优化参数的不同,出现不同的运行结果。因此,在调用函数时,应该保证调用者中声明的数据类型与自定义函数返回值类型一致

传递函数

在传递参数时,除了传递数字、字符等基本类型的变量外,还可以把一个函数名当做参数传递出去,如:

Fortran
program array
  integer :: i
  real, external :: plus2
  real, external :: sin
  read(*,*) i
  if (i == 1) then
    call message(plus2)
  else
    call message(sin)
  end if
end program array

subroutine message(func)
  implicit none
  real, external :: func(1.0)
  write(*,*) func(1.0)
end subroutine message

real function plus2(a)
  implicit none
  real, intent(in) :: a
  plus2 = a + 2.0
end function plus2

函数的接口使用

函数接口 (INTERFACE):在 Fortran 中,函数的接口是指函数的声明部分,即函数名、参数个数、参数类型、参数顺序等信息。函数的接口信息是在调用函数时,编译器用来检查调用者和被调用者之间参数的一致性的重要依据。下列情况中需要使用 INTERFACE:

  • 函数返回值为数组
  • 指定参数位置来传递参数时
  • 函数有可选参数时
  • 函数返回值为指针时

函数接口相当于函数重载(但不完全等同),其实现起来相当费力,本质是相同逻辑的重复与内存换运行的策略,具体逻辑为 调用函数接口 -> 函数接口索引到指定函数 -> 调用函数

fortran
subroutine add_int(a, b)
  implicit none
  integer, intent(in) :: a, b
  write(*,*) a + b
end subroutine add_int

subroutine add_real(a, b)
  implicit none
  real, intent(in) :: a, b
  write(*,*) a + b
end subroutine add_real

program main
  interface add
    subroutine add_int(a, b)
      implicit none
      integer, intent(in) :: a, b
    end subroutine add_int

    subroutine add_real(a, b)
      implicit none
      real, intent(in) :: a, b
    end subroutine add_real
  end interface add

  call add(1, 2)
  ! ^ 调用 add_int

  call add(1.0, 2.0)
  ! ^ 调用 add_real
end program main

可选参数

INTERFACE 中的子程序或自定义函数可以具有可选参数(用 optional 声明),在调用 INTERFACE 时,可以不提供可选参数的值,如:

Fortran
program main
  interface server
    subroutine send_value(hint, r, i)
      implicit none
      character(len=*), intent(in) :: hint
      real, intent(in), optional :: r
      integer, intent(in), optional :: i
    end subroutine send_value
  end interface server
  call server("first", r = 0.1)
  call server("second", i = 1)
  call server("third", r , v)
end program main

subroutine send_value(hint, r, i)
  implicit none
  character(len=*) hint
  real, optional :: r
  integer, optional :: i
  ! use present to check if the optional parameter is provided
  if (present(r)) write(6,*) hint, r
  if (present(i)) write(6,*) hint, i
end subroutine send_value

在子程序或自定义函数中使用可选参数时,应先用 present 确认是否提供了可选参数的值

特殊的函数类型

递归函数

递归函数即在运行时需要调用自身,调用自身时,需要使用 recursive 关键字,下面给出一个计算阶乘的递归函数,subroutine 同理

Fortran
program main
  implicit none
  integer :: i
  i = factorial(5)
  write(*,*) i
end program main

recursive function factorial(n) result(r)
  implicit none
  integer, intent(in) :: n
  integer :: r
  if (n == 0) then
    r = 1
  else
    r = n * factorial(n - 1)
  end if
end function factorial

内部函数

内部函数 (INTERNAL FUNCTION):在 Fortran 中,可以在一个(子)程序或自定义函数内声明一个函数/子程序,这个函数/子程序只能在该(子)程序或自定义函数内使用,使用 contain 关键字,下面给出以主程序为例的内部函数实例,子程序和函数同理

Fortran
program main
  implicit none
  integer :: i
  i = factorial(5)
  write(*,*) i

contains
  recursive function factorial(n) result(r)
    implicit none
    integer, intent(in) :: n
    integer :: r
    if (n == 0) then
      r = 1
    else
      r = n * factorial(n - 1)
    end if
  end function factorial
end program main

program other
  implicit none
  integer :: i
  i = factorial(5)
  ! ^ 此处编译出错,无法调用到 factorial
  write(*,*) i
end program other

PURE 函数

PURE 函数:函数的返回值仅与输入参数有关,不与程序中其他变量有关,使用 pure 关键字,下面给出一个计算阶乘的 PURE 函数,subroutine 同理

Fortran
program main
  real :: x = 2.0, y = 3.0 , sum
  sum = add(x, y)
  print *, "The sum of", x, "and", y, "is", sum
end program main

pure function add(a, b) result(result)
  real, intent(in) :: a, b
  real :: result
  result = a + b
end function add

ELEMENTAL 函数

ELEMENTAL 函数:函数的输入参数和返回值都是数组,且数组的每个元素都是独立计算的,使用 elemental 关键字,需要注意的是,声明函数参数时,应以元素出发,而不是数组,尽管在调用前后都是以数组的形式返回,下面给出一个计算阶乘的 ELEMENTAL 函数

Fortran
program main
  implicit none
  integer, dimension(5) :: a
  interface
  ! * 此处使用 interface 来描述函数接口
    elemental function plus_one(n) result(r)
      implicit none
      integer, intent(in) :: n
      integer :: r
    end function plus_one
  end interface
  a = plus_one([1, 2, 3, 4, 5])
  write(*,*) a
end program main

elemental function plus_one(n) result(r)
  implicit none
  integer, intent(in) :: n
  integer :: r
  r = n + 1
end function plus_one

MODULE

// todo

模块 (MODULE):在 Fortran 中,模块是一种特殊的程序单元,它可以包含变量、子程序、自定义函数等。模块的主要作用是将程序中的变量、子程序、自定义函数等按照功能进行分类和封装,便于程序的编写和维护。模块的使用方法如下:

Fortran
module myModule
  implicit none
  real :: r = 1.0
  integer :: i = 1
end module myModule

program main
  use myModule
  implicit none
  call message()
end program main

subroutine message()
  use myModule
  implicit none
  write(*,*) r, i
end subroutine message

module 中声明的变量默认为全局变量

module 小结

  • 可以在一个 module 中声明该 module 内不同子程序、自定义函数或函数共享使用的公共变量,公共变量可以声明为仅在 module 内访问(声明为 private),或可被 module 外开放访问(默认情况或声明为 public);类似的,一个 module 的子程序、自定义函数或函数也可以声明为 private
  • 可使用一个module所有开放访问的变量和子程序、自定义函数或函数,也可以指定所使用的具体变量和子程序、自定义函数或函数
  • 在使用 module 内的子程序、自定义函数或函数时,会对参数列表进行检查
  • 可以通过重命名所使用 module 的变量名、子程序、自定义函数或函数,以使用不同 module 的同名资源

基于 CC BY-NC-SA 4.0 许可发布