Fortran/Fortran 过程和函数
在大多数程序中,一段代码块通常会在多个地方重复使用。为了最大程度地减少代码重复并便于维护代码,此类代码块应放置在函数或子程序中。Fortran 函数
类似于数学函数,它接收一个或多个参数作为输入,并返回单个输出值。Fortran 子程序
是一段代码块,它对输入变量执行一些操作,并且作为调用子程序的结果,输入变量被修改。
包含函数调用的表达式
! func1 is a function defined elsewhere.
! It takes an integer as an input and returns another integer as the output.
a = func1(b)
对子程序的调用
! sub1 is a subroutine defined elsewhere.
! sub1 performs some operation on input variables e and f.
call sub1(e, f)
! Now e or f, or both (or neither) may be modified.
许多编程语言不区分函数和子程序(例如 C/C++、Python、Java)。纯函数式编程语言(例如 Haskell)只允许函数,因为子程序在某些情况下可以修改输入变量作为副作用,这会使代码复杂化。
函数比子程序更简单。函数必须返回单个值,并且可以像write
语句一样从表达式中调用,在if声明if (function) then
中调用,等等。子程序不返回值,但可以通过其参数返回多个值,并且只能用作独立命令(使用关键字call
)。
在 Fortran 中,可以使用函数
来返回值或值数组。以下程序调用一个函数来计算一个整数的平方和立方的和。
function func(i) result(j)
integer, intent (in) :: i ! input
integer :: j ! output
j = i**2 + i**3
end function
program main
implicit none
integer :: i
integer :: func
i = 3
print *, "sum of the square and cube of", i, "is", func(i)
end program
参数i
的intent (in)
属性意味着i
不能在函数内部更改,相反,返回值j
具有自动的intent (out)
。请注意,func
的返回类型需要声明。如果省略了它,一些编译器将无法编译。Open64 将在警告的情况下编译生成的代码,但行为是不确定的。
另一种公式(与 F77 兼容)是
FUNCTION func_name(a, b)
INTEGER :: func_name
INTEGER :: a
REAL :: b
func_name = (2*a)+b
RETURN
END FUNCTION
PROGRAM cows
IMPLICIT NONE
INTEGER :: func_name
PRINT *, func_name(2, 1.3)
END PROGRAM
func_name
的返回类型仍然需要声明,如上所示。唯一的区别是func_name
内部如何引用func_name
的返回类型。在这种情况下,返回变量与函数本身具有相同的名称。
递归函数可以声明,例如下面显示的那样,以便代码可以编译。
recursive function fact(i) result(j)
integer, intent (in) :: i
integer :: j
if (i==1) then
j = 1
else
j = i * fact(i - 1)
end if
end function fact
子程序
可用于通过其参数返回多个值。它使用call
语句调用。这是一个例子。
subroutine square_cube(i, isquare, icube)
integer, intent (in) :: i ! input
integer, intent (out) :: isquare, icube ! output
isquare = i**2
icube = i**3
end subroutine
program main
implicit none
external square_cube ! external subroutine
integer :: isq, icub
call square_cube(4, isq, icub)
print *, "i,i^2,i^3=", 4, isq, icub
end program
在声明需要传入或传出的函数和子程序内的变量时,可以添加意图。默认情况下,没有意图检查 - 这可能会导致编译器无法检测到错误编码。
intent (in)
- 可以在过程中使用虚拟参数的值,但不能修改。
intent (out)
- 可以在过程中设置虚拟参数,然后修改它,并将值返回给调用方。
intent (inout)
- 可以在过程中使用和修改虚拟参数的初始值,然后将其返回给调用方。
函数可以以不同的形式定义其结果的数据类型:作为单独的变量或通过函数名称。
参见下面的示例
function f1(i) result (j)
!! result's variable: separately specified
!! result's data type: separately specified
integer, intent (in) :: i
integer :: j
j = i + 1
end function
integer function f2(i) result (j)
!! result's variable: separately specified
!! result's data type: by prefix
integer, intent (in) :: i
j = i + 2
end function
integer function f3(i)
!! result's variable: by function name
!! result's data type: by prefix
integer, intent(in) :: i
f3 = i + 3
end function
function f4(i)
!! result's variable: by function name
!! result's data type: separately specified
integer, intent (in) :: i
integer :: f4
f4 = i + 4
end function
program main
implicit none
integer :: f1, f2, f3, f4
print *, 'f1(0)', f1(0) ! output: 1
print *, 'f2(0)', f2(0) ! output: 2
print *, 'f3(0)', f3(0) ! output: 3
print *, 'f4(0)', f4(0) ! output: 4
end program
过程必须通过模块use
或通过将它们指定为external
过程来包含。external
只提供一个隐式接口,该接口不如显式接口好,因为编译器不知道参数的数量,也不知道它们的类型。因此,它在编译时不能产生警告(与从模块use
提供的显式接口形成对比,参见 Fortran/OOP in Fortran)。
subroutine square_cube(i, isquare, icube)
integer, intent (in) :: i ! input
integer, intent (out) :: isquare, icube ! output
isquare = i**2
icube = i**3
end subroutine
integer function pow4(i)
integer, intent (in) :: i
pow4 = i**4
end function
program main
implicit none
external square_cube ! external subroutine (only implicit interface)
integer :: pow4 ! external function (only implicit interface)
integer :: i, isq, icub
i = 5
call square_cube(i, isq, icub)
print '(A,4I5)', "i,i^2,i^3,i^4=", i, isq, icub, pow4(i)
end program
函数和子程序都可以修改其输入变量。子程序必须修改输入变量,因为它们不返回值。函数不必修改,但默认情况下允许修改输入变量。函数可以使用所有输入变量的intent
属性转换为纯函数,该属性没有任何副作用,并且通过关键字pure
进一步加强。pure
关键字施加了额外的限制,这些限制基本上阻止了函数具有任何副作用。
pure
函数的示例。
pure real function square(x)
real, intent (in) :: x
square = x*x
end function
program main
real :: a, b, square
a = 2.0
b = square(a)
! After invoking the square(.) pure function, we can be sure that
! besides assigning the output value of square(a) to b,
! nothing else has been changed.
end program
如果通过其虚拟名称指定输入参数,则可以使用任何顺序。只要调用过程具有目标过程的接口块(如果通过module
使用模块来包含函数,则会自动创建该块),就可以做到这一点。
还有一种混合方法,其中一些参数通过位置指定,其余参数通过其虚拟名称指定。
给出一个例子
real function adder(a,b,c,d)
real, intent (in) :: a, b, c, d
adder = a+b+c+d
end function
program main
interface
real function adder(a,b,c,d)
real, intent (in) :: a, b, c, d
end function
end interface
print *, adder(d=1.0, b=2.0, c=1.0, a=1.0) ! specify each parameter by dummy name
print *, adder(1.0, d=1.0, b=2.0, c=1.0) ! specify some parameters by dummy names, other by position
end program
参数可以设置为optional
。可以使用内在函数present
检查是否设置了特定参数。
下面给出一个例子。
real function tester(a)
real, intent (in), optional :: a
if (present(a)) then
tester = a
else
tester = 0.0
end if
end function
program main
interface
real function tester(a)
real, intent (in), optional :: a
end function
end interface
print *, "[no args] tester() :", tester() ! yields: 0.0
print *, "[ args] tester(1.0):", tester(1.0) ! yields: 1.0
end program
如果一个过程有另一个过程作为哑参数,则需要指定它的类型,就像其他参数的类型一样。interface
块用于这种情况。它由包含其参数定义的过程语句组成。
注意,每个接口块都有自己的作用域。因此,如果需要访问外部值,则需要显式加载它们。这可以通过import
或 use
语句实现。
下面给出一个例子。
function tester(a)
real, intent (in) :: a
real :: tester
tester = 2*a + 3
end function tester
program main
interface
function tester(a)
real, intent (in) :: a
real :: tester
end function tester
end interface
print *, "tester(1.0):", tester(1.0) ! yields: 5.0
end program main
通过显式给出save
属性,可以在过程调用之间保存变量的值。
下面给出一个例子。
subroutine f()
implicit none
integer, save :: i = 0
i = i + 1
print *, "value i:", i
end
program main
implicit none
interface
subroutine f()
integer, save :: i = 0
end
end interface
call f() ! yields: 1
call f() ! yields: 2
call f() ! yields: 3
end program main
可以为不同的输入参数创建具有相同名称的泛型函数,类似于abs
函数,它适用于整数、实数和复数数据类型。
以下示例说明如何创建添加两个整数或字符字符串的函数add
。
module add_mod
implicit none
private
public :: add
interface add
procedure add_int, add_char
end interface add
contains
pure function add_int( x, y )
integer, intent (in) :: x, y
integer :: add_int
add_int = x+y
end function add_int
pure function add_char( x, y )
character (len=*), intent (in) :: x, y
character (len=len(x)+len(y)), allocatable :: add_char
add_char = x // y
end function add_char
end module add_mod
program main
use add_mod
implicit none
print *, "add ints: ", add( 1, 2 )
print *, "add chars: ", add("abc", "def")
end program main
可以将抽象类型的类型绑定过程设置为deferred
,以便需要在派生类型中重新实现它。有关更多信息,请参阅有关抽象类型的部分。
可以创建操作任意维度的参数的过程。elemental
关键字用于定义对单个对象(例如整数)的操作,并且自动处理一般情况。
给出了任意长整数维度的加法示例。
pure elemental function add_int(x, y)
integer, intent (in) :: x, y
integer :: add_int
add_int = x + y
end function add_int
program main
implicit none
interface
pure elemental function add_int(x, y)
integer, intent (in) :: x, y
integer :: add_int
end function add_int
end interface
print *, "add ints:", add_int(1, 2) ! yields: 3
print *, "add arrays:", add_int([1, 2], [2, 3]) ! yields: 3 5
end program main