FORTRANFormulaTranslation,即公式翻译)作为最早的高级语言,可以说是现存最古老的编程语言了,从它的名字就可以看出,它最初出现就只有一个目的——为科学计算服务。虽然现在有更先进的编程语言,但FORTRAN现在还在继续被人大量使用最主要还是历史遗留问题,即所谓的“祖传代码”。作为最早的高级语言,现在市面上存在大量的遗留代码,那些代码可是久经考验基本上没啥问题可以直接就拿来使用的,现在主要是学术界在使用FORTRAN。

最近在做数值计算的相关部分,前段时间也比较忙,所以这个月发布的内容相对来说就偏少。在有限元编程和其他数值计算方面,不少前辈使用的编程语言都是FORTRAN,我想也有必要了解一下FORTRAN语言的语法规则,以便能够读懂简单的FORTRAN程序,或者移植到其他编程语言中去。

以下内容摘自 Fortran 95快速学习笔记及《有限元方法编程》

1.'Hello World'

!后面的基本语法处一一解释其含义
PROGRAM First_Fortran
IMPLICIT NONE
WRITE(*,*), &
'Hello World'
PAUSE !暂停程序,enter后继续
STOP
END PROGRAM First_Fortran

1.Fortran对大小写不敏感。使用语句行来表示语句的开始和结束而不是分号。

2.长语句需要分两行时需要在第一行的行尾使用 “ & ” 来结束。

3.可以使用1~99999之间的任意数字放在行Fortran语句行的行首作为该语句的语句标号,可以在程序的其他地方通过该数字引用这条语句,语句标号必须是惟一的。

4.注释以 “ ! ” 开头,在编译时 “ ! ” 后的语句被编译器忽略。注释可以放在程序的任何位置。

5.PROGRAM 语句对编译器指定程序的名字,名字命名类似C,但必须以字母开头,该语句必须放在第一个语句行,相当于指定程序的执行入口。

6.IMPLICIT NONE 语句强制要求所有的变量名和常量名都必须显式声明,使Fortran中默认提供输入值类型的功能失效,该语句应该出现在PROGRAM语句之后和类型声明语句之前。默认输入值类型是指在Fortran中,变量可以不经定义直接使用,而变量的类型是以变量名的第一个字母来区分的,例如IMPLICIT real*8(A-H,O-Z)即表示A到H开头的变量类型都是浮点型。使用IMPLICIT NONE之后而所有变量必须在使用之前定义。这在调试程序时有极大的帮助。

7.WRITE 表示向输出写入数据,两个参数与 READ 中的意思相同。eg:

WRITE (*,*) output_list !output_list是输出项列表,多个数据项应使用逗号隔开

WRITE的表控输出语句等价于PRINT *, output_list
7.READ 为从输入读取数据,其中的第一个参数指明从哪个输入/输出单元读入数据,星号表示标准输入,第二个参数指明读入数据的格式,星号表示使用表控输入,即意味着为变量列表中的变量类型决定输入数据需要的格式。eg:

READ (*,*) input_list !将数据读入的变量列表中

每一条READ将始终从一个新行开始读取,如果上一行输入有剩余则会自动被清除。
8.STOP告诉计算机停止运行,END PROGRAM告诉编译器程序中不再有语句需要编译,当STOP紧挨着END PROGRAM语句时,它是可选的。
9.Fortran程序书写风格:

  • 1.保留字都大写

  • 2.程序的变量用小写字母表示

  • 3.常量名最好也使用大写,如PI(3.14159)

  • 4.名字中的下划线出现在两个字之间

10.Fortran的语句分为可执行语句(executable)和不可执行语句(unexecutable),声明即是不可执行语句,应放在程序的开头

2.基本数据类型及其运算

1.Fortran有5个内置的数据类型,其中三个对数字有效(INTEGER, REAL, COMPLEX),一个逻辑相关(LOGICAL),另一个是字符串(CHARACTER)。

2.字符文本可以使用单引号 ,也可以使用双引号

3.有两种方式可以定义变量的类型:默认式和显式。如果在程序中没有明确指定变量类型,那么就是默认式定义变量类型,默认方式为:任何以字母I,J,K,L,M或N开头变量名都假定为 INTEGER,其他字母开头的变量名假定为 REAL,注意 Fortran对大小写不敏感,例如默认情况 inc 变量为整型,big 为实型。
IMPLICIT NONE 语句将使默认变量类型功能失效。
显式声明方式为INTEGER :: var1 [, var2, var3, ...],其中[ ]表示其中的内容可选,只声明而不初始化的话双冒号也是可选的。

4.字符串的声明方式:

CHARACTER(len=10) :: first, last !声明两个长度为10的字符变量
CHARACTER :: initial !声明一个长度为1的字符变量  
CHARACTER(15) :: id !id长度为15

5.使用 PARAMETER 属性创建常数类型,方式为type, PARAMETER :: name=value [, name2=value2, ...]其中 type 的类型可以是整型,实型,逻辑型或字符型。eg:

REAL , PARAMETER :: PI = 3.141593

6.** 表示指数运算。

7.Fortran95/2003 中含有5个强制类型转换函数:

  • INT(X) 将REAL类型的X转换其整数部分

  • NINT(X) 返回REAL类型的X的四舍五入结果

  • CEILING(X) 大于或等于X的最小整数值,X为REAL

  • FLOOR(X) 小于或等于X的最大整数值,X为REAL

  • REAL(I) 将整数转换为实数

8.变量不会被默认初始化

9.逻辑数据类型只有两个可能的值:truefalse,分别对应的内置逻辑常数是

  .TRUE.

  .FALSE.

(注意两连的句点),逻辑变量声明方式

  LOGICAL :: var1, [, var2, var3, ...]

所有声明都应该放在第一条执行语句之前,PROGRAM 语句之后。

10.关系运算符中

  /=

表示不等于,对应的旧形式为

  .NE.

所有运算符:

  新形式:==   /=       >    >=    <    <= 
  旧形式:EQ.  .NE.  .GT.  .GE. .LT. .LE.

11.组合逻辑运算符:

  • .AND. 逻辑与
  • .OR. 逻辑或
  • .NOT. 逻辑非
  • .EQV. 逻辑等值,相同为真
  • .NEQV. 逻辑非等值,不相同为真

12.当逻辑变量出现在以 READ 开头的语句中时,相应的输入值必须以T或F开头的一个或一组字符,相应的值被设置为.TRUE..FALSE,输入其他字符开头将产生运行时错误。当逻辑变量或表达式出现在以 WRITE 开头的语句中时,相应输出将为单个字符T或F。

13.字符串声明与赋值:

CHARACTER(len=3) :: str
str = 'f'

当字符串长度小于变量长度时,默认使用空格填充

14.Fortran中的数组首元素是从1而不是0开始,子串抽取与python一样。例如若 str=’12345’,则str(2:4)234

15.连接操作符//可以将两个子串连接成一个大串。

16.可以在定义类型里手动指定变量所占用的字节大小。方法如下:TYPE(kind) :: var,其中kind为正整数,表示该变量所占字节数,如果不指定,默认为长整型 4 或浮点型的 8。例如INTEGER(4) :: var,var为占用4个字节的整型。Fortran90 之前的编译器声明方式为INTEGER*4 var

17.real(8) var var = 0.0_8 表示为var初始化值为 0.0,且占8个字节。

3.控制语句

3.1选择结构

1.IF 语句与 C 中类似,形式如下:

[名称: ]IF (logical_expr) THEN
  statement 1
  statement 2
ELSE IF (logical_expr_2) THEN [名称]
  statement 1
  statement 2
ELSE [名称]
  statement 1
  statement 2
  ...
END IF [名称]

其中 IF THEN 必须在同一行,且其下面一行必须紧跟可执行语句,ELSEELSE IF END IF 也必须独占一行,END IF 前面面不能有行号。
其中 名称 是可选的,但如果 IF 前面有名称那么 END IF 后面也必须有,且同名。

! 比如:
change_sign: IF(a /= b) THEN 
a = -a 
ELSE
b = -b 
END IF change_sign

IF 语句块中只有一行语句时,等价于IF (logical_expr) expression

2.当嵌套使用IF语句时,最好为其命名

3.SELECT CASE语句类似于C中每个case都带有break语句的switch,用法如下:

[name: ]SELECT CASE (case_expr)
CASE (case1) [name]
  statement 1
  statement 2
  ...
CASE (case2, case3, case4) [name]
  statement 1
  statement 2
  ...
CASE DEFAULT [name]
  statement 1
  statement 2
  ...
END SELECT [name]

其中 case_expr 可以是任意的整数、字符或逻辑表达式。对应的每个子case必须是整数、字符或逻辑数值,或数值范围,所有子case必须相互独立。其中数值范围是指以冒号隔开的一个范围,与python中的范围表示方式一样,如 (1:10) 表示 1~10

3.2循环结构

1.DO 循环:

exit_type: DO 
  ...
  IF (logical_expr) EXIT !或者 IF (logical_expr) CYCLE
  ...
END DO exit_type

控制语句中的 CYCLE 相当于C中的 continue,EXIT 相当于C中的break。循环语句也可以被命名,规则与IF命名一样。

2.DO WHILE循环:

DO WHILE (logical_expr)
  statement 1
  ...
  statement n
END DO

3.迭代 DO 循环,类型C中的 for 循环:

DO index=istart,end,incr
  statement 1
  ...
  statement n
END DO

其中index是一个整数变量,作为循环计数器使用,整数 istartiendincr 分别表示计数起始值,结束值和步长,它们可以是常量、变量或表达式,当 incr 省略时,步长默认为1。

4.输入输出(I/O)操作

1.格式化的 WRITE 输出:

WRITE (*,100) i, result
100 FORMAT (' The result for iteration ', I3, ' is ', F7.3)

意思是输出 iresult,使用100 做为语句标号,相当于一个大的占位符,用于代替 WRITE 语句的第二个控制输出方式的参数,输出结果即是字符串正常输出,带有占位符的地方,使用对应值替换。例如 I3 表示以占用3个字符宽度的方式输出 INTEGER 类型的i,F7.3 表示以占用7个字符宽度且保留小数点后3位方式输出REAL 类型的 result

2.以下三个输出结果是等价:

WRITE (*, 100) i, x     !使用FORMAT控制格式
100 FORMAT (1X, I6, F10.2)

CHARACTER (len = 20) :: string !使用字符变量控制格式
string = '(1X, I6, F10.2)'
WRITE (*, string) i, x

WRITE (*, '(1X, I6, F10.2)') i, x  !在字符常量中的格式

3.老式的Fortran编译器的格式控制字符中的第一个字符将起到控制输出格式的作用,所以上面1例中FORMAT的内容以空格开头,但在Fortran2003之后的版本中并没有这个限制。具体控制字符的作用如下:

  • 1 跳转到新页
  • 空格 单行间距
  • 0 双行间距
    • 没有间距(在前一行上打印)

下面的两句是等价的,都表示在新的一页的开头打印输出

WRITE (*,"('1', 'Count = ', I3)") icount
WRITE (*,"('1Count = ', I3)") icount

4.控制格式描述的符号如下:

  • c 列号
  • d 实数输入或输出小数位右边的位数
  • m 要显示的最小位数
  • n 要跳过的空格数
  • r 重复计数——一个描述符或一组描述符的使用次数
  • w 域宽——输入或输出使用的字符数

5.常见的输出控制格式:

  • 整数输入——I描述符的一般格式为rIwrIw.m
  • 实数输出——F描述符一般格式为rFw.d
  • 实数输出——E描述符一般格式为rEw.d
  • 真正的科学计数输出——ES描述符一般格式为rESw.d
  • 逻辑输出——L描述符一般格式为rLw
  • 字符输出——A描述符一般格式为rArAw
  • X描述符用于在缓冲区中插入间距,用法为nX
  • T描述符用于在缓冲区中跳过特定列,用法为Tc,其中c为要转到的列号
  • 改变输出行——斜线(/)描述符,类似C中的\n,用于换行

举例,以下两条语句是等价的:

320 FORMAT ( 1X, I6, I6, F10.2, F10.2, I6, F10.2, F10.2 )
320 FORMAT ( 1X, I6, 2(I6, 2F10.2) )

当格式控制字符中的宽度无法表示所要输出的数字时,不像C中直接全部输出,Fortran会输出为星号*,例如格式控制字符I1,输出10的时候,会输出一个*

6.输出格式要与输出变量严格对应,否则会有运行时错误

7.READ的读取格式控制与WRITE一一对应

8.Fortran的I/O读写语句的第一个参数就是用于指定读写设备。该位置的星号即表示标准输出或输入,如果使用其他设备则需要指定I/O单元号,单元号必须为整数类型。

9.常用的I/O语句:

  • OPEN 将指定的文件与指定的I/O单元号关联
  • CLOSE 取消指定的文件与指定的I/O单元号的关联
  • READ 从指定的I/O单元读取数据
  • WRITE 向指定的I/O单元写入数据
  • REWIND 移动到文件的开头
  • BACKSPACE 在当前打开的文件中向后移动一个位置

10.OPEN 用法为OPEN (open_list),其中 open_list 包含一组子句,分别指定I/O单元代号、文件名和关于如何存取文件的信息,这些列表使用逗号隔开。

11.open_list中最重要的六项内容:

  • UNIT=int_expr 指明与文件关联的I/O单元代号,int_expr 可以是非负的整数值

  • FILE=char_expr 指定要打开的文件名,char_expr 是一个包含要打开文件的名称的字符值

  • STATUS=char_expr 指定要打开的文件状态,char_expr 为下列值中的一个:'OLD''NEW''REPLACE''SCRATCH''UNKNOW'

  • ACTION=char_expr 指定一个文件是以只读、只写或读写方式打开。char_expr 为下列值中的一个:'READ''WRITE''READWRITE',如果没有指定任何操作,则默认以读写方式打开。

  • IOSTAT=int_var 指定一个整数变量名,打开操作的状态可以返回到这个变量中。如果OPEN语句成功执行,则返回给这个整数变量的值为0。

  • IOMSG=chart_var指定一个字符变量名,如果发生错误,则该错误信息将返回给这个变量。

如果OPEN语句成功执行,则该变量的内容不变。示例:

  !打开一个名为EXAMPLE.DAT的文件,并将其连接到I/O单元8上
  INTEGER :: ierror
  OPEN (UNIT=8, FILE='EXAMPLE.DAT', STATUS='OLD', ACTION='READ', &IOSTAT=ierror)  
  !以自由格式从文件中读取值到x,y,z
  OPEN (UNIT=8, FILE='INPUT.DAT', STATUS='OLD', IOSTAT=ierror)
  READ (8,*)  x, y, z
  !以特定的格式向文件OUTPUT.DAT中写入变量x,y,z的值
  OPEN (UNIT=9, FILE='OUTPUT.DAT', STATUS='REPLACE', IOSTAT=ierror)
  WRITE (9,100) x, y, z
  100 FORMAT (' X = ', F10.2, ' Y = ', F10.2, ' Z = ', F10.2)

12.BACKSPACEREWIND语句的功能相当于修改文件指针所指向的位置

13.READ 在按行读取文件时会自动跳过空行

5.数组

1.Fortran中的数组元素从下标1开始,通过小括号 ( ) 访问

2.数组声明方式如下:

!声明一个含有16个元素的实型数组voltage
REAL, DIMENSION(16) :: voltage
!等价于
REAL :: voltage(16)

!声明一个含有50个长度为20位字符的数组变量last_name
CHARACTER (len=20), DIMENSION(50) :: last_name

DIMENSION属性用来说明被定义数组的大小

3.通过 (/…/) 可以构建常量数组,如(/ 1, 2, 3, 4, 5 /)构建了一个含有5个整型元素的数组常量

4.数组的初始化可以先声明然后使用赋值的方式初始化,或者使用数组构建器初始化,也可以通过给数组名赋值将所有元素初始化同一个值,或者在声明时直接初始化。eg:

REAL, DIMENSION(10) :: array1
DO i = 1, 10
  array1(i) = REAL(i)
END DO
!等价于  
array1 = (/1., 2., 3., 4., 5., ..., 10./)
!等价于
REAL, DIMENSION(10) :: array1 = (/1., 2., 3., 4., 5., ..., 10./)
!将所有的array1初始化为0
array1 = 0

5.可以使用隐式DO循环初始化数组,形式为(arg1, arg2, ..., index = istart, iend, incr),其中 arg1, arg2 等是每次循环执行时估算的值,隐式DO循环支持嵌套。eg:

!上面声明并初始化array1的等价形式
REAL, DIMENSION(10) :: array1 = (/ (REAL(i), i=1,10) /)

6.可以使用以下方式在声明时指定下标取舍范围:

!该数组的大小为upper_bound - lower_bound + 1
REAL, DIMENSION(lower_bound : upper_bound)  :: array

7.声明常量名来作为数组大小:

INTEGER, PARAMETER :: MAX_SIZE = 1000
REAL :: array1(MAX_SIZE)

8.当两个数组元素类型和大小相同时,对数组名的运算相当于对数组的每个元素分别进行运算,如:

REAL, DIMENSION(10) :: a, b, c
c = a + b    !等价于c(i) = a(i) + b(i)

9.可以使用下标三元组或向量下标的形式来使用数组的部分变量,称为部分数组(section array),三元组的使用方式为subscript_1 : subscript_2 : stride,当stride省略时,表示默认步长为1,当subscript_1省略时默认表示从第一个元素开始,当subscript_2省略时默认到最后一个元素,与python的包前不包后不同,Fortran这种局部数组前后都包。

10.向量下标的使用方式如下:

INTEGER, DIMENSION(5) :: vec = (/1, 6, 4, 1, 9/)
REAL, DIMENSION(10)  :: a = (/1., -2., 3., -4., 5., -6., 7., -8., 9., -10./)
!a(vec)的内容是数组[1., -6., -4., 1., 9.]

11.读写数组时可以使用隐式DO循环,形式如下:

WRITE (unit, format) ( arg1, arg2, ... , index = istart, iend, incr)
READ (unit, format) ( arg1, arg2, ... , index = istart, iend, incr)
!以下打印前5个元素的方法等价
WRITE (*,100) a(1), a(2), a(3), a(4), a(5)
100 FORMAT (1X, 'a = ', 5F10.2)
!等价于
WRITE (*,100), (a(i), i = 1, 5)
100 FORMAT (1X, 'a = ', 5F10.2)

12.隐式DO循环可以嵌套使用子数组

5.1子数组

如果给数组的一个或多个下标指定一定的范围,我们就可以用它们来引用数组之中的某些部分(或称为“子数组” ) 。

如果某一下标未指明范围,则意味着引用该下标所在的整个范 围。因此,如果 ab 为二维数组,则 a( : ,1:3) 引用的是数组 a 中前3 列所有的项,而 b(11:13, :) 引用的则是 b 中第11行到第13行所有的项 (与Matlab的子数组引用方法相同)。如果子数组与原数组形式一致,即有正确的行数和列数,则可以像对“整个”数组那样对其进行操作。

在 FORTRAN中有时对整个数组进行加法计算和乘以一个数这样的简单计算非常容易得到结果。但需要注意的是,对相容的数组 a, bc 而言,尽管 *a = bc* 有意义,但它的结果是逐个单元法中数组 b 与 c 的单元乘积*,这与矩阵相乘不同。

5.2进行数组运算的内部函数

为了实现整个数组的算术运算,FORTRAN提供有一些内部函数,这在有限元计算中非常有用。这些内部函数通常分为两类,一类用于数组计算,另一类用于数组检查。

用于数组计算的函数有

FUNCTION MATMUL(a,b)            !返回 a 与 b 的乘积矩阵 
FUNCTION DOT_PRODUCT( v1, v2 )  !返回 v1 与 v2 的点积 
FUNCTION TRANSPOSE(a)           !返回 a 的转置矩阵

用于数组检查的函数有

FUNCTION MAXVAL(a)      !返回数组 a 中的最大值元素(不是绝对值) 
FUNCTION MINVAL(a)      !返回数组 a 中的最小值元素(不是绝对值)
FUNCTION MAXLOC(a)      !返回数组 a 中的最大值元素的位置 
FUNCTION MINLOC(a)      !返回数组 a 中的最小值元素的位置 
FUNCTION PRODUCT(a)     !返回数组 a 中的所有元素之积 
FUNCTION SUM(a)         !返回数组 a 中的所有元素之和 
FUNCTION LBOUND(a,1)  !返回数组 a 第一个下标的下界 
FUNCTION UBOUND(a,1)   !返回数组 a 第一个下标的上界

在数组操作中,数组 a 不止一维,MAXLOCMINLOC 返回指向所需位置相应的整数数目(行,列等)。上述内部函数中的前6个函数允许有一个可选的“屏蔽”变量,例如

asum = SUM(column,MASK=column>=0.0)

计算结果 asum 将包含数组column中所有正项元素之和。

下一篇:认识FORTRAN:从入门到放弃(2)


长风破浪会有时,直挂云帆济沧海。