cv.cm

编码 · 编程语言

Fortran 入门教程

阅读约 9 分钟

简短回答

Fortran 是一门为数值和科学计算而生的编译型静态类型语言。现代 Fortran(Fortran 90 及之后的标准)具有自由格式源码、模块、动态数组和强大的整个数组运算,编译器能把它转换成非常快的机器码。安装免费的 gfortran 编译器,把代码保存在 .f90 文件中,用 gfortran hello.f90 -o hello 构建。

Fortran 是什么,谁在用

Fortran(“公式翻译”,formula translation)由 John Backus 领导的团队在 20 世纪 50 年代于 IBM 开发,是仍在积极使用的最古老的编程语言之一。它由 ISO 标准化,主要修订版本有 Fortran 90、95、2003、2008、2018 和 2023。旧的固定格式风格(大写的 FORTRAN 77,代码从第 7 列开始)仍然可以编译,但新代码使用这里展示的自由格式语法。

Fortran 用于大量数值计算的场景:天气和气候模型、计算流体力学、物理和化学模拟,以及许多在超级计算机上运行的程序。NumPy 和 MATLAB 等工具底层使用的 BLAS 与 LAPACK 线性代数库,其参考实现就是用 Fortran 写的。

安装编译器并运行 Hello, world

gfortran 是 GCC 的一部分,免费,可用于 Linux(软件包 gfortran)、macOS(Homebrew 的 gcc)和 Windows(MSYS2 或 WSL)。Intel 的 ifx 和 LLVM Flang 是其他编译器。Fortran 包管理器(fpm)可以创建和构建项目。

program hello
  implicit none
  print *, "Hello, world!"
end program hello
gfortran hello.f90 -o hello
./hello
#  Hello, world!

print * 使用列表导向输出,由系统为你选择间距(注意开头的空格)。Fortran 不区分大小写:PRINT、Print 和 print 是一样的。

变量与基本类型

所有声明都放在程序单元的开头,在可执行语句之前。务必写上 implicit none:没有它,以 I 到 N 开头的未声明名字会被悄悄当作整数,其余的则是实数。

类型声明示例说明
integerinteger :: count = 3通常为 32 位;64 位用 integer(int64)
realreal :: x通常是单精度
real(real64)real(real64) :: y双精度,科学计算的常用选择
complexcomplex :: z = (1.0, 2.0)内置复数
logicallogical :: done = .false..true. / .false.,运算符 .and.、.or.、.not.
charactercharacter(len=20) :: name定长字符串,用空格填充
program types
  use, intrinsic :: iso_fortran_env, only: int64, real64
  implicit none
  integer :: count = 3
  integer(int64) :: big = 9000000000_int64
  real(real64) :: third
  logical :: done = .false.
  character(len=:), allocatable :: name

  third = 1.0_real64 / 3.0_real64
  name = "Ana"
  print *, count, big, third, done, name
end program types

后缀 _real64 设置字面量的 kind(精度)。没有它,1.0 是单精度常量,赋给双精度变量时会丢失有效数字。

控制流

program control
  implicit none
  integer :: i, total, score

  total = 0
  do i = 1, 10
    if (mod(i, 2) == 0) cycle   ! skip even numbers
    total = total + i
  end do
  print *, "sum of odd numbers:", total   ! 25

  score = 85
  if (score >= 90) then
    print *, "A"
  else if (score >= 80) then
    print *, "B"
  else
    print *, "C"
  end if

  select case (score)
  case (90:)
    print *, "excellent"
  case (80:89)
    print *, "good"
  case default
    print *, "keep going"
  end select
end program control

do i = 1, 10 包含两端,可选的第三个值设置步长(do i = 10, 1, -1)。cycle 跳到下一次迭代,exit 离开循环。也可以使用 do while (condition),以及带 exit 的普通 do。注释以 ! 开头。

函数、子程序与模块

Fortran 有两种过程:函数返回一个值,用在表达式中;子程序用 call 调用,通过参数返回结果。请把过程放进模块,这样编译器就能检查每一次调用。

module geometry
  implicit none
contains
  function circle_area(r) result(a)
    real, intent(in) :: r
    real :: a
    a = 3.14159 * r**2
  end function circle_area

  subroutine swap(a, b)
    integer, intent(inout) :: a, b
    integer :: tmp
    tmp = a
    a = b
    b = tmp
  end subroutine swap
end module geometry

program demo
  use geometry
  implicit none
  integer :: x, y
  x = 1
  y = 2
  print *, circle_area(2.0)   ! about 12.566
  call swap(x, y)
  print *, x, y               ! 2 1
end program demo

参数按引用传递,所以过程可以改变调用者的变量。intent(in)、intent(out) 和 intent(inout) 说明并强制规定每个参数能做什么。** 是乘方运算符。

数组:Fortran 的标志性特性

数组是 Fortran 的核心。索引默认从 1 开始(允许任意下界),算术运算无需循环就能作用于整个数组,切片使用 a(start:end),许多内置函数可以直接对数组操作。

program arrays
  implicit none
  integer :: i
  real :: a(5), b(5)
  real, allocatable :: c(:)
  integer :: m(2, 3)

  a = [(real(i), i = 1, 5)]   ! 1.0 2.0 3.0 4.0 5.0
  b = 2.0 * a + 1.0           ! whole-array arithmetic
  print *, b(2:4)             ! 5.0 7.0 9.0
  print *, sum(a), maxval(b)  ! 15.0 11.0

  allocate(c(10))
  c = 0.0
  print *, size(c)            ! 10
  deallocate(c)

  m = reshape([1, 2, 3, 4, 5, 6], [2, 3])
  print *, m(2, 1)            ! 2 (column-major order)
end program arrays

多维数组按列优先顺序存储:第一个索引在内存中变化最快,与 C 相反。为了速度,让最内层循环遍历第一个索引。其他有用的内置函数有 matmul、dot_product、transpose、minval 和 count。现代 Fortran 还提供 do concurrent 循环和 coarray,用于并行程序。

错误处理

Fortran 没有异常。可能失败的语句接受一个状态参数:输入输出用 iostat,allocate 用 stat。检查它的值并做出反应;如果省略,出错时程序会终止。error stop 以错误结束程序。

program parse
  implicit none
  character(len=20) :: text
  integer :: n, ios

  text = "abc"
  read(text, *, iostat=ios) n
  if (ios /= 0) then
    print *, "not a number: ", trim(text)
  else
    print *, "n =", n
  end if
end program parse

像上面这样从字符变量中读取,称为内部读取,是把字符串转换为数字的常用方式。

一个完整的小程序:平均值与标准差

module stats
  implicit none
  integer, parameter :: dp = kind(1.0d0)
contains
  pure function mean(x) result(m)
    real(dp), intent(in) :: x(:)
    real(dp) :: m
    m = sum(x) / size(x)
  end function mean

  pure function std_dev(x) result(s)
    real(dp), intent(in) :: x(:)
    real(dp) :: s
    s = sqrt(sum((x - mean(x))**2) / (size(x) - 1))
  end function std_dev
end module stats

program main
  use stats
  implicit none
  real(dp) :: values(5)
  values = [2.0_dp, 4.0_dp, 4.0_dp, 5.0_dp, 7.0_dp]
  print '(a, f6.3)', "mean    = ", mean(values)
  print '(a, f6.3)', "std dev = ", std_dev(values)
end program main
! mean    =  4.400
! std dev =  1.817

x(:) 声明是假定形状数组,所以这些函数可以接受任意长度的数组。pure 保证函数没有副作用。格式 '(a, f6.3)' 打印一个字符串,后面跟一个宽 6 个字符、保留 3 位小数的实数。

接下来读什么

  • fortran-lang.org:Learn 部分有快速入门教程和最佳实践。
  • GNU Fortran(gfortran)手册,介绍编译器选项和内置过程。
  • 试试 Fortran 包管理器(fpm),用它创建项目并使用 Fortran 标准库(stdlib)等库。
  • 学习时带上警告和运行时检查来编译,例如 gfortran -Wall -fcheck=all。

常见问题

Fortran 现在还有人用吗?

有。Fortran 在科学计算和高性能计算中仍被广泛使用,尤其是气候、天气、物理和工程模拟,语言标准也仍在更新。

.f 和 .f90 文件有什么区别?

按照惯例,.f 和 .for 文件使用 FORTRAN 77 的旧固定格式,列位置有意义,而 .f90 文件使用自由格式源码。.f90 扩展名用于任何现代标准的自由格式代码,而不仅限于 Fortran 90。

Fortran 为什么快?

这门语言就是为数值工作而设计的:数组是内置的,别名规则让编译器可以大胆优化,而且几十年的编译器工作都针对它。写得好的 C 或 C++ 也能达到相近的速度。

Fortran 数组从 0 还是 1 开始?

默认从 1 开始,但你可以选择任意下界,例如 real :: a(0:9) 声明了十个元素,索引为 0 到 9。

全部编码教程