本节摘要:1957 年,IBM 团队发布 FORTRAN(Formula Translation),人类第一次用接近数学的语句而非汇编指挥机器。本节回到这个起点:固定格式是穿孔卡片的物理烙印,GOTO 与 COMMON 块是那个年代的合理选择,也是后来大型工程的主要债务来源。理解这些"历史债"是怎么产生的,才能在 F90 的分水岭之后读懂为什么模块和自由格式是革命。
今天的 Fortran 代码长什么样子,一半取决于设计者想让它变成什么,另一半取决于五十年前机器长什么样子。1957 年发布的 FORTRAN,目标只有一个:让物理学家不用写汇编。当时程序员把程序穿在 80 列的打孔卡片上,一卡一行,写完一摞卡送进读卡机。这套物理流程直接决定了语法的形态——如果今天你觉得 Fortran 的"固定格式"莫名其妙,那是你没见过读卡机。
理解这段历史不是怀旧。你迟早要面对一段 1970 年代写的模拟代码,而判断它该怎么读、该不该重构,前提就是知道它当初为什么长成那样。演化日志的第一页,从这里翻开。
FORTRAN IV 与 FORTRAN 66 时代,代码行被切成固定列区:第 1–5 列写语句标号,第 6 列写续行标记,真正的语句从第 7 列开始,且受限于 72 列。写代码像是戴着枷锁跳舞,但对当年只有几百字节内存、编译器还分不清词法的机器来说,这种规整反而降低了解析成本。
C FORTRAN 66 固定格式示例:计算前 N 个自然数的和 INTEGER N, I, SUM SUM = 0 DO 10 I = 1, N SUM = SUM + I 10 CONTINUE WRITE(*,*) 'SUM =', SUM STOP END
注意几个细节:注释以 C 开头占第一列,DO 10 是带标号的循环,循环体靠标号识别,标号必须写在 1–5 列。变量 N 没有显式声明——FORTRAN 66 有隐式类型规则,以 I 到 N 开头的变量默认是整数,其余默认是实数。这条"隐式类型"规则让无数拼错变量名的 bug 静默发生,直到 implicit none 出现才被堵住。
那个年代的另一件标志性发明是 COMMON 块:一段命名的内存区,多个程序单元可以按顺序声明各自的变量映射到同一块内存。它的效率极高——不拷贝,直接共享地址——但代价是全局状态失控。子程序 A 改了 COMMON 里的 X,子程序 B 再用 X 时毫无防备。更糟的是,如果两个单元对同一 COMMON 块的变量类型声明不一致,内存布局就错位,产生难以追踪的数值错误。规模小的时候这些问题能忍,等到气候模式、结构力学这类代码长到几十万行,维护成本就压垮了项目。
1990 年的 Fortran 90 是演化日志里最重的一页,它做了三件打破旧世界的事。第一件是自由格式:不再有列限制,空格自由,大小写不敏感,代码可以像 C 一样自然书写。第二件是模块(module):用 use 建立显式依赖,用 public/private 控制可见性,COMMON 块的全局共享有了替代品。第三件是整体数组运算:两个数组相加从三重循环变成一行 C = A + B。
! Fortran 90 自由格式示例:同一件事的现代写法 program sum_first implicit none integer :: n, i, s n = 100 s = 0 do i = 1, n s = s + i end do write(*,*) 'sum =', s ! 数组版本:n 个数求和只要一行,编译器自己展开 s = sum([(i, i = 1, n)]) write(*,*) 'array sum =', s end program sum_first
implicit none 出现在第一行:它废除了隐式类型规则,所有变量必须显式声明。这条约定今天已经是铁律,任何新代码缺少它都会被同行要求补上。整体数组操作的意义不在"少打字",而在语义:C = A + B 向编译器宣告"这是逐元素运算,无数据依赖",编译器据此放心生成 SIMD 指令。而 F77 里手写循环时,编译器往往不敢激进优化,因为循环体里可能有隐藏的别名访问。
F90 之后的标准不再推翻重来,而是在三条轴上加杠杆。Fortran 95 补上纯过程(pure)与 FORALL,为并行化铺路;2003 引入面向对象(类型扩展、多态、finalizer)与 ISO_C_BINDING,让 Fortran 能与 C 双向调用;2008 加入协同数组(coarray)与 DO CONCURRENT,把分布式内存并行写进语言;2018 完善子模块与错误处理,2023 草案则把重点放在泛型模板与更细的 IEEE 浮点控制上。
! 现代 Fortran 的特征片段:用模块封装一个精度常量 module constants_mod use iso_fortran_env, only: real64 implicit none real(real64), parameter :: gravity = 9.80665_real64 end module constants_mod program demo use constants_mod, only: gravity use iso_fortran_env, only: real64 implicit none real(real64) :: t, drop t = 1.0_real64 drop = 0.5_real64 * gravity * t * t ! 自由落体位移 write(*,*) 'fall distance after 1 s =', drop, 'm' end program demo
这段代码在 F77 时代一句都写不出来:kind 常量、模块、命名导入,全是后来的产物。但它依然读起来像数学,这是 Fortran 五十年没有丢的东西。

面对旧代码,先判断年代再决定策略:F77 及更早的代码,重点警惕隐式类型与 COMMON 块;F90–95 的代码,可以放心使用模块和数组语法;2003 之后的代码才有面向对象设施可谈。不要一上来就重写——数值代码重写最容易引入精度漂移,宁可包一层接口逐步替换。这个"先诊断、再包壳、后替换"的流程,第 8 章会完整展开。
阅读完本节,你应当能够:
implicit none 是进入现代 Fortran 的第一道门槛下一节我们把镜头从历史拉回设计:为什么现代 Fortran 坚持"数组优先",又为什么敢对编译器承诺"没有别名"。