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
訂閱:
文章 (Atom)