2012年10月21日 星期日


最近看到Intel還在打Fortran的廣告,覺得份外訝異,好奇心驅使下重拾這年近花甲的Fortran,
發覺果然進化很多,形式變美了,時下某些流行的演算法教本,拿Fortran來實作搞不好會比用C/C++等實作來得順手些。這或許是美國一堆大學還在教Fortran的原因

此處以 gfortran (i.e. GNU Fortran) 為主, 型式用free form

fixed form: 傳統用法即為固定型式
此型式範例: /usr/lib/python2.7/site-packages/wx-2.8-gtk2-unicode/wx/tools/Editra/tests/syntax/fortran_77.for

free form: 自由型式,從 Fortran 90 開始引進, 諸如GOTO, ASSIGN等影響結構化的傳統用法此型式皆禁用
此型式範例: /usr/lib/python2.7/site-packages/wx-2.8-gtk2-unicode/wx/tools/Editra/tests/syntax/fortran_95.f95
(上兩範例檔的套件版本 wxPythonGTK-2.8.12.0-5)

目前的 gfortran 版本(gcc-gfortran-4.6.1-3.3)對上面兩種型式可自動偵測與判斷;然同一程式兩型式不能混用
傳統上前5格(column)放陳述標號(statement label),第6格為續行標記,由於此處採用free form,為方便起見,通常程式每行前先按<tab>鍵
與以前相同,當續行標記有非0非空的其他字元,視為上一行之連續

注意大小寫同義,此與時下一般語言不同

程式由敘述(statement)組成,一行一敘述

ex.1 程式 hello.for 如下 ('!' 後接註釋)
! Hello World

        program HelloWorld              ! HelloWorld 為主程式名
            i = 100                   ! 與傳統類似,i~n六字母開頭的變數預設為整數變數,其他則預設為實數
            write (*,*) 'Hello, world!',i           ! =>  印出 Hello, world! 後接100  註:用雙引號亦可
        end program HelloWorld

上面程式以 gfortran -o hello hello.for 來編譯連結
=> 產生執行檔 hello  (若不加 -o hello 則預設執行檔為a.out)

若限定變數必先宣告方能使用,則於開始處加 implicit none

ex.2 印出 Hello! World! 後接列印次數,此例共印5次(一行一次)
        program HelloWorld5
          implicit none
          integer j                ! 宣告j為變數,其資料型態為整數 (因有上行implicit none,才須加此行)
          do j = 1, 5                       ! j為迴圈的控制變數(index),j將從1到5, i.e. 每作一次j會加1(預設)
            write (*,*) "Hello! World!",j
          end do                            ! 迴圈終點
        end program HelloWorld5

! 注意do迴圈內,控制變數不可重新定值(此與C語言等不同) 例如加一行 j=2 於此行前再編譯會產生錯誤

ex.3 結果"幾乎"同上(指含有Hello! World!的行同上例,但其後數字皆不是列印次數), 只是先將 "Hello! World!" 用parameter指定為常數名為h的字元常數
        program HelloWorld5a
          implicit none                            !  下行的parameter表設其為常數(i.e.不可更改)
          character*13, parameter :: h = "Hello! World!"    ! 一開始先宣告其為長度13之字元,其後
          integer :: k=9, i
          do i = 1, k, 2           ! 上行k定為9, 最後用2表以跳2方式來計數,故 i 依次為1 3 5 7 9
            if (i .eq. 3)  k=23             ! 當 i=3 時if條件為真,此時會對k重新定值為23,然入迴圈前上限已定,故此不會影響對迴圈的控制(與C等不同)
            if (i .eq. 5)  write (*,*) "現下 i=5"       ! 當 i=5 時if條件為真,此時會印出" 現下 i=5" (於第三行)
            write (*,*) h,i,k                      ! 除第一行k=9外,其他行有k的都會是23
          end do
          write (*,*) "在迴圈外:"
          write (*,*) "i= ",i         ! i超過迴圈上限(k=9)會跳出迴圈 => 此行的i會印出11
          write (*,*) "k= ",k         ! k為23
        end program HelloWorld5a

例中的 .eq. 為表"等於"之關係運算子(relational operator),其他的關係運算子如下:
.lt. 小於(<) .le. 小於或等於; .ne. 不等於;
.gt. 大於(>) .ge. 大於或等於

若ex.3例 if (i .eq. 3)  k=23 那行改成 if (i .eq. 5)  k=23   則可與其下行合併改寫為:
            if (i .eq. 5)  then
k=23
write (*,*) "現下 i=5"
            end if                        ! 此行寫成中間沒空格的 endif 亦可

ex.4 印出"Hello! World!" 後接i值 , 注意do迴圈雖開始於-9結束於-1, 然以2遞增,故do迴圈時i依次為-9,-7,-5,-3,-1 故仍是作5次
        program HelloWorld5b
          character h*5, w*6, x*13       ! 宣告三個字元變數h,w,x, 長度依次為5, 6, 13個字元
          data h,w/'Hello', 'World!'/    ! 可用data對變數設初值,此行使得 h = 'Hello' 且  w = 'World!'
                                         ! 若上行改成 data h,w/2*'Hello'/ 則 h 與 w 皆設成 Hello ;在重覆設值的情況可用 重覆次數*初值 來簡化
          x = h//'! '//w               ! 字元可用//(concatenation)來串接 =>  x = “Hello! World!”
          do i = -9, -1, 2             !
            if (i.eq.-9 .or. i.eq.-1)  print "('現下 i=-9 或 i=-1')"    ! 當i=-9或-1時,印出"現下 i=-9 或 i=-1" (i.e. 第一行跟倒數第二行)
            write (*,*) x, i                                            ! 上行加()是基於格式碼(後述)
          end do
        end program HelloWorld5b

例中 .or. 為 邏輯運算子,邏輯運算子如下(優先權順序有左至右, 而.eqv.與.neqv.優先權最低,但此兩者常相同):
.not. 非;  .and. 且;  .or. 或;
.eqv. 邏輯一致(coincidence);    .neqv. 互斥或(exclusive-or);與.eqv.互補,故.neqv.亦為邏輯不等
因 邏輯運算子的優先權 比 關係運算子 低, 故此if敘述的()中可不再額外加括號

字元變數名(或字元常數名)後可用(開始位置:結東位置)來截取 子字串, 底下以ex4為例:
x(1:5) = x(:5) = h = 'Hello'               ! 開始位置沒給時預設為1
x(8:13) = x(8:) = w = 'World!'             ! 結東位置沒給時預設到最後位置 (此例為13)
x(6:6) = '!'

優先權順序由大到小:(i.e.編號愈低者愈優先)
1.括號()  2.函數  3.次方**  4. 乘除*/  5.加減+-  6. concatenation //   7.關係運算子  8.邏輯運算子

gfortran 的邏輯.TRUE.與.FALSE.雖可分別與整數1與0轉換,
但不能與C語言等將整數用在if敘述中
且無論是邏輯或是整數,都不可像C般用在I/O運算上

ex.(註:雖可轉換,但編譯時會出現警告就是)
! ref.: 6.1.12 Implicitly convert `LOGICAL' and `INTEGER' values

      proGram var01
          implicit none
          LOGICAL k,l
          INTEGER  n
          k = .TRUE.                      ! 邏輯變數常規用法
          write (*,*) k                   ! 印T
          ! 由下可知 1 <-> .TRUE.
          l = 1
          write (*,*) l                   ! 印T
          if (l) write(*,*) "真"          ! 此行會印出"真"
          n = .TRUE.
          write (*,*) n                   ! 印1
          ! 由下可知 0 <-> .FALSE.
          l = 0
          write (*,*) l                   ! 印F
          if (l) write(*,*) "真"          ! 由於 l 為 .FALSE. 故此行不顯示
          n = .FALSE.
          write (*,*) n                   ! 印0
        end program var01


混型算式:
e.g. 此例算式為 整數/整數*實數  ;注意其並不會因最右數值為實數而一開始即全式以實數來計算,而是由運算順序逐步判定,故i/5仍是為整數運算的結果
        program mixTest1
          i = 2
          do while (i < 5)          ! 當do while後條件為真時皆在迴圈內,此例為 i = 2~4
            write (*,*) i/5*4.6     ! 由於i/5結果是整數 => 被除數小於5的皆為 0  故三次皆印出 0.0000000
            i = i+1              
          end do
        end program mixTest1

!  修訂只需改變次序轉成 i*4.6/5
! 當上行改成 i*4.6/5 後,由於電腦實數不符合數學上的實數調密性,故難精確,例如i=2時,將會印出 1.8399999

格式碼:(底下一對單引號可換成一對雙引號,反之亦然)
上例若須印得簡潔,則write那行改成 write (*,'(F4.2)') i*4.6/5   或者  print '(F4.2)', i*4.6/5 亦可,
其中 F4.2 表一實數印4格,其中2格為小數 (故整數只一格,因小數點亦另占一格) 此處 F 稱為格式碼
而用格式T可把資料印在自設的位置,如write那行改成 write (*,'(T10,F4.2)') i*4.6/5 表於第10格始印此實數
注意輸入時可省略小數點,例如 read (*,'(F5.2)') y  若執行時輸入1234 則存於y之值 實為 12.34;若輸入123則y成為1.23 (即以程式定的小數位數為主)
但若輸入時有加小數點,仍以輸入的小數位數為主,如對上行y輸入1.234,則實際上亦是存1.234

實數的格式碼尚有E, 但用E 會用科學表示法, 例如write那行改成 print '(E8.2)', i*4.6/5
E8.2 表共占8格,小數占2位,其後未顯示的小數部份會四捨五入,故會顯示如下:
0.18E+01
0.28E+01
輸入時省略小數點的情況類似F,例如對程式中有 read (*,'(E8.2)') x 執行時輸入123E3 因省略小數點,故依E格式所設的有兩小數,故實際成1.23E3 = 1230
但若輸入12.3E3 由於輸入時有加小數點,故以輸入為主,故實際上亦是存12300.  (注意輸入的指數部份只能整數;且有正負號時可省略E, 如1.6-1 = 1.6E-1 = 0.16)

而倍準度格式嗎D類似E,例如上面print的E8.2改成D8.2,則0.28E+01 會顯示成 0.28D+01

格式碼G會依照實際情況自動選擇 格式F 或 格式E
Gw.d 判斷原則:數值轉成科學表示法後,若 其指數>d 或指數為負,則選擇E;否則選格式F
ex.  write (*,'(G14.6)') 1234.56789   由於 1234.56789 = 0.123456789E4 而 4<6,故擇格式F => 印出 1234.57 (以科學表示之小數六位.123456為準)

其他較常用的格式碼尚有,I-整數,X-跳一格,A-字元資料,L-邏輯(T表真,F表假;輸入用開頭為T或F字串皆可<常用TURE或FALSE>,兩旁的.可加可不加)
以及用/格式來隔行,/的左右各代表不同行,例如 read (*,'(/F4.2/)') y  此時y於第二行輸入才有效,且後面尚空一行(第一,三行隨便打字亦不影響y值)

格式碼前的數字表此格式的重覆次數,例如:'(F4.2, 3X, F4.2)'  其中3X - 跳格X重覆3次,即表3個空格 故兩實數間有三空格
括號前加數數字亦有類次效果,例如:(2(3F4.2))' 相當於 '(2(F4.2, F4.2, F4.2))' 相當於 '(6F4.2)'

T格式尚有TR 與 TL,分別表從當前位置右移 與 左移,例如 TR3 表右移3格,故 TR3 與上面的 3X 同義,故 '(F4.2, TR3, F4.2)' = '(F4.2, 3X, F4.2)'
同理 TL3 表 左移三格;但注意輸入時以當前欲輸入的位置左移
例如 read (*,'(I4,TL3,I4)') i,j  若輸入123456 則i成1234後位置在5,以 TL3 往左移三格後位置在2,故j成2345 而6並未實際輸入至變數

為求方便,有些格式碼(如F,G,I,L等)後可緊接0, 此時程式會依照資料實際情況調格式大小,例如 '(F4.2)' 亦可改成 '(F0.2)';而'(L0)'相當於('L2')
然而字元資料若要如此則格式碼A後須不接數字。若A0則會因表字元寬度0而產生錯誤
例如 hello例的 ex4 的 write (*,*) x, i 那行若改成  write (*,'(A,T15,I1)') x, i   則印出的最後一行會為 "Hello! World! 9"

ex.5 印出5次 Hello! World!
        program HelloWorld5c
          character x*13
          x = "Hello! World!"            ! 下行(下下行)為implied do loop  (i=1~5,作5次)
!          write (*,*) (x, i = 1, 5)     ! 用此行輸出5個hello擠在一起
          write (*,'(A)') (x, i = 1, 5)  ! 用格式碼A才會分印五行的"Hello! World!"
        end program HelloWorld5c

! 最後write 那行亦可寫成 print '(A)', (x, i = -5,-1)    其實遞增1之五個整數數列皆可

另外可如ex.4般另加顯示字串於格式中,例如將ex.5的 '(A)' 改成 '("測試:"A)' 或 "('測試:'A)" 則會印出五行的 "測試:Hello! World!"


一維陣列:
預設元素最多僅能65535個,若超過則應於編譯時加參數 -fmax-array-constructor=元素個數
預設從1開始,故一般宣告陣列大小除為元素個外,常常也為註標(subscript)最大值; 如下例之i(n) 表陣列有 i(1),i(2)...i(n) 共n 個元素
ex. 印出一常陣列i,其元素為 2~200000 之偶數 (編譯方式:gfortran -fmax-array-constructor=100000 -o test ./test.for)
! ref. gfortran.info.xz

        program test
          integer, parameter :: n = 100000           ! 表 整數常數n 為 100000
          integer, parameter :: i(n) = (/ (2*j, j = 1, n) /)    ! 設陣列i, 其元素值依次為2,4,6,....200000
          print '(10(I0,1X))', i      ! I0 表依元素實際情況調整整數格式尺寸,其後接1空格,最先的10表一行印10個元素
        end program test

一般而言,若變數個數多於格式時,則其格式的原模式將重覆套於多出來的變數直至變數輸出(入)完成
然而若有內層括號,則以內層括號及其右邊為重覆模式套於多出來的變數,例如 (F4.2,2(X3,I5),X3,E8.2) 此時重覆模式為 (2(X3,I5),X3,E8.2)
對陣列亦同理,例中的陣列i雖100000個元素,但都是由10個元素的格式 (10(I0,1X)) 重覆套於陣列中所有的元素

印出部份陣列亦可用 implied do loop 代替,如只要印出50001~50100處,只需將print 那行改為:
print '(10(I0,1X))', (i(j), j = 50001, 50100)

若常陣列i改用變數陣列,則 integer, parameter :: i(n) = (/ (2*j, j = 1, n) /) 用下兩行代替:
dimension i(n)
i(:) = (/ (2*j, j = 1, n) /)
或用下列四行代替:
          dimension i(n)        ! 宣告陣列大小,開頭i即預設其元素為整數;  此行用 integer i(n) 亦可
          do  j = 1, n          ! 若陣列i未賦值,即此處省略以下三行,初值亂碼機率高,此點須注意
            i(j) = 2*j          ! 設陣列的元素值為陣列註標的兩倍
          end do
變數命名方式皆類似,故當陣列開頭不是i~n時亦不是預設為整數,若上面i(n) 改成a(n),則前頭須加一行integer a 方能用 dimension a(n)
故用 integer a(n) 會比用 dimension 方便

陣列宣告尺寸時可指定下限,例如把上面四行改成下面四行,則將陣列i 的尺寸設成僅從註標99991~100000 才十個元素:
          dimension i(99991:n)  ! 下限99991,表變數陣列i從99991開始;此行用 integer i(99991:n) 亦可
          do  j = 99991, n      ! 因上行只宣告99991~n ,故只能從 99991 開始賦值
            i(j) = 2*j
          end do
若是對例中的常陣列i 緊縮成這樣的十元素尺寸,僅需:
將          integer, parameter :: i(n) = (/ (2*j, j = 1, n) /)   此行
改成        integer, parameter :: i(99991:n) = (/ (2*j, j = 99991, n) /)  即可

因才十個元素,故編譯時可不加 -fmax-array-constructor=100000

陣列亦可再加allocatable宣告成動態配置,如下例:
ex.
! allocatable array 的元素個數即使100000 (超過65535個),編譯時也不必加  -fmax-array-constructor=100000
! ref.: gfortran.info.xz

        program test1alloc
          implicit none
          integer j,n
          integer, dimension(:), allocatable :: i
          n = 100000
          allocate(i(n))
          i(:) = (/ (2*j, j = 1, n) /)
          print '(10(I0,1X))', i
          deallocate(i)
          print "(//'deallocate(i)後可再重新配置, 輸入 n :')"
          read (*,"(I9)"), n
          allocate(i(n))
          i(:) = (/ (3*j, j = 1, n) /)
          print '(10(I0,1X))', i
          deallocate(i)
        end program test1alloc


陣列註標可為算式或者其他陣列的元素
ex.
! 拿陣列i元素值用在陣列j的註標上
! ref. gfortran.info.xz

      program test0c
          integer, parameter :: m = 99991, n = 100000
          integer, parameter :: i(m:n) = (/ (2*j, j = m, n) /)      ! 陣列i十元素同上,元素值為註標的兩倍
          dimension j(2*m:2*n)    ! 設 陣列j的註標上下限 各為 陣列i註標上下限 的兩倍;元素個數為2n-2m+1個 (此例為19個)
          do  k = i(m)/2, i(n)/2            ! 此行例與 do  k = m, n 同義;寫得較複雓只是為顯示這樣的寫法亦可
            j(i(k)) = 3*k         ! 注意陣列i的元素值成為陣列j的註標,故陣列j 僅偶數註標有以此行設到初值
          end do
          print '(10(I0,1X))', i      ! 下面印出結果的第一行
          print '(10(I0,1X))', j      ! 下面印出結果的二三行
        end program test0c

印出結果如下:(由於程式中僅對偶數註標的陣列j元素設初值,故奇數註標的陣列j元素皆為亂碼)
199982 199984 199986 199988 199990 199992 199994 199996 199998 200000
299973 -1215772392 299976 134519416 299979 -1215771952 299982 1 299985 0
299988 134519488 299991 -1217945612 299994 -1217087842 299997 -1215817072 300000

由上可知,GNU Fortran 的變數在已宣告定義卻尚未給初值前,其變數值為任意值
故最好在 dimension 下一行加一行data設初值,此映設例為加 data j/ 19*0 /
若要自動些,則改加一行為 data j/ p*0 /  且在n = 100000後加 ,p = 2*n-2*m+1 (p亦為常數,故p=...與n=...同行)
此時結果如下:
199982 199984 199986 199988 199990 199992 199994 199996 199998 200000
299973 0 299976 0 299979 0 299982 0 299985 0
299988 0 299991 0 299994 0 299997 0 300000

此例兩行print 合併為一行 print '(10(I0,1X))', i, j  亦會有相同的印出結果
這是由於陣列i恰有十個元素,印完陣列i後接印陣列j正好換行
但陣列i元素個數若不是10的倍數,則此行的接印會讓陣列i後部與陣列j前部印在同一行

若再將 print '(10(I0,1X))', i, j 改成  print '(3(10X,I0))', (k, i(k), j(2*k),  k = m, n)
依次為  陣列i的註標k  陣列i的元素i(k)  陣列j的元素j(2k)  (故陣列j中為0元素不會印出)


二維陣列:同矩陣,基於多維陣列於記憶體放置的方式,以矩陣A為列,當用data輸入A時,資料先填行;同理write等輸出A時,資料先輸出行
ex.
        program test3a
          implicit none
          integer, parameter :: n = 3
          integer a,i,j
          dimension a(n,n)
          data a/1,2,3,4,5,6,7,8,9/        ! 注意資料會先填行

          do  i = 1, n
            print '(3(I0,1X))', a(i,:)     ! 將陣列a第i列印出
          end do

          a = 1 + a             !此用法可使得加法運算皆用在陣列的每個元素上(四則運算皆適用)

          print '("每個元素都加1後:")'
          print '(3(I0,1X))', (a(i,:), i = 1,n)     ! 亦是將陣列a依列印出
        end program test3a
印出如下:
1 4 7
2 5 8
3 6 9
每個元素都加1後:
2 5 8
3 6 9
4 7 10

若希望先填列,可把data那行改成 data ((a(i,j), j=1,n), i=1,n)/1,2,3,4,5,6,7,8,9/


ex. 陣列a為 5x5大小,陣列元素依次為 1,2,3....25
        program test3
          implicit none
          integer, parameter :: n = 5
          integer a,i,j
          dimension a(n,n)
          do  i = 1, n                ! 巢狀迴圈
            do  j = 1, n
              a(i,j) = (i-1)*5+j      ! a(1,1)為1,a(1,2)為2,......a(5,5)=25
            end do
            ! a(i,:) = (/ ((i-1)*5+j ,j=1,n) /)     ! 上面三行可用此行代替
          end do

          print '(/A)', "印a: (用do loop 搭配 implied do)"
          do  i = 1, n
            print '(5(I0,1X))', a(i,:)
          end do

          print '(/"印a: (全用implied do loop)"/,5(I0,1X))',      ! 字數超過compiler限制,必須分行
     +                                         ((a(i,j), j=1,n), i=1,n)

          print '(/"不用loop而印a: (預設會先輸出行"
     ,",故陣列的行向量反而印成橫的)")'      ! 字數超過compiler限制,故必須分行
          print '(5(I0,1X))', a
          ! 即上行相當於 print '(5(I0,1X))', (a(:,j), j=1,n)    ! 依序印出第j行陣列

        end program test3

同理可推廣到多維陣列


副程式(subprogram):
函數(function)  與 副常式(subroutine)  為 Fortran 的 副程式(subprogram)
於其內定義的變數,可見性僅在函數內
注意Fortran為call by reference. 故計算結果亦可藉由parameter傳回,故parameter既可當輸入,又可當輸出,
若要明確規範某參數是輸入,則可於副程式內(或interface)的宣告parameter處加 intent(in)
若欲規範某參數是輸出,則可加 intent(out)
但這些多數情況並非強制,只為建立良好的程式習慣

函數:
函數可分三類 - 1. intrinsic func.(內存函數,又稱庫存函數)  2.statement func.(敘述函數) 3. 函數副程式(function subprogram)

intrinsic function: Fortran 提供的內建函數,例如sin(),cos()等三角函數;能得知陣列註標高低值的ubound(),lbound()...等

statement function: 自定函數可僅用單一敘述即完整地定義此函數
                    函數命名與變數命名類似,i~n六字母開頭的函數名預設其值域為整數,其他為實數

ex. 自定一敘述函數xor3(),對三個邏輯參數作互斥或(exclusive-or);並利用xor3()作出真值表
      proGram sfunc1
          LOGICAL i,j,k,l,m,n,xor3            ! 注意須先宣告xor3()回傳的資料型態為邏輯,不然函數值域會是實數(因xor3開頭為x)
          xor3(l,m,n) = l .neqv. m .neqv. n      ! 此行定義xor3();而l,m,n形式參數(formal parameter)亦可用i,j,k替代
          INTEGER x,y,z

          print *,"i j k XOR"          ! <=> write (*,*) "i j k XOR"
          do x = 0,1              ! 巢狀迴圈
            do y = 0,1
              do z = 0,1
                i = x             ! 將x,y,z中的整數0,1 轉成邏輯i,j,k
                j = y
                k = z
                print "(4L2)",i,j,k,xor3(i,j,k)         ! 此處為xor3()的實際應用,而括號內的i,j,k為實質參數(actual parameter, 實際參數)
              end do
            end do
          end do
      end program sfunc1

印出結果如下:  (輸出不含括號)
 i j k XOR     (x y z)
 F F F F       (0 0 0)
 F F T T       (0 0 1)
 F T F T       (0 1 0)
 F T T F       (0 1 1)
 T F F T       (1 0 0)
 T F T F       (1 0 1)
 T T F F       (1 1 0)
 T T T T       (1 1 1)

若須再例中再定義一個六參數的敘述函數xor6(),除最前宣告的LOGICAL::行中加 xor6 外,只需再於xor3()定義的下一行加:
xor6(i,j,k,l,m,n) = xor3(i,j,k) .neqv. xor3(l,m,n)
即可 (因 互斥或.neqv. 具結合性)

若定義函數的算式中有變數不在函數的引數中,則計算時會以在執行函數當時之變數值為準
例如上式若改定義成 xor6(l,m,n) = xor3(i,j,k) .neqv. xor3(l,m,n)
則執行xor6()時只需l,m,n三個引數,而看當時的i,j,k值為何再代入

函數副程式(function subprogram): 因函數有資料型態,故定義完後,亦須先宣告方能使用

ex.如上例,但以function subprogram來建立xor3()
      program sfunc1b
          implicit none
          INTEGER  x,y,z,xor3     ! 注意此例xor3()參數與回傳值皆是整數;而上例的是邏輯

          write (*,*) "x y z XOR"
          do x = 0,1
            do y = 0,1
              do z = 0,1
                 print "(4L2)",x,y,z,xor3(x,y,z)
              end do
            end do
          end do
      end program sfunc1b

      integer function xor3(x,y,z)
          integer, intent(in) :: x,y,z      ! 不加 intent(in) 雖也可,但規範是好習慣 (此處規範x,y,z當輸入)
          logical i,j,k,o
          i = mod(x,2)    ! mod()為庫存函數,用來取餘數,此例是對2取餘數
          j = mod(y,2)    ! 故此處偶數皆會取成0, 而奇數會取成1
          k = mod(z,2)
          o = i .neqv. j .neqv. k
          xor3 = o                ! 利用函數名xor3來回傳值
          return                  ! 此行return 可省略(因在最後一行)
      end function


ex. 計數器 (由此例知 函數亦可不需要參數)
      program tstcount0
          implicit none
          integer k, cnt                     ! 注意此 k 與 函數cnt()內之k 無關

          k = 100
          print "('1st time cnt() : ',I0)", cnt()
          print "('2nd time cnt() : ',I0)", cnt()
          print "('3rd time cnt() : ',I0)", cnt()
          print "('In main, k = ',I0)", k
      end program tstcount0

      integer function cnt()
          integer :: k = 0      ! 宣告行上的初始值只在第一次呼叫時設立
          k = k+1
          cnt = k
      end function

結果如下: (由結果易知 cnt() 的 k 變數 local & static)
1st time cnt(x) : 1
2nd time cnt(x) : 2
3rd time cnt(x) : 3
In main, k = 100

若cnt()中的k不要static, 只需將其宣告行改為 integer k = 0  (即省略 ::) 或者 integer k
如此則每次呼叫cnt()都只會回傳1 (當然最後一行結果仍是100)

當有參數等副程式漸複雜後,要於主程式使用它們須建介面
然可將函式用contains 含入主程式中,如此則可省去建interface步驟

ex. 可設值計數器 (預設參數例: 實際參數個數少於形式參數時,使用程設之內定)
      program tstcount00
          implicit none
          print "('1st time cnt() : ',I0)", cnt()
          print "('2nd time cnt() : ',I0)", cnt()
          print "('3rd time cnt() : ',I0)", cnt()
          print "('reset... cnt() : ',I0)", cnt(0)      ! 設計數器值為 0
          print "('cnt() recounting: ',I0)", cnt()
       contains
        integer function cnt(x)
          integer, intent(in), optional :: x     ! 宣告整數變數 x 為optional ,規範為輸入
          integer :: k = 0
          if (present(x)) then            ! present(): 測試呼叫此函式時,有無設用此參數,有用時為T,沒用時為F
                  k = x
          else
                 k = k+1
          end if
          cnt = k
        end function
      end program tstcount00

結果如下:
1st time cnt() : 1
2nd time cnt() : 2
3rd time cnt() : 3
reset... cnt() : 0
cnt() recounting: 1

若不用contains含入主程式中,則可implicit none 下行建一介面如下:
          interface
                  integer function cnt(x)
                          integer, intent(in), optional :: x
                  end function
          end interface


副常式(subroutine): 與函數類似,只是用call 來呼叫,且無回傳值 (但由於call by reference,故亦可如函數般利用參數來獲得計算結果)

`COMMON' blocks: 共享區設置,主程式與函數或副常式之間共用的區域
如今compiler由於最佳化等因素會自動alignment, 故共享區並不見得會如預想般的設置,
因此最好共享區中依物件尺寸由大到小的次序來設置
形式參數不可出現在它所屬副程式的common內
若想關閉 自動alignment,可在編譯時加 -fno-align-commons

ex.如函數xor3()例,此處改以subroutine建立,且搭配`COMMON' blocks,使call xor3 時不需參數,以`COMMON' blocks最後一變數為計算結果
      program sfunc1c
          implicit none
          INTEGER  k,l,m,n               ! 右說明下面的共用, 以'='表示共用相同區:k=x;l=y; m=z; n=x3
          common k,l,/testcom/m,n        ! /testcom/m,n 為具名common, 兩/之中的testcom為自設特定共同區之名 ;而k,l 為不具名common
                                         ! 注意同一common名可重覆出現,其後變數亦會依次放於此common名的共同區中
          write (*,*) "k l m XOR"
          do k = 0,1
            do l = 0,1
              do m = 0,1
                 call xor3               ! 此處呼叫副常式
                 print "(4L2)",k,l,m,n
              end do
            end do
          end do
      end program sfunc1c

      subroutine xor3()
          integer x,y,z,x3
          common /testcom/z,x3//x,y           ! // 表unnamed, 故//後為不具名common;有名者依名依次對應,不具名common依不具名區依次對應
          logical i,j,k,o
          i = mod(x,2)
          j = mod(y,2)
          k = mod(z,2)
          o = i .neqv. j .neqv. k
          x3 = o
 return          ! subroutine內最後的return亦可省略,如下例
      end subroutine


當要讓副程式的形式參數的陣列隨實質參數變動,則須建 interface

ex. 選擇排序法(排序之結果為遞增)
      program tstselection
          real :: y(100) = (/ (2*(101-j), j = 1, 100) /)   ! 設陣列j其元素依次為200,198,....,2
          interface
                  subroutine selection(a)
                    real  a(:)
                  end subroutine
          end interface
          print '(10(F0.0,1X))', y
          call selection(y)            ! 由於 call by reference, 故副常式執行完後改變陣列y
          write (*,*) "排序後"
          print '(10(F0.0,1X))', y
      end program tstselection

      subroutine selection(xa)           ! 排序,由小到大
          real xa(:)
          integer lb(1), ub(1)
          lb = lbound(xa)       ! Get bounds,  由於相容多維陣列,故回傳值亦為陣列
          ub = ubound(xa)       ! lb(1) 是得到一維陣列xa的註標下限,ub(1)是獲得註標上限
          do i = lb(1), ub(1)-1
            imin = i
            do j = i+1, ub(1)
              if (xa(imin) .gt. xa(j))   imin = j
            end do
            temp = xa(i)           ! swap
            xa(i) = xa(imin)
            xa(imin) = temp
          end do
      end subroutine

由於例中selection()的形式參數xa 並未固定其陣列尺寸(此例是隨實質參數y陣列而隨機調整xa),
故`COMMON' blocks不適用 (因common陣列必須先定尺寸,或在implicit情況下於common內定尺寸)


由於call by reference, 可將函數名或副常式名當reference參數用(此類參數若在interface中函式中出現,可仍以external宣告,如下面ex2r.例之II.法)
其有兩種宣告法:I.法 external (若是庫存函數只是external 改成 intrinsic,其他皆相同)  II.法 建立 interface

entry: 可在副常式或函數之中用entry加進入點(可不只加一個, 如下例中的 hello2與hello1)

ex1r.(藉參數傳遞副常式reference)
I.法 external
! hello3() 印 三次hello, hello2() 印二次,hello1()印一次
! 注意程式中是先把這些 hello副常式 作為 sayIt副常式的參數,主程式中是藉呼叫sayIt來達成這些動作的

      program hello4entry
          implicit none
          external hello3,hello2,hello1

          print *,"印三次hello"
          call sayIt(hello3)
          print *,"印兩次hello"
          call sayIt(hello2)
          print *,"印一次hello"
          call sayIt(hello1)
      end program hello4entry

      subroutine hello3()
          print *, "Hello! World!"
      entry hello2()                      ! 第一個entry
          print *, "Hello! World!"
      entry hello1()                      ! 第二個entry
          print *, "Hello! World!"
      end subroutine

      subroutine sayIt(fv)
        print *, "I say,"
        call fv
      end subroutine

II.法 Interface
把例中  external hello3,hello2,hello1   此行
改成
          interface
                  subroutine hello3()
                  end subroutine
                  subroutine hello2()      ! 注意 entry 在介面中不是以entry宣告,即就介面而言,視為另一個函數或副常式
                  end subroutine
                  subroutine hello1()
                  end subroutine
          end interface

ex2r. (上例的增改版)
I.法 external

      program hello4entry2
          implicit none
          external hello3,hello2,hello1,sayIt

          print *,"印三次hello"
          call doSomething(sayIt, hello3)
          print *,"印兩次hello"
          call doSomething(sayIt, hello2)
          print *,"印一次hello"
          call doSomething(sayIt, hello1)
      end program hello4entry2

      subroutine hello3()
          print *, "Hello! World!"
      entry hello2()
          print *, "Hello! World!"
      entry hello1()
          print *, "Hello! World!"
      end subroutine

      subroutine sayIt(fv)
        print *, "I say,"
        call fv
      end subroutine

      subroutine doSomething(fv1,fv2)    ! 雖然fv1與fv2對doSomething而言是形式是形式參數
        external fv2                     ! 但fv2 對 fv1 而言是實質參數,故此處須加external fv2
        print *, "I wanna do......"
        call fv1(fv2)
      end subroutine

II.法 Interface
把例中  external hello3,hello2,hello1,sayIt   此行
改成
          interface
                  subroutine sayIt(fv)
                     external fv          ! external 表其參數為函數或副常式
                  end subroutine
                  subroutine hello3()
                  end subroutine
                  subroutine hello2()
                  end subroutine
                  subroutine hello1()
                  end subroutine
          end interface

我們亦可將此段 interface 存成一個檔案,例如檔名為 hello4entry_i2b.intf
然後把 external hello3,hello2,hello1,sayIt 此行
換成   include "hello4entry_i2b.intf"  即可


遞迴:(使用時於編譯時加 -frecursive)
ex. 副常式遞迴例(階乘)
! e.g. gfortran -frecursive -o tstr1 tstr1.for

      program tstr1
          implicit none
          INTEGER  n, f
          interface
                  subroutine tstfact(n,f)     !   f = n!
                    integer n,f
                  end subroutine
          end interface

          n = 5
          call tstfact(n,f)
          print "('factorial ',I0,' is ',I0)", n ,f
      end program tstr1

      subroutine tstfact(n,p)
          integer n,p

          if (n .gt. 1) then
                  call tstfact(n-1, p)
                  p = n * p
          else
                  p = 1
          end if
      end subroutine


ex. 函式遞迴例,亦為階乘,效果同上,但為indirect recursion,故原一函式能處理的須複製成兩份交互遞迴
! e.g. gfortran -frecursive -o tstr0 tstr0.for

      program tstr0
          implicit none
          INTEGER  i
          interface
                  integer function fact0(n)     !    n!
                    integer n
                  end function
          end interface

          do i = 0,5    ! 從0! 印到 5!
            print "('factorial ',I0,' is ',I0)", i ,fact0(i)
          end do
      end program tstr0

      integer function fact0(n)
          integer n
          interface
                  integer function fact0b(n)
                    integer n
                  end function
          end interface

          if (n .gt. 1) then
                  fact0 = n * fact0b(n-1)
          else
                  fact0 = 1
          end if
      end function

      integer function fact0b(n)
          integer n
          interface
                  integer function fact0(n)
                    integer n
                  end function
          end interface

          if (n .gt. 1) then
                  fact0b = n * fact0(n-1)
          else
                  fact0b = 1
          end if
      end function


Cray pointers: (編譯時需加 -fcray-pointer 選項)
類似C語言的指標,此為non-standard extension
語法:pointer ( <pointer1> , <pointee1> ), ( <pointer2> , <pointee2> ), ...
亦可只宣告一對(<pointer1> , <pointee1> ) 其中 pointer1 為整數,表欲指向的記憶體位址;
而 pointee 類似被指對象之別名(alias),一般 pointee型態宣告應出現在指標宣告之後;需一對(pointer,pointee)互相搭配才會類似C的一指標
但若是用於 malloc() 則 pointee型態宣告 應出現在指標宣告之前
ex.
      program tst1pointer
          implicit none
          integer i
          real tar1(20)

          pointer(intp1, pte1(20))
          real pte1
          intp1 = loc(tar1)         ! 將指標與tar1連在一起,loc()函數類似C語言的&

          tar1(:) = (/ (i-21., i = 1,20) /)
          print "('original tar1 : '/, 10(F0.0,X))", tar1

          pte1(:) = (/ (i*10., i = 1,20) /)
          print "('tar1 changed by pointer : '/, 10(F0.0,X))", tar1
      end program tst1pointer

結果如下:
original tar1 :
-20. -19. -18. -17. -16. -15. -14. -13. -12. -11.
-10. -9. -8. -7. -6. -5. -4. -3. -2. -1.
tar1 changed by pointer :
10. 20. 30. 40. 50. 60. 70. 80. 90. 100.
110. 120. 130. 140. 150. 160. 170. 180. 190. 200.

注意!基於最佳化 pointee 的宣告最好不要如下用法,例如上例改成:
          real tar1(20)
          real pte1(20)

          pointer(intp1, pte1)
          intp1 = loc(tar1)
這種用法編譯器會過,但對Fotran來說是不合規則的(illegal)
因上面第二行已宣示pte1為大小20之實數陣列,其後竟將此已存在之實數陣列弄成指標
這種不合規則的用法編譯器不能保證程式執行的安全性

ex. malloc(), free()用法類似C
       program test2malloc
          implicit none
          integer i
          real*8 x(*)
          pointer(ptr_x,x)

          ptr_x = malloc(20*8)

          do i = 1, 20
            x(i) = i*10.
          end do

          print '(10(F0.0,3X))', (x(i), i = 1, 20)   !不能只用一x, 因x實非陣列, 故不像陣列般能知其尺寸

          call free(ptr_x)
       end program test2malloc



***其他補充***
equivalence(等位):
equivalence後同一括號內視為同一群,同一群中的變數共用記憶體,同一群的資料型態最好相同,適合會來作別名,及或多維陣列轉一維
ex. 底下 a,c是指同一個二維陣列,最後一行亦可改為 equivalence (a,b,c)
          integer a,i,j,b,c
          dimension a(3,3),b(9),c(3,3)
          equivalence (a,b),(b,c)
上面最後一行可改成 equivalence (a,b,c)
即 a,b,c 等位,注意先填列,如例中 b(2) 與 a(2,1) 及 c(2,1) 等位
當括號中有變數在common區時,則與其相連者皆在common;但在同一common區者必不能等位, 例如有common x,y則必不能equivalence (x,y)
且尚有些情況會產生錯誤,如例中假使其後再加一行 equivalence (j,b(3)),而其中尚有一行 common i,j,k ,則b照理應在共同區,
但是因連續放置問題...k,b(4)等位; j,b(3)等位; i,b(2)等位;...必然導致b(1) 超出共同區範圍,此會造成錯誤(在編譯時應就不會過)

亂數:
用法:call random_number(r)   r 可不限於一維陣列,r中元素即為亂數
亂數種子的副常式如例中 init_random_seed 般自設會較佳
ex.
      program tst1rand
          implicit none
          real :: r(5,5)
!          real :: r(100)
          integer i

          print "('before randome r: '/,5(F0.20,3X))", (r(i,:), i = 1,5)
          call init_random_seed()
          call random_number(r)
          print "('after randome r: '/,5(F0.20,3X))", (r(i,:), i = 1,5)
      end program tst1rand

      SUBROUTINE init_random_seed()              ! from: gfortran.info.xz
        INTEGER :: i, n, clock
        INTEGER, DIMENSION(:), ALLOCATABLE :: seed

        CALL RANDOM_SEED(size = n)
        ALLOCATE(seed(n))

        CALL SYSTEM_CLOCK(COUNT=clock)
        seed = clock + 37 * (/ (i - 1, i = 1, n) /)

        CALL RANDOM_SEED(PUT = seed)
        DEALLOCATE(seed)
      END SUBROUTINE


計算程式執行時間 (from: gfortran.info.xz)
ex.
          program test_cpu_time
              real :: start, finish
              call cpu_time(start)
                  ! put code to test here
              call cpu_time(finish)
              print '("Time = ",f6.3," seconds.")',finish-start
          end program test_cpu_time