Fortran基本用法小結

再次用到Fortran,於是順便複習了一下,資源來源於網絡,只是重新用Markdown整理了一遍。
(學習Fortran好地方:http://fcode.cn/

目錄:

一、說明
二、概述
三、數據類型及基本輸入輸出
四、流程控制
五、循環
六、數組
七、函數
八、文件

一、說明

本文多數內容是我讀彭國倫《Fortran 95 程序設計》的筆記。只讀到第九章,主要是3~9章,都是最基本的用法(原書共16章)。這裏主要摘錄了我看書過程中總結的一些Fortran和C不同的地方,主要是語法方面。希望這份筆記能夠給學過C但沒有接觸過Fortran的同學帶去一些幫助。要想得更清楚些,推薦看一下原書,覺得作者真的寫得很好,很清楚;如果有C語言的基礎,看完前九應該很快的,花一兩天就行了。覺得如果耐心看完本文,基本功能應該也可以順利用起來了。此外,由於我之前沒有用過Fortran,這次爲了趕文檔看書又看得很粗淺,大多數東西看過之後都沒得及仔細想,只是按着作者的意思去理解。所以這份筆記還處於紙上談兵的層次。如果有不妥的方,希望大家指正。謝謝!

文中藍色的部分是程序代碼,!後面的內容爲註釋。!

二、概述

1、名詞解釋

Fortran=Formula Translator/Translation
一看就知道有什麼特色了:可以把接近數學語言的文本翻譯成機械語言。的確,從一開始,IBM設計的時候就是爲了方便數值計算和科學數據處理。設計強大的數組操作就是爲了實現這一目標。Fortran奠定了高級語言發展的基礎。現在Fortran在科研和機械方面應用很廣。

2、Fortran的主要版本及差別

按其發展歷史,Fortran編譯器的版本其實很多。現在在廣泛使用的是Fortran 77和Fortran90。Fortran 90在Fortran 77基礎上添加了不少使用的功能,並且改良了77編程的版面格式,所以編程時推薦使用90。鑑於很多現成的程序只有77版本,有必要知道77的一些基本常識,至少保證能夠看77程序。以下是77和90的一些格式上的區別。

Fortran 77: 固定格式(fixed format),程序代碼擴展名:.f或.for
(1)若某行以C,c或*開頭,則該行被當成註釋;
(2)每行前六個字符不能寫程序代碼,可空着,或者1~5字符以數字表明行代碼(用作格式化輸入出等);7~72爲程序代碼編寫區;73往後被忽略;
(3)太長的話可以續行,所續行的第六個字符必須是”0”以外的任何字符。
Fortran 90:自由格式(free format), 擴展名:.f90
(1)以”!”引導註釋;
(2)每行可132字符,行代碼放在每行最前面;
(3)以&續行,放在該行末或下行初。

以下都是討論Fortran 90

3、Fortran的一些特點,和C的一些不同

其實很多,在下面涉及具體方面時可以看到。這裏只是大致提一些。
(1)不分大小寫
(2)每句末尾不必要寫分號
(3)程序代碼命令間的空格沒有意義
(4)不像C,Fortran不使用{ }
(5)數據類型多出了複數和邏輯判斷類型。比如複數類型

complex :: a !聲明覆數的方法。複數顯然方便了科學計算,滿足了工程方面需求
a=(1.0,2.0) ! a=1+i

(6)多出了乘冪運算(**)。乘冪除了整數還可以是實數形式。如開方,開立方

a=4.0**0.5a=8.0**(1.0/3.0)。

(7)數組有一些整體操作的功能;可以方便的對部分元素進行操作
(8)有些情況下可以聲明大小待定的數組,很實用的功能

4、Fortran的基本程序結構

先看一看所謂的”Hello Fortran”程序。

program main !程序開始,mainprogram的名字,完全自定義
write(*,*) "Hello" !主程序
stop !終止程序
end [program[main]] !end用於封裝代碼,表示代碼編寫完畢。[]中的內容可省略,下同。 

再看一段實用一些的程序,好有點感性認識。程序用於計算圓柱的表面積,要求輸入底面半徑和。其中展示了Fortran的一些特色用法。程序摘自維基。其實是一個叫www.answers.com的網上引的維基的網頁。推薦去看看能查到不少有意思的東西。

program cylinder !給主函數起個名字

! Calculate the area of a cylinder.
! Declare variables and constants.
! constants=pi
! variables=radius squared and height
implicit none ! Require all variables to be explicitly declared
!這個一般都是要寫上的。下面會進一步說明。
integer :: ierr
character :: yn
real :: radius, height, area
real, parameter :: pi = 3.1415926536 !這是常量的聲明方法

interactive_loop: do !do循環,Fortran中的循環可以加標籤,如d前面的interactive_loop就是標籤

! Prompt the user for radius and height and read them.
write (*,*) 'Enter radius and height.' !屏幕輸出
read (*,*,iostat=ierr) radius,height !鍵盤輸入。isotat的值用判斷輸入成功否。

! If radius and height could not be read from input, then cycle through the loop.
if (ierr /= 0) then 
    write(*,*) 'Error, invalid input.'
    cycle interactive_loop !cycle 相當於C裏的continue
end if

! Compute area. The ** means "raise to a power."
area = 2 * pi * (radius**2 + radius*height) ! 指數運算比C方便

! Write the input variables (radius, height) and output (area) to the screen. 
write (*,'(1x,a7,f6.2,5x,a7,f6.2,5x,a5,f6.2)') & !"&"表示續行。這裏還顯示了格式化輸出
'radius=',radius,'height=',height,'area=',area
yn = ' '

yn_loop: do !內嵌的另一個do循環
write(*,*) 'Perform another calculation? y[n]'
read(*,'(a1)') yn
if (yn=='y' .or. yn=='Y') exit yn_loop
if (yn=='n' .or. yn=='N' .or. yn==' ') exit interactive_loop
end do yn_loop !結束內嵌do循環

end do interactive_loop 

end program cylinder 

Fortran程序的主要結構就是這樣了。一般還會有些module的部分在主函數前,函數在主函數後。

三、數據類型及基本輸入輸出

1、數據類型,聲明及賦初值

(1)integer: 短整型kind=2, 長整型kind=4

integer([kind=]2) :: a=3

如果聲明成integer:: a,則默認爲長整型。
!”::” 在聲明並同時賦初值時必須要寫上;類型名後面有形容詞時也必須保留::;其他情況可略去
!所謂形容詞,可以看一下這個。比如聲明常數

real,parameter :: pi=3.1415926

parameter就是形容詞。

(2)real:單精度kind=4(默認),雙精度kind=8

real([kind=]8) :: a=3.0

還有指數的形式,如1E10爲單精度,1D10爲雙精度

(3)complex 單精度和雙精度

complex([kind=]4) b

(4)character

character([len=]10) c !len爲最大長度

(5)logical

logical*2 :: d=.ture. (等價於logical(2)::d=.ture.)

(6)自定義類型type:類似於C中的struct
Fortran 77中給變量賦初值常用DATA命令,可同時給多個變量賦初值

data a,b,string /1, 2.0, 'fortran'/

與C不同的是,Fortran中變量不聲明也能使用,即有默認類型(跟implicit命令有關)。按
照默認的定,以i,j,k,l,m,n開頭的變量被定義爲integer,其餘爲real。取消該設置需在程序聲明部分之前implicit none。彭國倫建議一般都使用該語句。
另一點關於聲明的不同是Fortran有”等價聲明”:

integer a,b
equivalence(a,b)

使得a,b使用同一塊內存。這樣可以節省內存;有時可精簡代碼。如:equivalence(很長名字的變量如三維數組的某個元素,a),之後使用a來編寫程序就簡潔多了。

2、基本輸入輸出

輸入

read(*,*) a !從鍵盤讀入 

輸出

write(*,*) "text" !在屏幕上輸出。Fortran 77' text'。Fortan 90中一般" "' '都可
print *,"text" !只能用於屏幕輸出

(*,*)完整寫爲(unit=*,fmt=*)
其中unit爲輸入/輸出位置,如屏幕,文件等;fmt爲格式。如這兩項都寫成*,則按默認的方式進行,即上面描述的。print後面的*表示按默認格式輸出。

四、流程控制

1、運算符

(1)六個關係運算符

等於 不等於 大於 大於等於 小於 小於等於 適用標準
== /= > >= < <= Fortran 90用法
.EQ. .NE. .GT. .GE. .LT. .LE. Fortran 77用法

(2)五大邏輯運算符

邏輯與 邏輯或 邏輯非 邏輯等價 邏輯不等價
.AND. .OR. .NOT. .EQV. .NEQV.

! 僅.NOT.連接一個表達式,其餘左右兩邊都要有表達式(可以是logical類型的變量)
!.EQV.:當兩邊邏輯運算值相同時爲真, .NEQV.:當兩邊邏輯運算值不同時爲真

2、IF

(1) 基本

if(邏輯判斷式) then
   ……
end if 

如果then後面只有一句,可寫爲

if(邏輯判斷式) …… !thenend if可省略

(2) 多重判斷

if(條件1then
   ……
else if(條件2then
   ……
else if(條件3then
   ……
else
   ……
end if

(3) 嵌套

if(邏輯判斷式) then
   if(邏輯判斷式) then
      if(邏輯判斷式) then
      else if(邏輯判斷式) then
         ……
      else
         …… 
      end if 
   end if
end if

(4) 算術判斷

      program example

      implicit none
      real c

      write (*,*) "input a number"
      read (*,*) c
      if(c) 10,20,30 !//10,20和30爲行代碼,根據c小於/等於/大於0,執行10/20/30行的程
 10   write (*,*) "A"
      goto 40 !//goto可實現跳到任意前面或後面的行代碼處,但用多了破壞程序結
 20   write (*,*) "B"
      goto 40
 30   write (*,*) "C"
      goto 40
 40   stop

      end program example

3、SELECT CASE

類似於C的switch語句

select case(變量)
case(數值1) !//比如case(1:5)代表1<=變量<=5會執行該模塊
   ……  !//case135)代表變量等於135會執行該模塊
case(數值2) !//括號中數值只能是integer,character或logical型常量,不能real型
   ……
case default
   ……
end case

4、PAUSE, CONTINUE

pause暫停程序執行,按enter可繼續執行
continue貌似沒什麼用處,可用作封裝程序的標誌

五、循環

1、DO

do counter=初值, 終值, 增/減量 !//counter的值從初值到終值按增/減量變,
   …… !//counter每取一個值對應着一次循環。增/減量不寫則認爲1
   …… 
   …… !//循環主體也沒有必要用{}
   …… 
end do

Fortran 77中不是用end do來終止,而是下面這樣子:

do 循環終行行代碼 counter=初值, 終值, 增/減量 
   …… 
行代碼 …… !//這是do的最後一行

2、DO WHILE

do while(邏輯運算)
   …… 
   …… 
end do

類似於C中的while(邏輯運算) {……}。
一開始那個計算圓柱表面積的程序中,應該也算是這一類。不過它是通過內部的if語句來控制循。看來也是可以的,不過在這本書上沒看到這樣寫。其實應該也可以歸於下面這種。

3、沒看到和C裏面的do{……}while(邏輯運算); 相對應的循環語句.

不過可以這樣,保證至少做一循環:

do while(.ture.)
   …… 
   …… 
   if(邏輯運算) exit 
end do

exit就好比C裏面的break。C裏的continue在Fortran裏是cycle

4、Fortran的一個特色:帶署名的循環

可以這樣,不易出錯:

outer: do i=1,3 
inner: do j=1,3
    …… 
end do inner
end do outer

還可以這樣,很方便:

loop1: do i=1,3
loop2: do j=1,3
if(i==3) exit loop1 !exit終止整個循環loop1
if(j==2) cycle loop2 !cycle跳出loop2的本次循環,進行loop2的下次循環
write(*,*) i,j
end do loop2
end do loop1

還有一些循環主要用於Fortran中的數組運算,爲Fortran特有,很實用。

六、數組

1、數組的聲明

和C不同的是,Fortran中的數組元素的索引值寫在()內,且高維的也只用一個(),如

integer a(5) !聲明一個整型一維數組
real :: b(3,6) !聲明一個實型二維數組

類型可以是integer, real, character, logical或type。最高可以到7維。
數組大小必須爲常數。但是和C語言不同,Fortran也有辦法使用大小可變的數組,方法如:

integer, allocatable :: a(:)
!聲明小可變經過某個途徑得知所需數組大小size之後,用下面的語句:
allocate(a(size)) !配置內存空間

之後該數組和通過一般方法聲明的數組完全相同。
與C不同,Fortran索引值默認爲從1開始,而且可以在聲明時改變該規則:

integer a(-3:1) ! 索引值爲-3-2-1,0,1
integer b(2:3,-1:3) !b(2~3,-1~3)爲可使用的元素

2、數組在內存中的存放

和C不同,Fortran中的數組比如a(2,2)在內存中存放順序爲a(1,1),a(2,1),a(1,2),a(2,2)。原則是放低維的元素,再放高維的元素。此規則稱爲column major。

3、賦初值

(1)最普通的做法:

integer a(5)
data a /1,2,3,4,5/
或integer :: a(5)=(/1,2,3,4,5/)
若integer :: a(5)=5,則5個元素均爲5

對於integer :: a(2,2)=(/1,2,3,4/)
根據數組元素在內存中存放的方式,等價於賦值
a(1,1)=1,a(2,1)=2,a(1,2)=3,a(2,2)=4

(2)利用Fortran的特色:隱含式循環。看例子就明白了。

integer a(5)
integer i
data (a(i),i=2,4)/2,3,4/ !(a(i),i=2,4)表示i24循環,增量爲默認值1

還可以這樣:

integer i
integer :: a(5)=(/1,(2,i=2,4),5/) !五個元素分別賦值爲12225
integer :: b(5)=(/i, i=1,5/) !五個元素分別賦值爲1234

還可以嵌套

data ((a(i,j),i=1,2),j=1,2)=/1,2,3,4/ !//a(1,1)=1,1(2,1)=2,a(1,2)=3,a(2,2)=4

4、操作整個數組

設a,b爲相同類型、維數和大小的數組

a=5 !所有元素賦值爲5
a=(/1,2,3/) !這裏假設a爲一維,a(1)=1,a(2)=2,a(3)=3
a=b !對應元素賦值,要求a,b,c維數和大小相同,下同
a=b+c 
a=b-c
a=b*c
a=b/c
a=sin(b) !內部函數都可以這樣用

5、操作部分數組元素

a爲一維數組

a(3:5)=(/3,4,5/) !a(3)=3,a(4)=4,a(5)=5
a(1:5:2)=3 !a(1)=3,a(3)=3,a(5)=3
a(3:)=5 !a(3)以及之後的所有元素賦值爲5
a(1:3)=b(4:6) !類似於這種的要求左右數組元素個數相同
a(:)=b(:,2) !a(1)=b(1,2),a(2)=b(2,2),以此類推

6、WHERE

where形式上類似於if,但只用於設置數組。設有兩個同樣類型、維數和大小的數組a,b

where(a<3)
    b=a !a中小於3的元素賦值給b對應位置的元素
end where 

再如:where(a(1:3)/=0) c=a !略去了end where,因爲只跟了一行where可嵌,也可類似do循環有署名標籤。

7、FORALL

有點像C中的for循環:

forall(triplet1[,triplet2 [,triplet3…]],mask)

其中triplet形如i=2:6:2,表示循環,最後一個數字省略則增量爲1

例如:
forall(i=1:5,j=1:5,a(i,j)<10)
    a(i,j)=1
end forall

又如: forall(i=1:5,j=1:5,a(i,j)/=0) a(i,j)=1/a(i,j)
forall也可以嵌套使用,好比C中for循環的嵌套。

七、函數

Fortran中函數分兩類:子程序(subroutine)自定義函數(function)。自定義函數本質上就是學上的函數,一般要傳遞自變量給自定義函數,返回函數值。子程序不一定是這樣,可以沒有返值。傳遞參數要注意類型的對應,這跟C是一樣的。

1、子程序

目的:把某一段經常使用的有特定功能的程序獨立出來,可以方便調用。習慣上一般都把子程序放在主程序結束之後。
形式

subroutine name (parameter1, parameter2) 
!給子程序起一個有意義的名字。可以傳遞參數,子程序無返回值與自定義函數不同之處。括號內也可以空着,代不傳遞參數。 
implicit none 
integer:: parameter1, parameter2 !需要定義一下接收參數的類型。
…… !接下來的程序編寫跟主程序沒有任何別。
…… 
return !跟C不同,這裏表示子程序執行後回到調用它的地方繼續執行下面的程序。不一定放在最後。
!可以放在子程序的其他位置,作用相同;子程序中return之後的部分不執行。還可省略不寫
end [subroutine name]

調用:使用call命令直接使用,不需要聲明。在調用處寫:

call subroutine name(parameter1,parameter2)

注意點
a.子程序之間也可相互調用。直接調用就是了,像在主程序中調用子程序一樣。
b.傳遞參數的原理和C中不同。Fortran裏是傳址調用(call by address/reference),就是傳遞時用參數和子程序中接收時用的參數使用同一個地址,儘管命名可以不同。這樣如果子程序的執行改子程序中接收參數的值,所傳遞的參數也相應發生變化。
c.子程序各自內部定義的變量具有獨立性,類似於C。各自的行代碼也具有獨立性。因此各個子程序主程序中有相同的變量名、行代碼號,並不會相互影響。

2、自定義函數

和子程序的明顯不同在於:需要在主程序中聲明之後才能使用。調用方式也有差別。另外按照慣例用函數不去改變自變量的值。如果要改變傳遞參數的值,習慣上用子程序來做。
聲明方式

real, external :: function_name

一般自定義函數也是放在主程序之後。
形式

function function_name(parameter1, parameter2)
implicit none
real:: parameter1, parameter2 !聲明函數參數類型,這是必需的
real::function_name !聲明函數返回值類型,這是必需的
……
…… 
function_name=…. !返回值的表達式,以函數名返回
return !跟C不同,這裏表示子程序執行後回到調用它的地方繼續執行下面的程序。可以不寫
end 

也可以這樣直接聲明返回值類型,簡潔些:

real function function_name(parameter1, parameter2)
implicit none
real:: parameter1, parameter2 !這個還是必需的
……
…… 
function_name=…. !返回值表達式
return
end 

調用

function_name(parameter1,parameter2)

不需要call命令。
自定義函數可以相互調用。調用時也需要事先聲明。
總之,調用自定義函數前需要做聲明,調用子程序則不需要。

3、關於函數中的變量

(1)注意類型的對應。Fortran中甚至可以傳遞數值常量,但只有跟函數定義的參數類型對應纔會到想要的結果。如call ShowReal(1.0)就必須用1.0而不是1。
(2)傳遞數組參數,也跟C一樣是傳地址,不過不一定是數組首地址,而可以是數組某個指定元素地址。比如有數組a(5),調用call function(a)則傳遞a(1)的地址,調用call function(a(3))則遞a(3)的地址。
(3)多維數組作爲函數參數,跟C相反的是,最後一維的大小可以不寫,其他維大小必須寫。這決於Fortran中數組元素column major的存放方式。
(4)在函數中,如果數組是接收用的參數,則在聲明時可以用變量賦值它的大小,甚至可以不指定大小。例如:

subroutine Array(num,size)
implicit none
integer:: size
integer num(size) !可以定義一個數組,其大小是通過傳遞過來的參數決定的。這很實用。
…… 
……
return
end

!注意這裏與在主程序中聲明數組時,數組大小需要用常數來規定大小的規則不太一樣,而這裏也可以用參數變量賦值其數組大小。
(5)save命令:將函數中的變量值在調用之後保留下來,下次調用此函數時該變量的值就是上次保的值。只要在定義時加上save就行:

integer, save :: a=1

(6)傳遞函數(包括自定義函數、庫函數、子程序都是可以的)。類似於C中的函數指針需要在主程序和調用函數的函數中都聲明作爲參數傳遞的函數。如

real, external :: function !自定義函數
real, intrinsic :: sin !庫函數
external sub !子程序

(7)函數使用接口(interface):一段程序模塊。以下情況必需:
a.函數返回值爲數組
b.指定參數位置來傳遞參數時
c.所調用的函數參數個數不固定
d.輸入指標參數時
e.函數返回值爲指針時。
具體用法結合例子容易看懂。例子都很長。看書吧。

4、全局變量

功能就不用說了。
原理:根據聲明時的相對位置關係而取用,不同與C中根據變量名使用。

如果在主程序中定義:

integer :: a,b
common a,b !就是這樣定義全局變量的

在子程序或自定義函數中定義:

integer :: c,d
common c,d

則a和c共用相同內存,b和d共用相同內存。

全局變量太多時會很麻煩。可以把它們人爲歸類,只需在定義時在common後面加上區間名。如

common /groupe1/ a, common /group2/ b。

這樣使用時就不必把所有全局變量都列出來,再聲明common /groupe1/ c就可以用a、c全局變量了。

可以使用block data程序模塊。在主程序和函數中不能直接使用前面提到的data命令給全局變量賦初值。可以給它們各自賦初值;如果要使用data命令必須要這樣:

block data [name]
implicit none
integer a,b,c
real d,e
common a b c
common /group1/ d,e
data a,b,c,d,e /1,2,3,4.0,5.0/
end [block data [name]]

5、Module

Module不是函數。它用於封裝程序模塊,一般是把具有相關功能的函數及變量封裝在一起。用法很單,但能提供很多方便,使程序變得簡潔,比如使用全局變量不必每次都聲明一長串,
寫在Module裏調用就行了。Module一般寫在主程序開始之前。
形式

module module_name
    ……
    ……
end [module [module_name]]

使用:在主程序或函數中使用時,需要在聲明之前先寫上一行:

use module_name.

Module中有函數時必須在contains命令之後(即在某一行寫上contains然後下面開始寫函數,多所有函數都寫在這個contains之後)。並且module中定義過的變量在module裏的函數中可直接使用,函數之間也可以直接相互調用,連module中的自定義函數在被調用時也不用先聲明。

6、include放在需要的任何地方,插入另外的文件(必須在同一目錄下)。如:

include 'funcion.f90'

八、文件

1、文本文件

Fortran裏有兩種讀取文件的方式,對應於兩種文件
順序讀取:用於文本文件
直接讀取:用於二進制文件
這裏只摘錄關於文本文件的讀取。一般模式如下。

character(len=20)::filenamein="in.txt", filenameout="out.txt" !文件名
logical alive
integer::fileidin=10,fileidout=20 
!1020是給文件編的號,除1256的正整數都可,因爲26是默認的輸出位置(屏幕),15是默認的輸入位置(鍵盤)
integer::error
real::in,out

!下面這一段用於確認指定名字的文件是否存在
inquire(file=filenamein, exist=alive) !如果存在,alive賦值爲0
if(.NOT. alive) then
    write(*,*) trim(filenamein), " doesn't exist."!trim用於刪去filenamein中字串!後面的stop多餘空格,輸出時好看些
end if

open([unit=]fileidin, file=filenamein, status="old")
open([unit=]fileidout,file=filenameout[,status="new"])
!unit指定輸入/輸出的位置。打開已有文件一定要用status="old";打開新文件用status="new";
!不指定status,則默認status="unknown",覆蓋已有文件或打開新文件……

read([unit=]fileidin, [fmt=]100,iostat=error )in !error=0表示正確讀入數據。
100 format(1X,F6.3) 
!按一定格式輸入輸出,格式可以另外寫並指定行代碼,也可以直接寫在read/writewrite(([unit=]fileidout, "(1X,F6.3)")out 
close(fileidin)
close(fileidout)
!1X代表一個空格。F6.3代表real型數據用佔6個字符(含小數點),其中小數點後三位。
!常用的還有I3,用於整型數據,共佔三個字符;A8,字符型,佔8個字符。換行用 /

二進制文件的讀取有所不同。不再列舉。

2、內部文件

另一個很實用的讀寫功能是內部文件(internal file)。看看這個例子就明白了。

integer::a=1,b=2
character(len=20)::string
write(unit=string,fmt="(I2,'+',I2,'=',I2)")a,b,a+b
write(*,*)string

則結果輸出1+2=3。反過來也是可以的:

integer a
character(len=20)::string="123"
read(string,*)a
write(*,*)a

則輸出123。

!全文結束。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章