C语言和Fortran语言
发布于:2013-07-02 09:05

C语言入门-C语言和Fortran语言

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。 

更多