本文詳細介紹C語言和Fortran語言的差異
1. C++語言和Fortran語言的發展背景
在程序設計語言的發展過程中,FORTRAN 語言被認為是科學計算的專用語言。後來推出的FORTRAN90 和FORTRAN 95 版本也不例外,它們雖然可以完全實現C++語言同樣的功能,然而其軟件開發環境和軟件的集成性等方面都遠不如C++ 語言。近年來,隨著計算機軟硬件技術的發展,數據結構、數據庫管理技術、可視化與計算機圖形學、用戶接口系統集成以及人工智能等領域的成果被逐漸應用到結構分析軟件中,結構分析軟件的設計並不僅僅局限於單一的科學計算需要涉及眾多的軟件開發領域。C++ 語言可以提供這類軟件開發所需的功能,而用FORTRAN 90 卻很難實現,另一方面從軟件的編程環境來看,目前FORTRAN 90 的編譯器極少,而C++ 語言的編譯系統相當普及,可以運行在各種機型上,便於實現跨平台的軟件系統集成。
2. C語言和Fortran語言的差異
由於兩者產生的背景不同,它們是存在差異的,在比較了幾組源代碼之後,主要有以下體會:
C 最大的優點在於靈活,不但可以藉由 struct 來定義新的數據結構 ,同時 C 的pointer 更可以讓我們自由而且有效率地處理大數據。而在 UNIX 系統 中,由於整個操作系統絕大部分就是 C 寫出來的,故我們也有方便的 C 函數庫, 直接使用系統資源與享受系統帶來的服務,以做到一些低階、快速的動作。而FORTRAN從一開始就用於科學計算,它與C的差異主要表現為:
* 復數運算的速度
* 程序參數與字串
* 內存的動態管理
* 多維陣列的處理
* 函數調用與參數傳遞
2.1. 復數運算的速度
在進行復數運算的時候,C++ 可以定義復數的 class,還可以重新定義所有的四則運算式,復雜的算式也可以做到由一個表達式來解決。但它的重新定義復數四則運算是用函數來做的,使用函數來調用其速度很慢,除非采用 inline function 的方式,但會遇到以下的問題:要先將這個算式拆解,分別算過後再重組結果,故表面上程序代碼很簡潔,但實際上是 compiler做了很多工作,還是要付出相當的計算時間代價的。
至於 Fortran,最大的優點在於復數 (complex number) 的運算,復數是 Fortran 的基本數據類型之一,這正是 C 所缺乏的 (C 基本上只有實型與整型類型而已)。 雖然C 也可以由 struct 的定義,達到復數四則運算的目的,但 卻很可能犧牲了程序效能,或者是程序寫起來相當繁雜降低可讀性。因此,在大量而且要求高速的復數運算場合, Fortran 實際上比 C 還要適合。
然而既然復數已是 Fortran 基本數據類型之一,則 Fortran compiler在設計上可以做到對復數特別的 optimization,例如如果遇到較短的復數運算式,它可以用“心算” 直接得出 real_part 與 imag_part 的 expression,像這樣:
real(a) =……;imag(a) = …….
如此只需兩步就得到結果。直到遇到太長太復雜的算式,才去做拆解的動作。
這樣使用 C 來做復數運算可能需要繞圈圈,而且繞出來的圈圈可能還不小。不過如果程序中需要復合的數據結構,如一個自定義的數據結構中既有浮點數、整數、還有字符串時, Fortran 只有舉白旗投降了。當然, Fortran 如果要做還是可以做,只是不太方便,而且可能也需要繞圈圈。但如果使用 Fortran 90 則不成問題了,因為 Fortran 90 也有類似 C 的 struct 結構以定義復合的數據類型。
2.2. 程序參數與字串
C 程序可以有參數串列, Fortran 則沒有。例如,當程序執 行時,必須輸入 a, b, c
三個參數,在 C 可以這樣寫:
int main(int argc, char **argv)
{
int a, b, c;
a = atoi(argv[1]);
b = atoi(argv[2]);
c = atoi(argv[3]);
}
而程序執行時,參數就是這樣傳入: a.out 12 15 18
Fortran 卻沒有辦法 ,要傳入任何參數,只能透過對話的方式:
integer a, b, c
c ------------------------------------
write(*,*) ''please input integer a:''
read(*,*) a
write(*,*) ''please input integer b:''
read(*,*) b
write(*,*) ''please input integer c:''
read(*,*) c
c ------------------------------------
end
2.3. 內存的動態管理
C 可以動態分配存儲空間給任何數據類型,而Fortran 卻不行。
例如:
float *c_function(int cnt)
{
float *a;
a = malloc(sizeof(float) * cnt);
/*
* 操作 array a.
*/
return a;
}
而且如果在程序執行過程中,如果不再需要這個 array a 了,還可以隨時釋放a所占用的存儲空間。而 Fortran 在一般情況下是不行的,因此在一般的 Fortran 程序中,常見所有需要用的 array, 都是在 MAIN__裡頭就配置好記存儲空間,再一個個傳入subroutine 裡頭來用,例如:
program fout
c ----------------------------
integer cnt
parameter (cnt^P00)
real a(cnt)
call f_routine(cnt, a)
end
c ----------------------------
subroutine f_routine(cnt, a)
c ----------------------------
integer cnt
real a(cnt)
c
c 操作 array a.
c
end
這裡的 parameter 是設定變數的固定值,其作用就相當於 C 的 const 一樣,經設定後的參數就是一個無法改變其值的常數了。有的時候,在某個函數中我們臨時需要一個暫存陣列,但等到計算完成離開函數後,該陣列就沒有用了,這在 C 可以做的很劃算,即進入函數時malloc() 一個陣列,離開前再 free() 掉它。但在 Fortran 中卻別無選擇,一定要在 MAIN__ 裡頭先將暫存陣列配置好,再一起傳入 subroutine 中。
2.4. 多維陣列的處理
不論是在 C 或 Fortran, 所謂的多維陣列實際上只是一個很長的一維連續存儲空間,經過分割後當做多維陣列使用。例如,一個 C 的二維陣列聲明如下:
double a[12][10];
則它在存儲空間中的配置為:
|<--- 10 ---><--- 10 ---> .... <--- 10 --->|
|<<-------------- 共 12 組 --------------->>|
所以它實際上是一塊 12*10*sizeof(double) bytes 的連續存儲區塊,而經由以上的聲明,compiler 便知道當使用到它時,要將分割成每單位元素為 sizeof(double),每 10 個單位一組,而總共 12 組的一個二維陣列,則當我們使用陣列的 index 來存取陣列元素時, compiler 也會自動算好該元素陣列在此存儲區塊的位置,因此就能很方便地使用。
Fortran 也是如此,但有一個很大的不同,它的存儲區塊分割配置的方式是與 C 反向的。例如聲明一個二維陣列如下:
double precision a(12,10)
則它在存儲空間中的配置為:
|<--- 12 ---><--- 12 ---> .... <--- 12 --->|
|<<--------------- 共 10 組 -------------->>|
因此,如果我們要在 Fortran 中配置一個與上頭那個 C 一模一樣的二維陣列時,實際上應該要將其 index 反過來:double precision a(10,12)
除此之外, C 的陣列 index 一律是從 0 開始,對於一個有 N 個元素的陣列,其最後一個 index 是 N-1,且每個陣列元素的 index 值差 1。 Fortran 不一定,要看怎麼聲明了。例如聲明一個陣列如下:
double precision a(100)
則此陣列 index 是從 1 開始,最後一個是 100, 每個的值差 1。不僅如此, Fortran 還可以這樣聲明:
double precision a(11:110)
這還是一個一維陣列,共 100 個元素,但第一個元素的 index 是 11, 最後一個是110 。在這裡我們可以看到, (idx1:idx2) 這樣的敘述在 Fortran 中就是用來指定一個陣列的范圍。
2.5. 函數調用與參數傳遞
C 的函數調用就只有一種方式,函數可以傳入參數,也可以返回值,例如:
void c_function1(int a, float *b)
{
........
}
int c_function2(int a, float *b)
{
int r;
........
return r;
}
int main(void)
{
int a, r;
float b;
c_function1(a, &b);
r = c_function2(a, &b);
}
其中 c_function1() 沒有返回值,而 c_function2() 有返回值。而 C 函數的參數傳入則有兩種方式,正如前面的例子,一個是 call-by-value, 即 (int a),另一個是call-by-reference, 即 (float *b),二者的差別在於:call-by-value 是將參數值復制一份副本到子函數的參數列中,讓子函數使用,就算子函數改變了副本的值,也只是改變了子函數中的副本,也不會影響到上層父函數的原參數值。Call-by-refernce 則相反,它則是將參數所在的地址當做參數傳給子函數中,子函數只知道此參數的存儲空間所在的地址,它要存取該參數時,必須由此地址去找到該參數。因此,子函數使用的,其實是與父函數相同的一個參數,故如果子函數改變了此參數的值,則父函數的參數值也跟著改變了。
而Fortran又與 C 相反了。它的函數有兩種,一為 subroutine,一為 function。subroutine不會有傳回值,但 function 有傳回值,就以上 C 的例子, Fortran 的寫法如下:
subroutine f_function1(a, b)
integer a
real b
........
end
integer function f_function2(a, b)
integer a
real b
........
f_function2 = ....
end
program fout
integer a, r, f_function2
real b
c -----------------------------------
call f_function(a, b)
r = f_function2(a, b)
end
若從 C 的觀點來看, subroutine 其實就相當於 C 的無返回值函數,而 function 就相當於 C 的有返回值函數。但 Fortran 的 function 還要更特殊一點,可以看到,當程序要使用 f_function2 時,必須先申明:
integer f_function2
因為它的返回值是 integer。這有點像Fortran 將 function 看做是參數的一種,只是這個參數不太簡單,它背後還帶著一連串的計算。再看看 f_function2 實際上的寫法,在 C 的有返回值函數中,最後一定要用 return 將要返回的參數返回i來,但Fortran 則不是,它是一定要將返回的值設成該函數名(即 f_functio n2), 如此即可返回。這就好像是, function 本身就是一個參數。
不論是 Fortran 的 subroutine 或 function 的參數,其傳入的方式就只有一種,就是 call-by-reference,所以在上述所有的 Fortran subroutine/function 的參數中,如果子函數改變了參數值,其父函數的相對應參數也會跟著改變。
最後說一點,Fortran 不像 C,所有用到的參數到要聲明,就算不聲明也可以直接用。 若不聲明時,那些參數是有預設型別的,就視其參數名而定,例如參數名以 i, j, k, l, m, n 字母為首者,其預設型別即為 integer, 否則為 float。