博客 / 詳情

返回

Y 分鐘速成 Fortran

源代碼下載: learnfortran-cn.f95

Fortran 是最古老的計算機語言之一。它由IBM開發於1950年用於數值運算(Fortran 為 “Formula Translation” 的縮寫)。雖然該語言已年代久遠,但目前仍用於高性能計算,如天氣預報。 該語言仍在持續發展,並且基本保持向下兼容。知名的版本為 Fortran 77, Fortran 90, Fortran 95, Fortran 2003, Fortran 2008 與 Fortran 2015。

這篇概要將討論 Fortran 95 的一些特徵。因為它是目前所廣泛採用的標準版本,並且與最新版本的內容 也基本相同(而 Fortran 77 則是一個非常不同的版本)。

! 這是一行註釋

program example   !聲明一個叫做 example 的程序

    ! 代碼只能放在程序、函數、子程序或者模塊內部
    ! 推薦使用縮進,但不是必須的。

    ! 聲明變量
    ! ===================

    ! 所有的聲明必須放在語句與表達式之前

    implicit none    !阻止變量的隱式聲明 (推薦!)
    ! Implicit none 必須在每一個 函數/程序/模塊 中進行聲明

    ! 重要  - Fortran 對大小寫不敏感
    real z
    REAL Z2

    real :: v,x    ! 警告: 默認值取決於編譯器!    
    real :: a = 3, b=2E12, c = 0.01
    integer :: i, j, k=1, m
    real, parameter :: PI = 3.1415926535897931    !聲明一個常量
    logical :: y = .TRUE. , n = .FALSE.    !布爾值
    complex :: w = (0,1)    !sqrt(-1) (譯註: 定義複數,此為-1的平方根)
    character (len=3) :: month    !長度為3的字符串

    real :: array(6)     !聲明長度為6的浮點數數組
    real, dimension(4) :: arrayb    !聲明數組的另一種方法
    integer :: arrayc(-10:10)   !有着自定義索引的數組
    real :: array2d(3,2)    !多維數組

    ! 分隔符 '::' 並不總是必要的,但推薦使用

    ! 還存在很多其他的變量特徵:
    real, pointer :: p    !聲明一個指針

    integer, parameter :: LP = selected_real_kind(20)
    real (kind = LP) :: d    !長精度變量

    ! 警告:在聲明期間初始化變量將導致在函數內發生問題,因為這將自動具備了 “save” 屬性,
    ! 因此變量的值在函數的多次調用期間將被存儲。一般來説,除了常量,應分開聲明與初始化!

    ! 字符串
    ! =======

    character :: a_char = 'i'
    character (len = 6) :: a_str = "qwerty"
    character (len = 30) :: str_b
    character (len = *), parameter :: a_long_str = "This is a long string."
    !可以通過使用 (len=*) 來自動判斷長度,但只對常量有效

    str_b = a_str // " keyboard"    !通過 // 操作符來連接字符串


    ! 任務與計算
    ! =======================

    Z = 1    !向之前聲明的變量 z 賦值 (大小寫不敏感).
    j = 10 + 2 - 3
    a = 11.54  /  (2.3 * 3.1)
    b = 2**3    !冪


    ! 控制流程語句 與 操作符
    ! ===================================

    !單行 if 語句
    if (z == a) b = 4  !判別句永遠需要放在圓括號內

    if (z /= a) then !z 不等於 a
    ! 其他的比較運算符: < > <= >= == /=
      b = 4
    else if (z .GT. a) then !z 大於(Greater) a
    ! 文本形式的比較運算符: .LT. .GT. .LE. .GE. .EQ. .NE.  
      b = 6
    else if (z < a) then !'then' 必須放在該行
      b = 5 !執行部分必須放在新的一行裏
    else
      b = 10
    end if !結束語句需要 'if' (也可以用 'endif').


    if (.NOT. (x < c .AND. v >= a .OR. z == z)) then   !布爾操作符
      inner: if (.TRUE.) then    !可以為 if 結構命名
        b = 1
      endif inner    !接下來必須命名 endif 語句.
    endif


    i = 20
    select case (i)
      case (0)    !當 i == 0
        j=0
      case (1:10)    !當 i 為 1 到 10 之內 ( 1 <= i <= 10 )
        j=1
      case (11:)    !當 i>=11
        j=2
      case default
        j=3
    end select


    month = 'jan'
    ! 狀態值可以為整數、布爾值或者字符類型
    ! Select 結構同樣可以被命名
    monthly: select case (month)
      case ("jan")
         j = 0
      case default
         j = -1
    end select monthly

    do i=2,10,2    !從2到10(包含2和10)以2為步進值循環
      innerloop: do j=1,3    !循環同樣可以被命名
        exit    !跳出循環
      end do innerloop
    cycle    !重複跳入下一次循環
    enddo


    ! Goto 語句是存在的,但強烈不建議使用
    goto 10    
    stop 1    !立即停止程序 (返回一個設定的狀態碼).
10  j = 201    !這一行被標註為 10 行 (line 10)


    ! 數組
    ! ======
    array = (/1,2,3,4,5,6/)
    array = [1,2,3,4,5,6]    !當使用 Fortran 2003 版本.
    arrayb = [10.2,3e3,0.41,4e-5]
    array2d =  reshape([1.0,2.0,3.0,4.0,5.0,6.0], [3,2])

    ! Fortran 數組索引起始於 1
    ! (默認下如此,也可以為數組定義不同的索引起始)
    v = array(1)    !獲取數組的第一個元素
    v = array2d(2,2)

    print *, array(3:5)    !打印從第3到第五5之內的所有元素
    print *, array2d(1,:)    !打印2維數組的第一列

    array = array*3 + 2    !可為數組設置數學表達式
    array = array*array    !數組操作支持元素級(操作) (element-wise)
    !array = array*array2d    !這兩類數組並不是同一個維度的

    ! 有很多內置的數組操作函數
    c = dot_product(array,array)    !點乘 (點積)
    ! 用 matmul() 來進行矩陣運算.
    c = sum(array)
    c = maxval(array)
    print *, minloc(array)
    c = size(array)
    print *, shape(array)
    m = count(array > 0)

    ! 遍歷一個數組 (一般使用 Product() 函數).
    v = 1
    do i = 1, size(array)
        v = v*array(i)
    end do

    ! 有條件地執行元素級操作
    array = [1,2,3,4,5,6]
    where (array > 3)
        array = array + 1
    elsewhere (array == 2)
        array = 1
    elsewhere
        array = 0
    end where

    ! 隱式DO循環可以很方便地創建數組
    array = [ (i, i = 1,6) ]    !創建數組 [1,2,3,4,5,6]
    array = [ (i, i = 1,12,2) ]    !創建數組 [1,3,5,7,9,11]
    array = [ (i**2, i = 1,6) ]    !創建數組  [1,4,9,16,25,36]
    array = [ (4,5, i = 1,3) ]    !創建數組 [4,5,4,5,4,5]


    ! 輸入/輸出
    ! ============

    print *, b    !向命令行打印變量 'b'

    ! 我們可以格式化輸出
    print "(I6)", 320    !打印 '   320'
    print "(I6.4)", 3    !打印 '  0003'
    print "(F6.3)", 4.32    !打印 ' 4.320'


    ! 該字母與數值規定了給定的數值與字符所用於打印輸出的類型與格式
    ! 字母可為 I (整數), F (浮點數), E (工程格式),
    ! L (邏輯/布爾值), A (字符) ...
    print "(I3)", 3200    !如果數值無法符合格式將打印 '***'

    ! 可以同時設定多種格式
    print "(I5,F6.2,E6.2)", 120, 43.41, 43.41
    print "(3I5)", 10, 20, 30    !連續打印3個整數 (字段寬度 = 5).
    print "(2(I5,F6.2))", 120, 43.42, 340, 65.3   !連續分組格式

    ! 我們也可以從終端讀取輸入
    read *, v
    read "(2F6.2)", v, x    !讀取2個數值

    ! 讀取文件
    open(unit=11, file="records.txt", status="old")
    ! 文件被引用帶有一個單位數 'unit', 為一個取值範圍在9-99的整數
    ! 'status' 可以為 {'old','replace','new'} 其中之一
    read(unit=11, fmt="(3F10.2)") a, b, c
    close(11)

    ! 寫入一個文件
    open(unit=12, file="records.txt", status="replace")
    write(12, "(F10.2,F10.2,F10.2)") c, b, a
    close(12)
    ! 在討論範圍之外的還有更多的細節與可用功能,並於老版本的 Fortran 保持兼容


    ! 內置函數
    ! ==================

    ! Fortran 擁有大約 200 個內置函數/子程序
    ! 例子
    call cpu_time(v)    !以秒為單位設置時間
    k = ior(i,j)    !2個整數的位或運算
    v = log10(x)    !以10為底的log運算
    i = floor(b)    !返回一個最接近的整數小於或等於x (地板數)
    v = aimag(w)    !複數的虛數部分


    ! 函數與子程序
    ! =======================

    ! 一個子程序會根據輸入值運行一些代碼並會導致副作用 (side-effects) 或修改輸入值
    ! (譯者注: 副作用是指對子程序/函數外的環境產生影響,如修改變量)

    call routine(a,c,v)    !調用子程序

    ! 一個函數會根據輸入的一系列數值來返回一個單獨的值
    ! 但輸入值仍然可能被修改以及產生副作用

    m = func(3,2,k)  !調用函數

    ! 函數可以在表達式內被調用
    Print *, func2(3,2,k)

    ! 一個純函數不會去修改輸入值或產生副作用
    m = func3(3,2,k)


contains ! 用於定義程序內部的副程序(sub-programs)的區域

    ! Fortran 擁有一些不同的方法去定義函數

    integer function func(a,b,c)    !一個返回一個整數的函數
        implicit none   !最好也在函數內將含蓄模式關閉 (implicit none)
        integer :: a,b,c !輸入值類型定義在函數內部
        if (a >= 2) then
            func = a + b + c !返回值默認為函數名
            return !可以在函數內任意時間返回當前值
        endif
        func = a + c
        ! 在函數的結尾不需要返回語句
    end function func


    function func2(a,b,c) result(f)    !將返回值聲明為 'f'
        implicit none
        integer, intent(in) :: a,b    !可以聲明讓變量無法被函數修改
        integer, intent(inout) :: c
        integer :: f     !函數的返回值類型在函數內聲明
        integer :: cnt = 0    !注意 - 隱式的初始化變量將在函數的多次調用間被存儲
        f = a + b - c
        c = 4    !變動一個輸入變量的值
        cnt  = cnt + 1    !記錄函數的被調用次數
    end function func2


    pure function func3(a,b,c)  !一個沒有副作用的純函數
        implicit none
        integer, intent(in) :: a,b,c
        integer :: func3
        func3 = a*b*c
    end function func3


    subroutine routine(d,e,f)
        implicit none
        real, intent(inout) :: f
        real, intent(in) :: d,e
        f = 2*d + 3*e + f
    end subroutine routine


end program example   ! 函數定義完畢 -----------------------

! 函數與子程序的外部聲明對於生成程序清單來説,需要一個接口聲明(即使它們在同一個源文件內)(見下)
! 使用 'contains' 可以很容易地在模塊或程序內定義它們

elemental real function func4(a) result(res)
! 一個元函數(elemental function) 為一個純函數使用一個標量輸入值
! 但同時也可以用在一個數組並對其中的元素分別處理,之後返回一個新的數組
    real, intent(in) :: a
    res = a**2 + 1.0
end function func4


! 模塊
! =======

! 模塊十分適合於存放與複用相關聯的一組聲明、函數與子程序

module fruit
    real :: apple
    real :: pear
    real :: orange
end module fruit


module fruity

    ! 聲明必須按照順序: 模塊、接口、變量
    ! (同樣可在程序內聲明模塊和接口)

    use fruit, only: apple, pear    ! 使用來自於 fruit 模塊的 apple 和 pear
    implicit none    !在模塊導入後聲明

    private    !使得模塊內容為私有(private)(默認為公共 public)
    ! 顯式聲明一些變量/函數為公共
    public :: apple,mycar,create_mycar
    ! 聲明一些變量/函數為私有(在當前情況下沒必要)(譯註: 因為前面聲明瞭模塊全局 private)
    private :: func4

    ! 接口
    ! ==========
    ! 在模塊內顯式聲明一個外部函數/程序
    ! 一般最好將函數/程序放進 'contains' 部分內
    interface
        elemental real function func4(a) result(res)
            real, intent(in) :: a
        end function func4
    end interface

    ! 重載函數可以通過已命名的接口來定義
    interface myabs
        ! 可以通過使用 'module procedure' 關鍵詞來包含一個已在模塊內定義的函數
        module procedure real_abs, complex_abs
    end interface

    ! 派生數據類型
    ! ==================
    ! 可創建自定義數據結構
    type car
        character (len=100) :: model
        real :: weight    !(公斤 kg)
        real :: dimensions(3)    !例: 長寬高(米)
        character :: colour
    end type car

    type(car) :: mycar    !聲明一個自定義類型的變量
    ! 用法具體查看 create_mycar()

    ! 注: 模塊內沒有可執行的語句

contains

    subroutine create_mycar(mycar)
        ! 展示派生數據類型的使用
        implicit none
        type(car),intent(out) :: mycar

        ! 通過 '%' 操作符來訪問(派生數據)類型的元素
        mycar%model = "Ford Prefect"
        mycar%colour = 'r'
        mycar%weight = 1400
        mycar%dimensions(1) = 5.0    !索引默認起始值為 1 !
        mycar%dimensions(2) = 3.0
        mycar%dimensions(3) = 1.5

    end subroutine

    real function real_abs(x)
        real :: x
        if (x<0) then
            real_abs = -x
        else
            real_abs = x
        end if
    end function real_abs

    real function complex_abs(z)
        complex :: z
        ! 過長的一行代碼可通過延續符 '&' 來換行
        complex_abs = sqrt(real(z)**2 + &
                                         aimag(z)**2)
    end function complex_abs


end module fruity

更多資源

瞭解更多的 Fortan 信息:

  • wikipedia
  • Fortran95language_features
  • fortranwiki.org
    * www.fortran90.org/
  • list of Fortran 95 tutorials
  • Fortran wikibook
  • Fortran resources
    * Mistakes in Fortran 90 Programs That Might Surprise You

有建議?或者發現什麼錯誤?在Github上開一個issue,或者發起pull request!

原著Robert Steed,並由0個好心人修改。
Translated by: Corvusnest
© 2022 Robert Steed
本作品採用 CC BY-SA 3.0 協議進行許可。

user avatar guizimo 頭像 suporka 頭像 zisrfs 頭像 dingxi 頭像
4 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.