跳轉到內容

Fortran/Fortran 過程和函式

來自 Wikibooks,開放世界中的開放書籍

函式和子程式

[編輯 | 編輯原始碼]

在大多數程式中,一段程式碼塊通常會在多個地方重複使用。為了最大程度地減少程式碼重複並便於維護程式碼,此類程式碼塊應放置在函式或子程式中。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

引數iintent (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 塊用於這種情況。它由包含其引數定義的過程語句組成。

注意,每個介面塊都有自己的作用域。因此,如果需要訪問外部值,則需要顯式載入它們。這可以透過importuse 語句實現。

下面給出一個例子。

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
華夏公益教科書