欣赏一下下图这段 FORTRAN 77 时代的代码,语句中大量的 GOTO 是那个时代编程的典型特征。

这段代码的功能非常简单: 读入一个整数 MAXINT,然后输出从 2 到 MAXINT 之间的所有素数。
它采用的是最朴素的试除法:对于每个整数 N,从 2 到 N-1 依次尝试。

下面是完整代码,加了一行用户输入提示。

prime.f
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
C     PROGRAM TO COMPUTE PRIME NUMBERS
C
PROGRAM PRIME
INTEGER MAXINT, N, DIVSOR
INTEGER QUOT, PROD
WRITE (*,*) 'ENTER THE UPPER LIMIT:'
READ (*,100) MAXINT
N = 2
WRITE (*,150) MAXINT
5 IF (MAXINT-N) 200,10,10
10 DIVSOR = 2
15 IF ((N-1)-DIVSOR) 30,20,20
20 QUOT = INT(N/DIVSOR)
PROD = INT(QUOT*DIVSOR)
IF (N-PROD) 25,30,25
25 DIVSOR = DIVSOR + 1
GO TO 15
30 IF (DIVSOR-(N-1)) 40,40,35
35 WRITE (*,100) N
40 N = N + 1
GO TO 5
100 FORMAT (I5)
150 FORMAT ('THE PRIME NUMBERS FROM 2 TO ',I5,' ARE:')
200 STOP
END

可以用 gfortran 编译运行

1
gfortran prime.f -o prime

使用例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 ENTER THE UPPER LIMIT:
50
THE PRIME NUMBERS FROM 2 TO 50 ARE:
2
3
5
7
11
13
17
19
23
29
31
37
41
43
47

这段代码是典型的古典 FORTRAN 77 编程风格,采用的是传统的固定格式(fixed source form)。在固定格式中,每一列具有特定含义:

  • 第 1 列:C 或 * 表示注释行
  • 第 1~5 列:语句标签(支持 GOTO 跳转)
  • 第 6 列:续行标记
  • 第 7~72 列:Fortran 语句
  • 第 73~80 列:传统卡片序号,通常被忽略

现代 Fortran 语言更建议源码使用自由格式,两种格式需要区分,可以从文件后缀推断,例如 main..fmain.for 通常是固定格式,main.f90 通常是自由格式。
GNU Fortran 提供编译选项 -ffixed-form-ffree-form 来区分固定格式和自由格式,不提供时也会通过后缀推断。

值得注意的是,这段代码的条件语句使用的是算术 IF 语法,格式如下

1
IF (expr) label_negative, label_zero, label_positive

其中:

  • expr < 0 跳转到 label1
  • expr = 0 跳转到 label2
  • expr > 0 跳转到 label3

算术 IF 语法是基于 GOTO 跳转实现的控制语句,虽然直到现在,条件语句在底层 CPU 中仍然是用这种类似的方式实现的,但是高级编程语言都提供了更优雅的结构化语法,例如 C 语言中的 if-else 语句。

算术 IF 在 Fortran 90 中已经被列为过时特性,并在 Fortran 2018 中正式从标准中删除。不过 GNU Fortran、Intel Fortran 等编译器至今仍然支持它,较新版本的可能需要开启额外的兼容性选项。

计算素数表的逻辑其实主要是两层循环和一个整除判断,由于 FORTRAN 77 没有提供结构化的循环语句,循环逻辑还是通过 IF 和 GOTO 语句实现的。

下面给出与上述代码等价的使用现代 Fortran 语言的自由格式版本

prime.f90
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
program prime
implicit none

integer :: maxint
integer :: n
integer :: divisor
logical :: is_prime

write (*, '(A)', advance='no') 'Enter the upper limit: '
read (*, *) maxint

write (*, '(A,I0,A)') &
'The prime numbers from 2 to ', maxint, ' are:'

do n = 2, maxint
is_prime = .true.

do divisor = 2, n - 1
if (mod(n, divisor) == 0) then
is_prime = .false.
exit
end if
end do

if (is_prime) then
write (*, '(I0)') n
end if
end do

end program prime

其中几个值得注意的区别包括:

  • 使用小写而非全大写
  • 使用 ! 表示注释而非在第一列的 C
  • 禁止隐式的类型推断
  • 使用结构化的 doif 语法替代原本的算术 IF 和 GOTO 跳转