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