本系列笔记假定读者具有基础的计算机知识,以及 c/c++, Java 等至少一门现代编程语言,只学习 Fortran 的核心应用——数值计算需要的部分,对于其他的部分比如鸡肋的 Fortran 面向对象等,直接忽略(c++,Java 不香么)。我们使用 vscode+mingw(gfortran)的编程环境和编译器,环境配置过程见前文。

基本特点

  • 固定格式 Fortran:在早期计算机使用 Fortran 进行科学计算时,需要使用穿孔卡片纸带输入源代码,因此每行的缩进/长度等格式有着极其苛刻的要求,一般全文都使用大写字符,文件名以.FOR,.for 或.f 为后缀。语法标准为 Fortran 77。如果希望感受 20 年前的 Fortran 教程风格,参考USTC Fortran 教程
  • 自由格式 Fortran:为了适应现代的编程风格,Fortran 标准提出了自由格式,没有那些适合打孔纸带的格式要求。一般全文的保留关键词使用小写字符,文件名以.F90、.f90 为后缀。(固定格式就应该彻底留在历史书上,我们当然应该使用自由格式编写 Fortran,使用 Fortran 90 及以后的现代语法标准)

Fortran 字符集:

  • 大小写英文字母:A 到 Z,a 到 z。
  • 数字:0 到 9。
  • 22 个特殊符号:
    • +加法,-减法,*乘法,/除法,()小括号。
    • =等号,>大于,<小于。
    • ,英文逗号,.英文句号,:英文冒号,;英文分号,‘单引号,"双引号。
    • _下划线,空格,%百分号,&与,$美元符号,?问号,!感叹号。

与 c/c++相比,Fortran 还有以下值得注意的特点:

  • Fortran 源代码中不区分大小写,INTEGER 和 Integer, integer 是完全一样的效果,变量 a 和 A 是同一个变量。为了历史兼容性,不论固定格式还是自由格式都不区分大小写。
  • 以感叹号!开头的一行视作注释。
  • 不支持使用"{}"划分代码结构,甚至"{}","[]"都不是合法的 Fortran 源代码字符;也不要求使用任何的缩进划分代码结构,只是为了方便阅读建议使用。
  • 每一行结尾不需要以分号;结尾。每一行至多 132 个字符,如果代码过长需要跨行拼接:在第一行的行尾使用&,第二行的行首使用&进行拼接。(只在一行使用&也是可以的)
  • 空格除了方便阅读以外没有任何意义,甚至可以用空格分隔保留关键词。
  • 变量名可以含有字母,数字和下划线,但是只允许字母开头,不允许下划线开头。

禁止变量的默认类型

与 c 类似,Fortran 使用变量之前通常用需要声明变量的类型。但为了兼容打孔纸带时代的语法糖,Fortran 还可以不声明直接使用变量——它居然自己通过变量名的首字母推断变量类型:首字母为 I,J,K,L,M,N 的变量视作整数,其他视作浮点数。这简直离了大谱,为了避免可能的 bug,建议在源代码的开头使用下面的语句禁止所有的默认类型的自动推断。

1
implicit none

与 c/c++,Java,python 等现代的计算机语言相比,Fortran 的辈分实在是太老了:从 1957 年开始,Fortran 的诞生标志着计算机从汇编进入了高级语言时代。此后也陆续涌现了一些高级语言,但同时代的只有 Fortran 语言活到了现在,c 语言直到 1972 年才诞生。 Fortran 为了完全兼容历史上的固定格式语法,保留了很多奇怪的语法糖和规定,包括全局乱用的 goto 语句,以及上面的默认类型,这些当然都是我们难以容忍的,可能会导致一些难以理解的 bug。因此我们选择尽可能地避免奇怪的语法,虽然它们仍然有效。

数据类型与运算符

Fortran 其实就是 Formula Translation 的缩写,因此从数学表达式转换为 Fortran 语句是非常方便直观的,这可能也是 Fortran 活到现在的原因之一吧。

基本数据类型

  1. 整数——integer:包括短整数——对应 c 语言的 short,长整数——对应 c 语言的 int。默认都是有符号的。和 c 语言不同,我们需要明确指定整数采用几个字节:2bytes,4bytes。
  2. 实数,浮点数——real:包括单精度——对应 c 语言的 float,双精度——对应 c 语言的 double。和 c 语言不同,我们需要明确指定实数采用几个字节:4bytes,8bytes。
  3. 复数——complex:a+bi 其实就是一个实数对(a,b),也分为单精度和双精度。Fortran 直接在内置数据类型中就提供了复数,而 c/c++实现复数需要进一步的封装。
  4. 字符——character;
  5. 逻辑判断——logical:支持.true.和.false.。

数学运算符:

  1. +,加法,-,减法
  2. *,乘法,/,除法
  3. **乘方
  4. () 括号,改变优先级

注意不支持+=之类的语法糖。和 c 语言一样,整数之间的除法也有自动取整的问题。还需要注意类型之间可能的显式转换或者隐式转换。

1
2
3
4
5
6
7
program main
implicit none
integer :: a=2
integer :: b=3
write(*,*) "a/b=", a/b
write(*,*) "a*1.0/b=", a*1.0/b
end program main

HelloWorld

和 c 语言从 main 函数执行一样,Fortran 也有固定的主程序,Helloworld 程序如下:

1
2
3
4
program main
implicit none
write(*,*) "hello,world!"
end program main

这里 main 是我们给 program 起的名称,也可以是其他名称,Fortran 程序只需要有唯一的 program 并从这里开始执行,在 end 处结束。implicit 命令必须在 program 之后的第一行。

Hello IO

最基本的输入输出的例子如下:

1
2
3
4
5
6
7
program main
implicit none
integer :: a
write(*,*) "please input a:"
read(*,*) a
write(*,*) "a=",a
end program main

运行效果为

1
2
3
 please input a:
8
a= 8

write 是标准的输出语句,read 是标准的输入语句。其中的(*,*):第一个*指的是默认的输入/输出设备,第二个*是默认的输入/输出格式,可以改成其他的参数,先不管这些细节,(*,*)将就着总是可以用的。print 语句也可以用来输出,但是局限于向屏幕输出,建议使用 write 完全替代。

变量声明

与 c 语言不同,Fortran 对于变量声明的位置要求严格,只能在程序代码的可执行部分之前,一旦开始执行输入输出或者运算就不能再进行变量声明,报编译错误。基本数据类型的声明,可以用 kind 明确指定变量的字节数确保跨平台性,也可以不使用 kind 使用默认的字节数。

1
2
3
4
integer(kind=2) :: a1
integer(4) :: a2 ! 也代表kind=4
real*2 :: b1 ! 也代表kind=2
real(kind=4) :: b2, b3 ! 可以同时声明多个变量

双冒号::表示变量的声明和描述已经完成,因为下文允许较复杂的声明描述。对于字面值常量可以在下划线后指定对应的 kind。

1
2
a1 = 1.0_4 ! 确保是单精度浮点数
ab = 1.0_8 ! 确保是双精度浮点数

可以在声明时定义变量的初值,此时必须加上双冒号::。

1
2
integer(kind=4) :: a=4
complex(kind=4) :: c=(1.0,2.0)

Fortran 77 的语法还需要特制的 Data 命令才能批量地定义初值。可以在声明时对变量赋值并且定义为常量,视作声明内容的一部分,也可以直接写在声明的语句中。对常量进行的修改会导致编译报错。

1
2
3
4
5
6
! type 1
real pi
parameter(pi=3.1415926)

! type 2
real,parameter :: pi=3.1415926 ! 此时必须使用::表示声明和描述已完成

可以让两个同类型变量共享一个内存空间,可以视作别名,用于强调变量的具体含义或者简写某个数组的重要分量(读写会更快),也可以用于共享的临时变量命名,节约内存(没意义,不缺这点内存)。

1
2
integer a,b
equivalence(a,b)

类型转换

不同基本类型的变量之间支持类型转换,显式转换例如:

1
2
3
4
5
real :: a1,a2
integer :: b=1,c=2

a1 = b/c ! 0
a2 = real(b)/real(c) ! 0.5

Fortran 也支持自动的隐式转换,与 c 语言基本相同。不过这种隐式转换似乎只主动地发生在运算表达式中;在赋值时,1 和 1.0 是不同的,把 1 赋给一个实数可能有大问题!

预处理

Fortran 标准语法中,不包含预处理的部分,但是实际上 Fortran 可以使用#ifdef, #define 等与 c 语言完全相同的基本宏定义,并且可以调用 c 语言的预处理器进行处理。