Skip to content

Fortran 速查表

科学和数值计算的先驱语言。

01

基础与程序结构

程序结构与 Hello World

每个 Fortran 程序以 'program NAME' 开头,以 'end program NAME' 结束。'implicit none' 在现代 Fortran 中是强制性的——它要求显式声明所有变量(如果不使用,Fortran 会采用隐式类型规则,以 i-n 开头的变量为整数,其他为实数,这是 bug 的主要来源)。'contains' 块将可执行代码与内部过程(在程序内部定义的子例程/函数)分开。注释以 '!' 开头。自由源码格式(Fortran 90+)使用 .f90 扩展名;列位置无关紧要。使用 gfortran/ifort 编译。

fortran
program hello
  ! A complete Fortran program structure
  implicit none
  ! declarations go here
  integer :: status = 0

  print *, "Hello, World!"   ! list-directed output to stdout

  ! executable statements
  call do_work(status)

  print *, "Exit status: ", status
contains
  subroutine do_work(st)
    integer, intent(out) :: st
    st = 0
    print *, "Working..."
  end subroutine do_work
end program hello

! Compile: gfortran hello.f90 -o hello
! Run:     ./hello

变量与内建类型

Fortran 有 5 种内建类型:integer、real、complex、character、logical。'kind' 选择精度/大小——使用 kind=8 表示 64 位(或更好,使用 selected_real_kind/iso_fortran_env 以提高可移植性)。实数字面量需要 kind 后缀:3.14_8(不能只写 3.14)。Double precision 是 real(kind=8) 的旧语法。复数字面量使用 (real, imag) 形式。逻辑值是 .true. / .false.(带点)。字符串有固定的 'len',除非声明为 len=: 和 allocatable(延迟长度,Fortran 2003+)。始终使用匹配的 kind 后缀初始化,以避免静默精度损失。

fortran
program variables
  implicit none
  ! Integer types
  integer :: count = 0
  integer(kind=8) :: big = 9223372036854775807_8   ! 64-bit
  ! Real types
  real :: x = 3.14              ! default (often 32-bit)
  real(kind=8) :: y = 2.718281828459045_8   ! double precision
  double precision :: z = 1.0d0
  ! Complex
  complex :: c = (1.0, 2.0)    ! 1 + 2i
  complex(kind=8) :: cw = (1.0_8, 2.0_8)
  ! Character
  character(len=20) :: name = "Alice"
  character(len=:), allocatable :: flexible   ! deferred length
  ! Logical
  logical :: flag = .true.
  ! Print all
  print *, count, big
  print *, x, y, z
  print *, c, cw
  print *, name, flag
end program variables

常量与参数

常量使用 'parameter' 属性,必须在声明时初始化。它们不能被修改——编译器可以优化和内联它们。按约定使用 SCREAMING_SNAKE_CASE 命名。character(*) 表示'从初始化器获取长度'(对字符串常量很方便)。参数通常用于数组大小、物理常数和类似枚举的整数代码。Fortran 2003+ 也有正式的 ENUM 类型,但 parameter 整数仍然是惯用选择。参数可用于数组维度声明和其他常量表达式上下文中。

fortran
program constants
  implicit none
  ! Named constants via 'parameter' attribute
  integer, parameter :: MAX_SIZE = 100
  real, parameter :: PI = 3.14159265
  real, parameter :: E = 2.718281828
  character(*), parameter :: APP_NAME = "MyApp"
  ! Using parameters
  real :: arr(MAX_SIZE)
  arr = 0.0
  print *, APP_NAME, " size=", MAX_SIZE
  print *, "Circumference: ", 2.0 * PI * 5.0

  ! Enum-like via parameter
  integer, parameter :: SUNDAY = 1, MONDAY = 2, TUESDAY = 3
  integer :: day = MONDAY
  print *, "Day code: ", day
end program constants

运算符与表达式

Fortran 运算符:算术(+ - * / **),其中 ** 表示幂运算(Fortran 特有)。整数除法向零截断——使用 real(a)/b 进行真除法。两种关系运算语法:现代(< > == /= <= >=)和旧式(.lt. .gt. .eq. .ne. .le. .ge.)。逻辑运算:.and. .or. .not. .eqv.(等价).neqv.(异或)。字符串连接使用 //;trim() 移除尾部空格(Fortran 用空格填充定长字符串)。mod 与 modulo:mod 遵循截断除法的符号,modulo 遵循向下取整除法——对负操作数结果不同。

fortran
program operators
  implicit none
  integer :: a = 17, b = 5
  real :: x = 2.0
  ! Arithmetic
  print *, a + b, a - b, a * b    ! 22 12 85
  print *, a / b                   ! 3 (integer division!)
  print *, real(a) / b             ! 3.4 (cast to real)
  print *, a ** 2                  ! 289 (exponentiation)
  print *, mod(a, b)               ! 2 (modulo)
  print *, modulo(a, b)            ! 2 (differs for negatives)
  ! Relational (both forms work)
  print *, a > b, a < b            ! T F
  print *, a .gt. b, a .lt. b      ! T F (old form)
  print *, a == b, a /= b          ! F T
  ! Logical
  print *, (a > 0) .and. (b > 0)   ! T
  print *, (a > 0) .or. (b < 0)    ! T
  print *, .not. (a > 0)           ! F
  print *, (a > 0) .eqv. (b > 0)   ! T (equivalence)
  ! String concatenation
  character(10) :: s1 = "Hello", s2 = "World"
  print *, trim(s1) // " " // trim(s2)   ! Hello World
end program operators

内建函数与数学

Fortran 拥有丰富的内建(内置)函数。数学:abs、sqrt、exp、log(自然对数)、log10、sin/cos/tan/asin/acos/atan/atan2、sinh/cosh/tanh。取整:int(截断)、nint(最近)、floor、ceiling。转换:real()、int()、cmplx()。查询:size、shape、huge(最大值)、tiny(最小正值)、kind。所有三角函数使用弧度。atan2(y, x) 返回正确象限的角度(与 atan 不同)。使用 huge/tiny 检查范围限制。内建函数是元素级的——它们自动按元素作用于数组。

fortran
program intrinsics
  implicit none
  real :: x = -3.7, y = 2.5
  ! Math functions
  print *, abs(x)         ! 3.7
  print *, sqrt(2.0)      ! 1.414...
  print *, exp(1.0)       ! 2.718... (e^x)
  print *, log(2.0)       ! 0.693... (natural log)
  print *, log10(1000.0)  ! 3.0
  print *, sin(3.14159/2) ! 1.0
  print *, cos(0.0)       ! 1.0
  print *, atan2(1.0,1.0) ! 0.785... (pi/4)
  ! Rounding
  print *, int(x)         ! -3 (truncate toward zero)
  print *, nint(x)        ! -4 (nearest integer)
  print *, floor(x)       ! -4 (toward -inf)
  print *, ceiling(x)     ! -3 (toward +inf)
  print *, abs(x), max(x, y), min(x, y)   ! 3.7 2.5 -3.7
  ! Type conversion
  print *, real(5)        ! 5.0
  print *, int(3.9)       ! 3
  ! Inquiry
  real :: arr(10)
  print *, size(arr)      ! 10
  print *, huge(1)        ! 2147483647
  print *, tiny(1.0)      ! smallest positive real
end program intrinsics
02

控制流

If...Then...Else

块 IF:'if (cond) then ... else if (cond) then ... else ... end if'。每个分支需要 'then'(最后的 else 除外)。逻辑 IF 是单行形式:'if (cond) statement'(没有 'then'/'end if')。条件使用关系运算符(< > == /= <= >= 或 .lt. .gt. .eq. .ne. .le. .ge.)与 .and. .or. .not. 组合。'stop' 终止程序(可选带消息/代码)。算术 IF(if (x) label1, label2, label3)在 Fortran 2018 中已被删除——永远不要使用它。始终使用 'implicit none' 以捕获未声明的变量。

fortran
program if_demo
  implicit none
  integer :: score = 85
  character(1) :: grade

  ! Multi-branch if/else if/else
  if (score >= 90) then
    grade = 'A'
  else if (score >= 80) then
    grade = 'B'
  else if (score >= 70) then
    grade = 'C'
  else if (score >= 60) then
    grade = 'D'
  else
    grade = 'F'
  end if

  print *, "Score ", score, " -> Grade ", grade

  ! Logical if (single statement, no 'then')
  if (score < 0 .or. score > 100) stop "Invalid score"

  ! Arithmetic if (OBSOLETE - avoid)
  ! if (x) 10, 20, 30   ! jump to label based on sign
end program if_demo

Select Case(Switch)

select case 是 Fortran 的 switch 语句。case 可以是单个值(case (3))、列表(case (1, 3, 5))或范围(case (4:5) 表示 4 到 5,含端点)。case default 是回退分支。与 C 不同,没有 fall-through——每个分支独立,只运行一个。适用于 integer、character 和 logical 类型(不适用于 real)。字符范围使用 ASCII 排序('A':'Z')。对于浮点数比较,使用 if/else。对于整数/字符分派,select case 比长 if/else if 链更高效(编译器可能使用跳转表)。

fortran
program case_demo
  implicit none
  integer :: day = 3
  character(1) :: ch = 'A'
  character(10) :: day_name

  ! Integer select case
  select case (day)
  case (1)
    day_name = "Monday"
  case (2)
    day_name = "Tuesday"
  case (3)
    day_name = "Wednesday"
  case (4:5)
    day_name = "Thu/Fri"
  case (6:7)
    day_name = "Weekend"
  case default
    day_name = "Invalid"
  end select
  print *, day_name

  ! Character select case (case-insensitive via pre-upper)
  select case (ch)
  case ('A':'Z')
    print *, "Uppercase letter"
  case ('a':'z')
    print *, "Lowercase letter"
  case ('0':'9')
    print *, "Digit"
  case default
    print *, "Other"
  end select

  ! Logical select case
  select case (day > 5)
  case (.true.)
    print *, "Weekend!"
  case (.false.)
    print *, "Weekday"
  end select
end program case_demo

Do 循环(计数)

计数 DO 循环:'do var = start, end, step'(step 默认为 1)。循环在 var <= end(正步长)或 var >= end(负步长)时运行。var 在每次迭代后递增。隐式 do 构造 [(expr, var=start,end)] 对数组初始化和 I/O 列表很强大。命名循环(outer: do ... end do outer)允许将 cycle/exit 指向特定的嵌套层级。循环变量自动定义;在 Fortran 中,循环结束后它保留最终值。避免在循环体内修改循环变量。

fortran
program do_loops
  implicit none
  integer :: i, j, total

  ! Basic counted loop: do var = start, end [, step]
  do i = 1, 5
    print *, i          ! 1 2 3 4 5
  end do

  ! With step
  do i = 10, 1, -1      ! countdown
    print *, i
  end do

  do i = 0, 100, 25     ! 0 25 50 75 100
    print *, i
  end do

  ! Implied-do (inline, for array init / I/O)
  integer :: arr(5) = [(i**2, i=1,5)]   ! 1 4 9 16 25
  print *, arr
  print *, (i, i=1,3)                   ! 1 2 3

  ! Nested loops with labels (for cycle/exit targeting)
  total = 0
  outer: do i = 1, 3
    inner: do j = 1, 3
      total = total + i*j
    end do inner
  end do outer
  print *, "Total: ", total
end program do_loops

Do While 与无限循环

do while (cond) ... end do 是前置测试循环(每次迭代前检查条件;可能运行零次)。对于后置测试行为,使用 do ... if (cond) exit ... end do。裸 'do ... end do' 是无限循环——必须有 exit 语句(否则无限循环)。'exit' 退出最内层循环(或命名循环)。命名循环(factorial_loop:)让 exit 可以指向外层循环。当迭代次数未知且取决于条件时使用 do while;当次数预先知道时使用计数 do。

fortran
program while_demo
  implicit none
  integer :: n, count
  real :: x, sum

  ! do while: pre-test loop
  n = 1024
  count = 0
  do while (n > 1)
    n = n / 2
    count = count + 1
  end do
  print *, "log2(1024) = ", count   ! 10

  ! Infinite loop with exit
  sum = 0.0
  do
    read(*, *) x
    if (x < 0) exit          ! leave loop
    sum = sum + x
  end do
  print *, "Sum: ", sum

  ! do ... end do with conditional exit
  n = 1
  factorial_loop: do
    if (n > 10) exit factorial_loop
    print *, n, factorial(n)
    n = n + 1
  end do factorial_loop

contains
  recursive function factorial(n) result(f)
    integer, intent(in) :: n
    integer :: f
    if (n <= 1) then
      f = 1
    else
      f = n * factorial(n-1)
    end if
  end function factorial
end program while_demo

Cycle、Exit 与循环控制

cycle 跳过当前迭代的剩余部分并跳到下一次迭代(类似 C/Python 中的 'continue')。exit 完全跳出循环(类似 'break')。两者默认针对最内层循环,但使用命名循环(search: do ... end do search)可以指向外层循环:'exit search' 或 'cycle search'。这对于干净地跳出嵌套循环至关重要。使用 cycle 进行过滤(跳过不需要的迭代),使用 exit 进行提前终止(找到搜索结果、检测到错误)。命名循环使嵌套控制流明确且可读。

fortran
program loop_control
  implicit none
  integer :: i, j

  ! cycle: skip to next iteration (like 'continue' in C)
  do i = 1, 10
    if (mod(i, 2) == 0) cycle   ! skip even numbers
    print *, i                  ! 1 3 5 7 9
  end do

  ! exit: break out of loop (like 'break' in C)
  do i = 1, 100
    if (i * i > 50) then
      print *, "Stopped at i=", i
      exit
    end if
  end do

  ! Named loops: cycle/exit can target outer loops
  search: do i = 1, 5
    do j = 1, 5
      if (i + j == 7) then
        print *, "Found: ", i, "+", j, "= 7"
        exit search             ! break out of OUTER loop
      end if
    end do
  end do search

  ! Early exit from a search
  integer :: arr(10) = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
  do i = 1, size(arr)
    if (arr(i) == 9) then
      print *, "Found 9 at index ", i
      exit
    end if
  end do
end program loop_control
03

数组与向量运算

数组声明与初始化

Fortran 数组默认从 1 开始索引(下界 = 1),但可以指定自定义下界:a(0:4) 的索引为 0..4。多维数组使用 (rows, cols) 顺序——列主序存储(第一个索引在内存中变化最快)。使用数组构造器 [1,2,3] 或隐式 do [(expr, i=start,end)] 初始化。reshape 从 1D 列表填充多维数组。size() 返回总元素数;lbound/ubound 返回下/上界。shape() 以 1D 数组形式返回形状。数组是'整体数组'——可以无需显式循环就进行赋值和操作。

fortran
program array_decl
  implicit none
  ! Declaration with dimension
  integer :: a(5)                  ! 1D, indices 1..5
  integer :: b(0:4)                ! 1D, indices 0..4 (custom lower bound)
  real :: c(3, 4)                  ! 2D, 3 rows x 4 cols
  real, dimension(10) :: d         ! using dimension attribute

  ! Initialization at declaration
  integer :: x(5) = [1, 2, 3, 4, 5]
  integer :: y(5) = [(i*2, i=1,5)] ! implied-do: 2 4 6 8 10
  integer :: z(5) = 0              ! all zeros
  real :: m(2,2) = reshape([1,2,3,4], [2,2])

  ! Allocation later
  print *, size(x), lbound(x), ubound(x)   ! 5 1 5
  print *, size(c, dim=1)                  ! 3 (rows)
  print *, size(c, dim=2)                  ! 4 (cols)

  ! Array of characters
  character(10) :: names(3) = ["Alice", "Bob", "Carol"]
  print *, names(2)                        ! Bob
end program array_decl

数组片段与向量下标

数组片段(切片)使用 a(start:end:stride) ��络语法——所有部分可选。步长可以为负(反向)。向量下标允许使用索引数组进行聚集/分散:a(idx) 返回 [a(idx(1)), a(idx(2)), ...]。片段可以赋值:a(2:4) = [99,98,97]。'where' 构造是数组级条件赋值(类似 numpy 的 where)。Fortran 的数组操作是向量化的——元素级运算无需显式循环。这是 Fortran 用于数值代码的杀手锏:干净、类似数学的语法,编译器自动向量化。

fortran
program array_sections
  implicit none
  integer :: a(10) = [(i, i=1,10)]
  integer :: b(5)
  integer :: idx(3) = [2, 5, 7]

  ! Array sections (slicing): a(start:end[:stride])
  print *, a(3:7)         ! 3 4 5 6 7
  print *, a(1:10:2)      ! 1 3 5 7 9 (stride 2)
  print *, a(:5)          ! 1 2 3 4 5 (start defaults to 1)
  print *, a(6:)          ! 6 7 8 9 10 (end defaults to ubound)
  print *, a(::3)         ! 1 4 7 10
  print *, a(10:1:-1)     ! 10 9 8 ... 1 (reverse)

  ! Vector subscript (gather)
  b = a(idx)              ! b = [a(2), a(5), a(7)] = [2, 5, 7]
  print *, b

  ! Assigning to a section
  a(2:4) = [99, 98, 97]
  print *, a(1:5)         ! 1 99 98 97 5

  ! 2D sections
  real :: m(4,4) = 0.0
  m(2:3, 2:3) = 1.0       ! set 2x2 sub-block
  print *, m(2,2), m(3,3) ! 1.0 1.0

  ! Where construct (masked array assignment)
  where (a > 50) a = 0    ! set elements > 50 to 0
end program array_sections

多维数组与矩阵

Fortran 以列主序存储数组(第一个索引在内存中变化最快)——与 C 相反。reshape 以列主序填充,所以 reshape([1,2,3,4],[2,2]) 得到 [[1,3],[2,4]]。matmul(A,B) 是真正的矩阵乘法(线性代数);A*B 是元素级(Hadamard)——它们不相同!transpose(A) 返回转置。归约:sum、product、maxval、minval、maxloc、minloc、count——都支持 dim= 沿一个轴归约。为获得高性能,以列主序编写循环(最内层循环遍历第一个索引)以对缓存友好。

fortran
program matrices
  implicit none
  real :: A(3,3), B(3,3), C(3,3)
  integer :: i, j

  ! Initialize with implied-do
  A = reshape([(real(i), i=1,9)], [3,3])
  ! A = 1 4 7
  !     2 5 8
  !     3 6 9   (column-major fill!)

  ! Identity matrix
  B = 0.0
  do i = 1, 3
    B(i,i) = 1.0
  end do

  ! Matrix multiplication (intrinsic)
  C = matmul(A, B)        ! A * I = A
  print *, C(1,1), C(2,2) ! 1.0 5.0

  ! Transpose
  print *, transpose(A)(1,:)   ! 1 2 3 (first row of A^T)

  ! Element-wise operations
  C = A + B               ! element-wise add
  C = A * 2.0             ! scalar multiply
  C = A * B               ! element-wise (NOT matmul!)

  ! Array reduction along a dimension
  print *, sum(A, dim=1)   ! column sums: 6 15 24
  print *, sum(A, dim=2)   ! row sums: 12 15 18
  print *, maxval(A)       ! 9.0
  print *, maxloc(A)       ! 3 3 (location of max)

  ! Reshape
  integer :: flat(6) = [1,2,3,4,5,6]
  integer :: mat(2,3)
  mat = reshape(flat, [2,3])
end program matrices

可分配数组(动态)

allocatable 数组是 Fortran 中做动态内存的现代方式——比指针更安全(无内存泄漏,作用域退出时自动释放)。使用 allocatable 属性和延迟形状(:、(:,:) 等)声明。allocate() 使用 stat= 捕获错误(始终检查!)。deallocate() 显式释放。Fortran 2003+ 支持赋值时自动重新分配:flex = [flex, 4] 扩展数组。allocated() 检查当前是否已分配。对于动态数组,allocatable 优于指针,因为编译器会跟踪并自动释放——无泄漏,无悬空指针。

fortran
program alloc_demo
  implicit none
  integer, allocatable :: arr(:), matrix(:,:)
  integer :: n, m, i, stat

  ! Get size from user
  print *, "Enter size:"
  read(*, *) n
  m = n * 2

  ! Allocate
  allocate(arr(n), matrix(n, m), stat=stat)
  if (stat /= 0) then
    print *, "Allocation failed!"
    stop 1
  end if

  ! Use the arrays
  arr = [(i, i=1, n)]
  matrix = 0.0
  do i = 1, n
    matrix(i, :) = i
  end do

  print *, size(arr), size(matrix, dim=2)
  print *, allocated(arr)   ! T

  ! Deallocate (or let it auto-deallocate at scope exit)
  deallocate(arr, matrix)
  print *, allocated(arr)   ! F

  ! Automatic reallocation on assignment (Fortran 2003)
  integer, allocatable :: flex(:)
  flex = [1, 2, 3]          ! auto-allocates to size 3
  flex = [flex, 4, 5]       ! reallocates to size 5: 1 2 3 4 5
  print *, flex
  deallocate(flex)
end program alloc_demo

数组内建函数

Fortran 的数组内建函数是其超能力。归约:sum、product、maxval、minval、maxloc(最大值索引)、minloc、count(true 的数量)、any(存在)、all(全部)。都支持 mask= 进行条件归约。pack() 收集 mask 为 true 的元素(类似 numpy 的 compress);unpack() 分散。cshift/eoshift 旋转数组(循环 vs 端点移出)。merge(a, b, mask) 进行元素级选择。注意:Fortran 没有内建排序——必须自己写(或使用库)。这些内建函数是元素级的且可向量化,使 Fortran 代码既干净又快速。

fortran
program array_funcs
  implicit none
  integer :: a(5) = [3, 1, 4, 1, 5, 9, 2, 6]
  ! Wait, let's fix size
  integer :: b(8) = [3, 1, 4, 1, 5, 9, 2, 6]
  integer :: c(5) = [10, 20, 30, 40, 50]
  logical :: mask(5) = [.true., .false., .true., .false., .true.]

  ! Inquiry
  print *, size(b)        ! 8
  print *, shape(b)       ! 8
  print *, lbound(b), ubound(b)   ! 1 8

  ! Reductions
  print *, sum(b)         ! 31
  print *, product(c)     ! 120000000
  print *, maxval(b), minval(b)   ! 9 1
  print *, maxloc(b)      ! 6 (index of max)
  print *, minloc(b)      ! 2 (index of min, first occurrence)
  print *, count(b > 3)   ! 5 (number of true elements)
  print *, any(b > 8)     ! T (at least one)
  print *, all(b > 0)     ! T (all of them)

  ! With mask
  print *, sum(b, mask=b > 3)     ! sum of elements > 3
  print *, pack(b, b > 3)         ! compact array of elements > 3
  print *, unpack([1,2], mask, 0) ! spread values per mask

  ! Manipulation
  print *, cshift(b, 2)   ! circular shift left by 2
  print *, eoshift(b, 2)  ! end-off shift left by 2 (fills 0)
  print *, merge(b, c, mask)  ! element-wise: b where mask true, else c

  ! Sorting (Fortran 2003+)
  integer :: sorted(8)
  sorted = b
  call sort_array(sorted)   ! custom sort (no built-in sort)
end program array_funcs
04

字符串与字符处理

字符声明与长度

Fortran 字符串默认是定长的——较短的字符串用空格填充以填满声明的长度。character(N) 或 character(len=N) 声明长度为 N。character(*) 从上下文获取长度(parameter 初始化器或虚参)。character(:), allocatable 启用延迟长度动态字符串(Fortran 2003+)——字符串在赋值时重新分配。len() 返回声明长度;len_trim() 返回不含尾部空格的长度。trim() 返回不含尾部空格的字符串(但结果在上下文中仍是定长)。对于变长文本处理,使用 allocatable 延迟长度字符串。

fortran
program char_decl
  implicit none
  ! Fixed-length strings
  character(10) :: s1 = "Hello"
  character(len=20) :: s2 = "World"
  character(20) :: s3          ! len= keyword optional

  ! Deferred-length (allocatable) - Fortran 2003+
  character(:), allocatable :: flex
  flex = "Dynamic"             ! len=7
  flex = "Now longer string"   ! reallocates to len=18
  print *, len(flex)           ! 18

  ! Array of strings
  character(15) :: names(3) = ["Alice", "Bob", "Carol"]

  ! Single character
  character(1) :: ch = 'A'
  character :: ch2 = 'B'       ! len=1 default

  print *, s1                  ! "Hello     " (padded to 10)
  print *, trim(s1)            ! "Hello" (no padding)
  print *, len(s1), len_trim(s1)   ! 10 5
  print *, names(2)            ! "Bob" (padded)
end program char_decl

字符串连接与操作

字符串连接使用 // 运算符。repeat(s, n) 将字符串重复 n 次。子字符串使用 s(start:end)——从 1 开始索引,两端都包含(与 Python 不同)。s(8:) 表示从位置 8 到末尾;s(:5) 表示从开头到位置 5。index(s, sub) 返回 sub 第一次出现的位置(未找到为 0,区分大小写)。scan(s, set) 返回集合中第一个字符的位置;verify(s, set) 返回不在集合中的第一个字符的位置。adjustl/adjustr 移动前导/尾部空格。Fortran 字符串不像 C 那样以 null 结尾——长度单独跟踪。

fortran
program string_ops
  implicit none
  character(20) :: first = "John", last = "Doe"
  character(50) :: full

  ! Concatenation with //
  full = first // " " // last   ! "John Doe"
  print *, trim(full)

  ! Repeat
  print *, repeat("-", 30)      ! 30 dashes

  ! Substring (1-indexed, inclusive)
  character(20) :: s = "Hello, World!"
  print *, s(1:5)               ! "Hello"
  print *, s(8:12)              ! "World"
  print *, s(8:)                ! "World!"  (to end)
  print *, s(:5)                ! "Hello"   (from start)

  ! Index (find substring)
  print *, index(s, "World")    ! 8 (position, 0 if not found)
  print *, index(s, "world")    ! 0 (case-sensitive)
  print *, scan(s, "aeiou")     ! 2 (first vowel position)
  print *, verify(s, "abcdefg") ! 1 (first char NOT in set)

  ! Length
  print *, len_trim(s)          ! 13
  print *, adjustl(s)           ! left-justify
  print *, adjustr(s)           ! right-justify
end program string_ops

内建字符串函数

iachar(c) 返回字符的 ASCII 码;achar(i) 是逆操作。(ichar/char 依赖于处理器——为可移植性优先使用 iachar/achar。)Fortran 没有内建大小写转换——手动使用 iachar/achar(A-Z 是 65-90,a-z 是 97-122,差值是 32)。字典序比较:lge/lgt/lle/llt(字典序大于/小于)能优雅处理不同长度的字符串。内部 I/O(读/写到字符串而不是文件)是在字符串和数字之间转换的惯用方式:read(str, *) num 和 write(str, fmt) num。I0 格式给出最小宽度整数(无填充)。

fortran
program string_funcs
  implicit none
  character(20) :: s = "Hello World"
  integer :: i
  character(1) :: ch

  ! Character code conversion
  print *, iachar('A')          ! 65 (ASCII code)
  print *, achar(66)            ! 'B' (from ASCII code)
  print *, ichar('A')           ! processor-dependent (use iachar for ASCII)

  ! Case conversion (manual - no built-in)
  do i = 1, len_trim(s)
    ch = s(i:i)
    if (ch >= 'A' .and. ch <= 'Z') then
      s(i:i) = achar(iachar(ch) + 32)   ! to lowercase
    end if
  end do
  print *, s                    ! "hello world"

  ! Comparison
  print *, lge("apple", "banana")   ! F (lexicographic >=)
  print *, lgt("zebra", "apple")    ! T
  print *, lle("abc", "abcd")       ! T (<=)
  print *, llt("abc", "abd")        ! T (<)

  ! String to number conversion (internal read)
  character(20) :: num_str = "3.14159"
  real :: pi_val
  read(num_str, *) pi_val
  print *, pi_val * 2           ! 6.28318

  ! Number to string (internal write)
  character(20) :: out_str
  integer :: n = 42
  write(out_str, '(I0)') n      ! I0 = minimal-width integer
  print *, "Number: " // trim(out_str)
end program string_funcs

字符串格式化

格式规范位于字符串中:'(I5, F8.2, A)'。I=整数,F=定点实数,E=指数,ES=科学记数法(尾数 1-10),A=字符,X=空格,/=换行。宽度在前(I5 = 宽度 5),然后是可选的 .m 表示最小位数(I5.3)。I0 表示最小宽度(无填充)。ES 给出正确的科学记数法(1.23E+6),而 E 是(0.12E+7)。重复:3I4 = 三个整数每个宽度 4。格式中的字符串字面量:'("text")'。格式可以是字符串变量、*(表控,编译器选择)或标签(语句号)。为获得干净输出,整数优先用 I0,实数用 F 或 ES。

fortran
program format_demo
  implicit none
  integer :: n = 42
  real :: pi = 3.14159265
  real :: big = 1234567.89
  character(20) :: name = "Alice"

  ! Format specifiers
  ! Iw     - integer, width w
  ! Iw.m   - integer, width w, at least m digits
  ! Fw.d   - fixed-point real, width w, d decimals
  ! Ew.d   - exponential, width w, d decimals
  ! Aw     - character, width w
  ! A      - character, default width
  ! nX     - n spaces
  ! /      - newline

  write(*, '(I5)') n            ! "   42"
  write(*, '(I5.3)') n          ! "  042"
  write(*, '(I0)') n            ! "42" (minimal)
  write(*, '(F10.4)') pi        ! "    3.1416"
  write(*, '(E12.4)') big       ! " 0.1235E+07"
  write(*, '(ES12.4)') big      ! " 1.2346E+06" (scientific)
  write(*, '(A10)') name        ! "     Alice"
  write(*, '(A, I3)') "n=", n   ! "n= 42"

  ! Multiple items
  write(*, '(A, I3, A, F8.4)') "n=", n, " pi=", pi

  ! Repeated format: 3I4 = three integers width 4
  write(*, '(3I4)') 1, 2, 3     ! "   1   2   3"

  ! Newline and spacing
  write(*, '("Name: ", A, /, "Age: ", I3)') name, n

  ! List-directed (default format)
  print *, name, n, pi
end program format_demo

解析与分词

Fortran 没有内建的 split/分词——必须使用 index() 和子字符串手动编写。模式:用 index 找到分隔符,用子字符串提取标记,跳过分隔符,重复。对于 key=value 对,用 index 找到 '=' 并拆分为 key(之前)和 value(之后)。trim() 移除尾部空格;adjustl() 移除前导空格。为稳健解析,还要处理空标记和空白。或者,使用带格式说明符的内部读取处理结构化数据,或像从文件一样从字符串读取。某些 Fortran 框架中存在 split() 等库,但不是标准。

fortran
program parse_demo
  implicit none
  character(100) :: line = "name=Bob,age=30,city=NYC"
  character(50) :: token
  integer :: pos, start, end

  ! Split by comma
  start = 1
  do
    end = index(line(start:), ",")
    if (end == 0) then
      ! last token
      token = line(start:)
      call process(trim(token))
      exit
    end if
    token = line(start:start+end-2)
    call process(trim(token))
    start = start + end
  end do

contains
  subroutine process(t)
    character(*), intent(in) :: t
    integer :: eq_pos
    eq_pos = index(t, "=")
    if (eq_pos > 0) then
      print *, "Key: ", trim(t(:eq_pos-1)), &
               " Value: ", trim(t(eq_pos+1:))
    end if
  end subroutine process
end program parse_demo

! Output:
! Key: name Value: Bob
! Key: age Value: 30
! Key: city Value: NYC
05

过程:函数与子例程

函数

函数返回一个值并在表达式中使用(类似数学函数)。现代语法:'function name(args) result(var)'——result 变量是返回的内容。intent(in) 标记只读参数(编译器强制执行)。函数可以返回标量或数组(使用输入的 size() 来确定输出大小)。函数应该是 PURE 的(无副作用)——不要在函数中修改全局状态或做 I/O。内部过程(在 'contains' 块中)可以访问宿主的变量(宿主关联)。对于外部过程,使用 interface 块指定签名。

fortran
program func_demo
  implicit none
  ! Function declared in interface or as external
  print *, add(3, 4)         ! 7
  print *, square(5)         ! 25
  print *, distance(0.0, 0.0, 3.0, 4.0)   ! 5.0

contains
  ! Basic function with result clause
  function add(a, b) result(c)
    integer, intent(in) :: a, b
    integer :: c
    c = a + b
  end function add

  ! Function with result named same as function (legacy)
  function square(x) result(y)
    integer, intent(in) :: x
    integer :: y
    y = x * x
  end function square

  ! Real function
  function distance(x1, y1, x2, y2) result(d)
    real, intent(in) :: x1, y1, x2, y2
    real :: d
    d = sqrt((x2-x1)**2 + (y2-y1)**2)
  end function distance

  ! Array-valued function
  function reverse(arr) result(rev)
    integer, intent(in) :: arr(:)
    integer :: rev(size(arr))
    integer :: i, n
    n = size(arr)
    do i = 1, n
      rev(i) = arr(n-i+1)
    end do
  end function reverse
end program func_demo

子例程与 Intent

子例程用 'call' 调用,不返回值——它们就地修改参数。在以下情况使用子例程:(1)需要修改多个参数,(2)操作是'命令'而非'计算',(3)返回数组值结果不方便。intent 属性记录并强制参数方向:intent(in) = 只读(赋值给它会编译错误),intent(out) = 只写(进入时未定义,返回前必须设置),intent(inout) = 读写。始终指定 intent——它捕获 bug 并启用优化。子例程可以修改传给它们的数组(如果连续则不制作副本)。

fortran
program sub_demo
  implicit none
  integer :: x = 10, y = 20
  real :: arr(5) = [1.0, 2.0, 3.0, 4.0, 5.0]

  ! Subroutines are called with 'call'
  call swap(x, y)
  print *, x, y              ! 20 10

  call scale_array(arr, 2.0)
  print *, arr               ! 2 4 6 8 10

  call fill_zero(arr)
  print *, arr               ! 0 0 0 0 0

contains
  subroutine swap(a, b)
    integer, intent(inout) :: a, b   ! read AND write
    integer :: tmp
    tmp = a
    a = b
    b = tmp
  end subroutine swap

  subroutine scale_array(a, factor)
    real, intent(inout) :: a(:)
    real, intent(in) :: factor
    a = a * factor           ! whole-array operation
  end subroutine scale_array

  subroutine fill_zero(a)
    real, intent(out) :: a(:)        ! write-only (output)
    a = 0.0
  end subroutine fill_zero
end program sub_demo

纯函数与元素函数

pure 函数没有副作用:无 I/O、无全局变量修改、无 stop,只能调用其他 pure 过程。它们启用编译器优化(并行化、公共子表达式消除),并在某些上下文中是必需的(如 DO CONCURRENT)。elemental 函数为标量编写,但自动按元素作用于数组——写一次,两者通用。pure elemental 结合两者。对任何真正的数学函数(无副作用)使用 pure。当操作自然地按元素应用于数组时(数学函数、转换)使用 elemental。编译器可以自动向量化数组上的 elemental 调用。

fortran
program pure_demo
  implicit none
  real :: a(5) = [1.0, 2.0, 3.0, 4.0, 5.0]
  real :: b(5)
  integer :: i

  ! Pure function: no side effects, no I/O
  b = square_arr(a)
  print *, b                 ! 1 4 9 16 25

  ! Elemental function: works on scalars AND arrays automatically
  b = cube(a)                ! applies cube() element-wise
  print *, b                 ! 1 8 27 64 125

  print *, cube(2.0)         ! also works on scalar: 8.0

contains
  ! Pure: no side effects, no I/O, no stop, only pure calls
  pure function square_arr(x) result(y)
    real, intent(in) :: x(:)
    real :: y(size(x))
    y = x * x
  end function square_arr

  ! Elemental: scalar signature, but works on arrays too
  elemental function cube(x) result(y)
    real, intent(in) :: x
    real :: y
    y = x * x * x
  end function cube

  ! Pure elemental: both
  pure elemental double precision function sq(x) result(y)
    double precision, intent(in) :: x
    double precision :: y
    y = x * x
  end function sq
end program pure_demo

可选参数与关键字参数

optional 参数允许调用者省略它们。在过程内使用 present(arg) 检查是否提供了参数——访问缺失的 optional 是未定义行为。关键字参数(name="value")允许以任何顺序传递参数,并使调用自文档化。一旦使用关键字,所有后续参数也必须使用关键字。可选参数在签名中必须位于所有必需参数之后。默认值通过 present() 检查实现(Fortran 没有内建默认值语法)。关键字 + optional 一起启用灵活的 API:调用者只指定需要的。

fortran
program optional_demo
  implicit none
  ! All arguments after the first optional must also be optional
  print *, greet("Alice")                    ! Hello, Alice!
  print *, greet("Bob", "Hi")                ! Hi, Bob!
  print *, greet("Carol", greeting="Hey")    ! keyword argument
  print *, greet(greeting="Welcome", name="Dave")  ! all keywords

  ! With present() check
  call log_msg("Starting up")
  call log_msg("Error!", level=2)
  call log_msg("Debug info", level=0, file="debug.log")

contains
  function greet(name, greeting) result(msg)
    character(*), intent(in) :: name
    character(*), intent(in), optional :: greeting
    character(50) :: msg
    character(20) :: g

    if (present(greeting)) then
      g = greeting
    else
      g = "Hello"
    end if
    msg = trim(g) // ", " // name // "!"
  end function greet

  subroutine log_msg(message, level, file)
    character(*), intent(in) :: message
    integer, intent(in), optional :: level
    character(*), intent(in), optional :: file
    integer :: lvl
    lvl = 1
    if (present(level)) lvl = level
    print *, "[L", lvl, "] ", trim(message)
  end subroutine log_msg
end program optional_demo

内部与递归过程

内部过程(在 'contains' 内)具有宿主关联——它们可以读取和修改宿主程序的变量(类似闭包)。将它们用于需要宿主状态的辅助函数。递归过程必须用 'recursive' 前缀声明(Fortran 90/2003);Fortran 2018 使递归成为默认。对于相互递归,使用 interface 块声明前向引用。递归优雅但可能慢(函数调用开销)且有风险(深度递归的栈溢出)。对于阶乘/斐波那契,迭代版本更快更安全。对自然递归的问题(树遍历、分治)使用递归,且深度有界。

fortran
program nested_demo
  implicit none
  integer :: counter = 0   ! host variable

  ! Internal procedures (in contains) access host variables
  call increment
  call increment
  print *, counter          ! 2

  ! Recursive function
  print *, factorial(5)     ! 120
  print *, fib(10)          ! 55

  ! Mutual recursion (needs forward declaration)
  print *, is_even(4)       ! T

contains
  subroutine increment
    counter = counter + 1   ! modifies host's counter
  end subroutine increment

  recursive function factorial(n) result(f)
    integer, intent(in) :: n
    integer :: f
    if (n <= 1) then
      f = 1
    else
      f = n * factorial(n-1)
    end if
  end function factorial

  recursive function fib(n) result(f)
    integer, intent(in) :: n
    integer :: f
    if (n < 2) then
      f = n
    else
      f = fib(n-1) + fib(n-2)
    end if
  end function fib

  ! Mutual recursion with interface
  recursive function is_even(n) result(r)
    integer, intent(in) :: n
    logical :: r
    interface
      recursive function is_odd(m) result(ro)
        integer, intent(in) :: m
        logical :: ro
      end function is_odd
    end interface
    if (n == 0) then
      r = .true.
    else
      r = is_odd(n-1)
    end if
  end function is_even
end program nested_demo
06

模块与封装

模块基础与使用

模块是 Fortran 的主要封装机制(替代 common 块和外部过程)。模块文件包含:(1)声明(常量、变量、派生类型),(2)带过程的 'contains' 块。使用 'use module_name' 导入;'use module_name, only: x, y' 只导入特定实体(推荐——避免命名空间污染)。模块变量是持久的(静态的)并由所有使用该模块的过程共享。模块提供显式接口(编译器检查参数类型),不同于外部过程。始终在使用模块的文件之前编译模块文件。模块中的 'implicit none' 会传播到其所有过程。

fortran
! geometry.f90 - module file
module geometry
  implicit none
  private                      ! default: everything private
  public :: circle_area, circle_perimeter, PI

  ! Module-level constants (persistent)
  real, parameter :: PI = 3.14159265

contains
  function circle_area(r) result(a)
    real, intent(in) :: r
    real :: a
    a = PI * r * r
  end function circle_area

  function circle_perimeter(r) result(p)
    real, intent(in) :: r
    real :: p
    p = 2.0 * PI * r
  end function circle_perimeter
end module geometry

! main.f90 - using the module
program use_module
  use geometry, only: circle_area, circle_perimeter, PI
  implicit none
  real :: r = 5.0
  print *, "Area: ", circle_area(r)         ! 78.5398
  print *, "Perimeter: ", circle_perimeter(r) ! 31.4159
  print *, "PI: ", PI
end program use_module

! Compile: gfortran geometry.f90 main.f90 -o main

访问控制(Public/Private)

访问控制:'private' 使实体成为模块内部;'public' 导出它们。默认可在模块级别设置('private' 然后选择性 'public :: ...')——这是最佳实践(显式接口)。派生类型组件可以是 private 的,即使类型本身是 public 的——调用者可以使用该类型但不能直接访问内部;必须通过过程。'save' 使模块变量持久(它们在调用之间保留值)——模块变量默认被保存。'final' 定义析构函数(对象超出作用域时调用)。这种封装通过过程强制不变量,实现真正的 OOP。

fortran
module bank_account
  implicit none
  private                       ! default: everything private
  ! Explicitly export:
  public :: account_t, deposit, withdraw, get_balance

  ! Derived type - can expose type but hide internals
  type :: account_t
    private                    ! components are private
    real :: balance = 0.0
    integer :: id = 0
  contains
    procedure :: balance => get_bal   ! type-bound procedure
    final :: cleanup                  ! destructor
  end type account_t

  ! Module-level counter (private, not exported)
  integer, save :: next_id = 1000

contains
  ! Constructor (factory function)
  function create_account(initial) result(acc)
    type(account_t) :: acc
    real, intent(in) :: initial
    acc%balance = initial
    acc%id = next_id
    next_id = next_id + 1
  end function create_account

  subroutine deposit(acc, amount)
    type(account_t), intent(inout) :: acc
    real, intent(in) :: amount
    acc%balance = acc%balance + amount
  end subroutine deposit

  function get_bal(acc) result(b)
    class(account_t), intent(in) :: acc
    real :: b
    b = acc%balance
  end function get_bal

  subroutine cleanup(acc)
    type(account_t) :: acc
    ! cleanup code (e.g., log closure)
  end subroutine cleanup
end module bank_account

模块中的派生类型

在模块中定义的派生类型可以有:类型绑定过程(procedure :: name => impl)、构造器(通过类型名称的重载 interface)和 allocatable 组件。类型绑定过程中的 'class(keyword)' 启用多态(实际类型可能是子类)。类型绑定过程以 obj%method(args) 调用——OOP 语法。将类型名称重载为 interface 允许有多个构造器(vector_from_array、vector_from_size)。allocatable 组件自动分配/释放。这是现代 Fortran OOP:封装、方法、构造器和多态,全部在模块系统内。

fortran
module vector_mod
  implicit none
  private
  public :: vector_t, vector_add, vector_scale

  ! Derived type with type parameters (Fortran 2003)
  type :: vector_t
    real, allocatable :: data(:)
    integer :: length = 0
  contains
    procedure :: norm => vector_norm
    procedure :: print => vector_print
  end type vector_t

  ! Constructor interface (overloaded)
  interface vector_t
    procedure vector_from_array
    procedure vector_from_size
  end interface vector_t

contains
  function vector_from_array(arr) result(v)
    real, intent(in) :: arr(:)
    type(vector_t) :: v
    v%data = arr
    v%length = size(arr)
  end function

  function vector_from_size(n, fill) result(v)
    integer, intent(in) :: n
    real, intent(in) :: fill
    type(vector_t) :: v
    allocate(v%data(n))
    v%data = fill
    v%length = n
  end function

  function vector_norm(self) result(n)
    class(vector_t), intent(in) :: self
    real :: n
    n = sqrt(sum(self%data**2))
  end function

  subroutine vector_print(self)
    class(vector_t), intent(in) :: self
    print *, "Vector(len=", self%length, "): ", self%data
  end subroutine

  function vector_add(a, b) result(c)
    type(vector_t), intent(in) :: a, b
    type(vector_t) :: c
    c%data = a%data + b%data
    c%length = a%length
  end function

  function vector_scale(a, s) result(c)
    type(vector_t), intent(in) :: a
    real, intent(in) :: s
    type(vector_t) :: c
    c%data = a%data * s
    c%length = a%length
  end function
end module vector_mod

通用过程与重载

通用 interface 提供临时多态(重载):一个名称根据参数类型分派到不同的特定过程。'interface name / module procedure proc1, proc2 / end interface' 块列出所有特定过程。编译器在编译时根据参数类型/秩选择匹配的过程。通用中的所有特定过程必须有独特的签名(可通过参数类型区分)——否则歧义。这就是 Fortran 在没有模板的情况下实现运算符/函数重载的方式。通用分派在编译时解析(无运行时开销)。使用通用提供跨类型的统一 API。

fortran
module generics_mod
  implicit none
  private
  public :: print_value, add

  ! Generic interface: one name, multiple specific procedures
  interface print_value
    module procedure print_int
    module procedure print_real
    module procedure print_str
    module procedure print_int_array
  end interface print_value

  interface add
    module procedure add_int
    module procedure add_real
    module procedure add_arrays
  end interface add

contains
  subroutine print_int(x)
    integer, intent(in) :: x
    print *, "Integer: ", x
  end subroutine

  subroutine print_real(x)
    real, intent(in) :: x
    print *, "Real: ", x
  end subroutine

  subroutine print_str(s)
    character(*), intent(in) :: s
    print *, "String: ", trim(s)
  end subroutine

  subroutine print_int_array(arr)
    integer, intent(in) :: arr(:)
    print *, "Array: ", arr
  end subroutine

  function add_int(a, b) result(c)
    integer, intent(in) :: a, b
    integer :: c
    c = a + b
  end function

  function add_real(a, b) result(c)
    real, intent(in) :: a, b
    real :: c
    c = a + b
  end function

  function add_arrays(a, b) result(c)
    real, intent(in) :: a(:), b(:)
    real :: c(size(a))
    c = a + b
  end function
end module generics_mod

program use_generics
  use generics_mod
  implicit none
  call print_value(42)              ! Integer: 42
  call print_value(3.14)            ! Real: 3.14
  call print_value("Hello")         ! String: Hello
  call print_value([1,2,3])         ! Array: 1 2 3
  print *, add(2, 3)                ! 5
  print *, add(2.5, 3.5)            ! 6.0
end program use_generics

运算符重载

运算符重载让你定义 +、-、*、/、== 等如何作用于派生类型。interface operator(+) / module procedure vec_add / end interface 将 + 运算符绑定到函数。对于二元运算符,可以用单独的过程重载两种顺序(vec*scalar 和 scalar*vec)。assignment(=) 重载赋值运算符(该过程是带 intent(out) LHS 和 intent(in) RHS 的子例程)。这启用类似数学的语法:c = a + b 而不是 c = vec_add(a, b)。对数学类型(向量、矩阵、复数)使用运算符重载,可提高可读性。避免对非显而易见的语义进行重载。结构构造器 vec3(x,y,z) 对派生类型是内建的。

fortran
module vec_ops
  implicit none
  private
  public :: vec3, operator(+), operator(*), assignment(=)

  type :: vec3
    real :: x, y, z
  end type vec3

  ! Overload the + operator for vec3 + vec3
  interface operator(+)
    module procedure vec_add
  end interface

  ! Overload * for vec3 * scalar and scalar * vec3
  interface operator(*)
    module procedure vec_scale_r
    module procedure vec_scale_l
    module procedure vec_dot
  end interface

  ! Overload = for array-to-vec assignment
  interface assignment(=)
    module procedure arr_to_vec
  end interface

contains
  function vec_add(a, b) result(c)
    type(vec3), intent(in) :: a, b
    type(vec3) :: c
    c = vec3(a%x+b%x, a%y+b%y, a%z+b%z)
  end function

  function vec_scale_r(v, s) result(r)
    type(vec3), intent(in) :: v
    real, intent(in) :: s
    type(vec3) :: r
    r = vec3(v%x*s, v%y*s, v%z*s)
  end function

  function vec_scale_l(s, v) result(r)
    real, intent(in) :: s
    type(vec3), intent(in) :: v
    type(vec3) :: r
    r = vec3(v%x*s, v%y*s, v%z*s)
  end function

  ! vec3 * vec3 = dot product (scalar)
  function vec_dot(a, b) result(d)
    type(vec3), intent(in) :: a, b
    real :: d
    d = a%x*b%x + a%y*b%y + a%z*b%z
  end function

  subroutine arr_to_vec(v, arr)
    type(vec3), intent(out) :: v
    real, intent(in) :: arr(3)
    v = vec3(arr(1), arr(2), arr(3))
  end subroutine
end module vec_ops

program use_ops
  use vec_ops
  implicit none
  type(vec3) :: a, b, c
  a = vec3(1.0, 2.0, 3.0)
  b = vec3(4.0, 5.0, 6.0)
  c = a + b              ! vec3 addition
  print *, c%x, c%y, c%z ! 5 7 9
  print *, a * 2.0       ! scale: 2 4 6
  print *, 3.0 * b       ! scale: 12 15 18
  print *, a * b         ! dot product: 32
end program use_ops
07

派生类型(结构体)与 OOP

定义派生类型

派生类型是 Fortran 的结构体(用户定义的复合类型)。用 'type :: Name ... end type Name' 定义。组件用 % 访问(不是 .——那是用于复数的)。结构构造器:Name(val1, val2) 创建实例。组件可以有默认值(声明中的 = value)。整体类型赋值复制所有组件(对 allocatable 组件是深拷贝)。支持派生类型数组。派生类型是 Fortran OOP 的基础(带类型绑定过程、继承、多态)。使用 % 访问组件:obj%field、obj%method()。构造器语法 Name(args) 是自动的,除非用 interface 覆盖。

fortran
program derived_types
  implicit none
  ! Basic derived type (struct)
  type :: Point
    real :: x, y
  end type Point

  ! Type with default initialization
  type :: Person
    character(20) :: name = "Unknown"
    integer :: age = 0
    logical :: active = .true.
  end type Person

  ! Declare and construct
  type(Point) :: p1, p2
  type(Person) :: alice, bob

  ! Structure constructor
  p1 = Point(3.0, 4.0)
  p2 = Point(0.0, 0.0)
  alice = Person("Alice", 30, .true.)
  bob = Person("Bob", 25)        ! uses default for 'active'

  ! Access components with %
  print *, p1%x, p1%y            ! 3.0 4.0
  print *, alice%name, alice%age ! Alice 30
  print *, bob%active            ! T (default)

  ! Modify components
  p1%x = p1%x + 1.0
  alice%age = 31

  ! Whole-type assignment (component-wise copy)
  p2 = p1
  print *, p2%x                  ! 4.0

  ! Array of derived types
  type(Point) :: points(3)
  points(1) = Point(1.0, 1.0)
  points(2) = Point(2.0, 2.0)
  points(3) = Point(3.0, 3.0)
  print *, points(2)%y           ! 2.0
end program derived_types

类型组件与构造器

派生类型可以有:allocatable 组件(自动管理内存)、默认初始化的组件、类型绑定过程(方法)和 finalizer(析构函数)。'interface TypeName / module procedure custom_init / end interface' 用自定义工厂函数重载结构构造器。类型绑定过程中的 'class(ClassName)'(vs 'type(ClassName)')启用多态(实际类型可能是子类)。'final' 过程在对象超出作用域时运行(析构函数)——用它们释放资源。allocatable 组件在终结时自动释放,但对于复杂清理,显式 finalizer 更清晰。block 构造允许在代码中间声明变量(Fortran 2008)。

fortran
program type_components
  implicit none
  ! Type with various component kinds
  type :: Student
    integer :: id
    character(20) :: name
    real, allocatable :: grades(:)    ! allocatable component
    integer :: num_grades = 0
  contains
    procedure :: add_grade
    procedure :: average => student_avg
    final :: student_finalize
  end type Student

  ! Overloaded constructor
  interface Student
    module procedure student_init
  end interface Student

  type(Student) :: s

  ! Use custom constructor
  s = Student(101, "Alice")
  call s%add_grade(85.0)
  call s%add_grade(92.0)
  call s%add_grade(78.0)
  print *, s%average()    ! 85.0

contains
  function student_init(id, name) result(s)
    integer, intent(in) :: id
    character(*), intent(in) :: name
    type(Student) :: s
    s%id = id
    s%name = name
    s%num_grades = 0
  end function

  subroutine add_grade(self, g)
    class(Student), intent(inout) :: self
    real, intent(in) :: g
    integer :: n
    n = self%num_grades
    if (n == 0) then
      allocate(self%grades(1))
    else
      ! grow array
      block
        real, allocatable :: tmp(:)
        tmp = self%grades
        deallocate(self%grades)
        allocate(self%grades(n+1))
        self%grades(1:n) = tmp
      end block
    end if
    self%num_grades = n + 1
    self%grades(n+1) = g
  end subroutine

  function student_avg(self) result(avg)
    class(Student), intent(in) :: self
    real :: avg
    if (self%num_grades > 0) then
      avg = sum(self%grades) / self%num_grades
    else
      avg = 0.0
    end if
  end function

  subroutine student_finalize(self)
    type(Student) :: self
    if (allocated(self%grades)) deallocate(self%grades)
  end subroutine
end program type_components

类型绑定过程(方法)

类型绑定过程是 Fortran 的方法:'procedure :: method_name => implementation'。以 obj%method(args) 调用——OOP 语法。第一个参数是 'self'(对象),声明为 'class(TypeName)'(多态)或 'type(TypeName)'(具体)。'class' 允许继承/多态;'type' 用于不可扩展类型。'=> implementation' 将方法名映射到特定过程(允许重命名)。final 是析构函数。此示例实现了一个带自动增长数组的动态栈。类型绑定过程提供真正的 OOP:封装(数据 + 方法在一起)、消息传递语法(obj%method)和多态(通过 class)。始终为类型绑定过程使用 class() 以启用未来继承。

fortran
module stack_mod
  implicit none
  private
  public :: stack_t

  type :: stack_t
    integer, allocatable :: data(:)
    integer :: top = 0
    integer :: capacity = 0
  contains
    procedure :: push => stack_push
    procedure :: pop => stack_pop
    procedure :: peek => stack_peek
    procedure :: is_empty => stack_is_empty
    procedure :: size => stack_size
    procedure :: clear => stack_clear
    final :: stack_finalize
  end type stack_t

  interface stack_t
    module procedure stack_init
  end interface

contains
  function stack_init(initial_cap) result(s)
    integer, intent(in), optional :: initial_cap
    type(stack_t) :: s
    integer :: cap
    cap = 16
    if (present(initial_cap)) cap = initial_cap
    allocate(s%data(cap))
    s%capacity = cap
    s%top = 0
  end function

  subroutine stack_push(self, val)
    class(stack_t), intent(inout) :: self
    integer, intent(in) :: val
    if (self%top >= self%capacity) then
      ! grow
      block
        integer, allocatable :: tmp(:)
        tmp = self%data
        deallocate(self%data)
        allocate(self%data(self%capacity * 2))
        self%data(1:self%capacity) = tmp
        self%capacity = self%capacity * 2
      end block
    end if
    self%top = self%top + 1
    self%data(self%top) = val
  end subroutine

  function stack_pop(self) result(val)
    class(stack_t), intent(inout) :: self
    integer :: val
    if (self%top == 0) stop "Stack underflow"
    val = self%data(self%top)
    self%top = self%top - 1
  end function

  function stack_peek(self) result(val)
    class(stack_t), intent(in) :: self
    integer :: val
    val = self%data(self%top)
  end function

  logical function stack_is_empty(self) result(r)
    class(stack_t), intent(in) :: self
    r = (self%top == 0)
  end function

  integer function stack_size(self) result(n)
    class(stack_t), intent(in) :: self
    n = self%top
  end function

  subroutine stack_clear(self)
    class(stack_t), intent(inout) :: self
    self%top = 0
  end subroutine

  subroutine stack_finalize(self)
    type(stack_t) :: self
    if (allocated(self%data)) deallocate(self%data)
  end subroutine
end module stack_mod

program use_stack
  use stack_mod
  implicit none
  type(stack_t) :: s
  s = stack_t(8)         ! initial capacity 8
  call s%push(10)
  call s%push(20)
  call s%push(30)
  print *, s%size()      ! 3
  print *, s%pop()       ! 30
  print *, s%pop()       ! 20
  print *, s%is_empty()  ! F
end program use_stack

继承与多态

Fortran OOP:'type, extends(Parent) :: Child' 创建子类(继承)。'type, abstract :: Name' 带 'procedure(...), deferred :: method' 定义抽象基类(类似 Java 抽象类 / C++ 纯虚函数)。'class(Base)' 是多态的——可以持有任何子类。多态分派:调用 obj%method() 调用子类的重写。'select type (var => expr) / type is (ConcreteType) / end select' 进行运行时类型检查(向下转型)。用 'allocate(TypeName::var)' 创建特定具体类型的多态对象。这是完整的 OOP:继承、多态、抽象类型和运行时分派——与 Java/C++ 相当。

fortran
module shapes
  implicit none
  private
  public :: shape, circle, rectangle, shape_ptr

  ! Base type (abstract)
  type, abstract :: shape
    character(20) :: name
  contains
    procedure(area_if), deferred :: area
    procedure(describe_if), deferred :: describe
    procedure :: get_name => shape_get_name
  end type shape

  ! Abstract interface (must be implemented by subclasses)
  abstract interface
    function area_if(self) result(a)
      import :: shape
      class(shape), intent(in) :: self
      real :: a
    end function area_if
    subroutine describe_if(self)
      import :: shape
      class(shape), intent(in) :: self
    end subroutine describe_if
  end interface

  ! Derived type: circle extends shape
  type, extends(shape) :: circle
    real :: radius
  contains
    procedure :: area => circle_area
    procedure :: describe => circle_describe
  end type circle

  ! Derived type: rectangle extends shape
  type, extends(shape) :: rectangle
    real :: width, height
  contains
    procedure :: area => rect_area
    procedure :: describe => rect_describe
  end type rectangle

  ! Class pointer for polymorphism
  type :: shape_ptr
    class(shape), allocatable :: ptr
  end type shape_ptr

contains
  function shape_get_name(self) result(n)
    class(shape), intent(in) :: self
    character(20) :: n
    n = self%name
  end function

  function circle_area(self) result(a)
    class(circle), intent(in) :: self
    real :: a
    a = 3.14159265 * self%radius**2
  end function

  subroutine circle_describe(self)
    class(circle), intent(in) :: self
    print *, "Circle '", trim(self%name), "' r=", self%radius
  end subroutine

  function rect_area(self) result(a)
    class(rectangle), intent(in) :: self
    real :: a
    a = self%width * self%height
  end function

  function rect_describe(self) result(s)
    class(rectangle), intent(in) :: self
    character(50) :: s
    write(s, '("Rectangle ", F6.2, "x", F6.2)') self%width, self%height
  end function
end module shapes

program use_polymorphism
  use shapes
  implicit none
  type(shape_ptr) :: shapes_arr(2)
  ! Allocate concrete types into polymorphic container
  allocate(circle::shapes_arr(1)%ptr)
  select type(s => shapes_arr(1)%ptr)
  type is (circle)
    s%radius = 5.0
    s%name = "C1"
  end select

  allocate(rectangle::shapes_arr(2)%ptr)
  select type(s => shapes_arr(2)%ptr)
  type is (rectangle)
    s%width = 3.0
    s%height = 4.0
    s%name = "R1"
  end select

  ! Polymorphic dispatch
  block
    integer :: i
    do i = 1, 2
      call shapes_arr(i)%ptr%describe
      print *, "Area: ", shapes_arr(i)%ptr%area()
    end do
  end block
end program use_polymorphism

嵌套类型与类型数组

派生类型可以嵌套(组合):一个类型可以有其他派生类型的组件。用链式 % 访问嵌套组件:emp%home%city。结构构造器自然嵌套:Employee(id, name, Address(...), salary)。支持派生类型数组:type(Employee) :: emps(N)。可以提取单个组件的数组:emps(:)%id 给出整数数组。allocatable 组件允许动态大小的集合(例如,有可变数量员工的部门)。这种组合模型是在 Fortran 中构建复杂数据结构(树、图、列表)的基础。当没有明确的子类型关系时,使用组合(has-a)而非继承(is-a)。

fortran
program nested_types
  implicit none
  ! Nested derived types
  type :: Address
    character(50) :: street
    character(30) :: city
    character(10) :: zip
  end type Address

  type :: Employee
    integer :: id
    character(30) :: name
    type(Address) :: home      ! nested type
    real :: salary
  end type Employee

  type :: Department
    character(30) :: name
    type(Employee), allocatable :: employees(:)   ! array of types
    integer :: count = 0
  end type Department

  ! Construct with nested structure constructor
  type(Employee) :: emp
  emp = Employee(101, "Alice", &
        Address("123 Main St", "Springfield", "12345"), 75000.0)

  ! Access nested components
  print *, emp%name                      ! Alice
  print *, emp%home%city                 ! Springfield
  print *, emp%home%zip                  ! 12345

  ! Department with array of employees
  type(Department) :: dept
  dept%name = "Engineering"
  allocate(dept%employees(3))
  dept%employees(1) = emp
  dept%employees(2) = Employee(102, "Bob", &
                        Address("456 Oak Ave", "Shelbyville", "54321"), 68000.0)
  dept%count = 2

  ! Iterate over array of types
  block
    integer :: i
    do i = 1, dept%count
      print *, dept%employees(i)%id, trim(dept%employees(i)%name), &
               dept%employees(i)%home%city
    end do
  end block

  ! Array of derived type components
  print *, dept%employees(1:2)%id        ! array of id field
end program nested_types
08

文件 I/O 与格式化

打开与关闭文件

open() 将文件连接到单元号。newunit=u 让编译器选择唯一单元(避免冲突)——始终优先使用此方式而非硬编码单元号。status:'old'(文件必须存在)、'new'(必须不存在)、'replace'(删除 + 创建)、'scratch'(临时,关闭时自动删除)。action:'read'、'write'、'readwrite'。position:'rewind'(开头)、'append'(末尾)、'asis'(保持原位)。始终在 open 和 read 后检查 iostat——非零表示错误(负 = EOF,正 = 错误)。iomsg 给出描述性错误消息。close() 断开连接。为稳健的文件处理,始终检查 iostat 并优雅地处理错误。

fortran
program file_open
  implicit none
  integer :: u, ios
  character(100) :: msg

  ! newunit: compiler picks a unique unit number (Fortran 2008)
  ! status: 'old' (must exist), 'new' (must not exist),
  !         'replace' (overwrite), 'scratch' (temporary)
  ! action: 'read', 'write', 'readwrite'
  ! position: 'rewind', 'append', 'asis'
  open(newunit=u, file="data.txt", status="replace", &
       action="write", iostat=ios, iomsg=msg)
  if (ios /= 0) then
    print *, "Open failed: ", trim(msg)
    stop 1
  end if

  write(u, *) "First line"
  write(u, *) "Second line"
  close(u)

  ! Append to existing file
  open(newunit=u, file="data.txt", status="old", &
       action="write", position="append", iostat=ios)
  if (ios == 0) then
    write(u, *) "Appended line"
    close(u)
  end if

  ! Scratch file (auto-deleted on close)
  open(newunit=u, status="scratch", action="readwrite")
  write(u, *) "Temporary data"
  rewind(u)
  ! read back...
  close(u)   ! file disappears

  ! Reading with end-of-file detection
  open(newunit=u, file="data.txt", status="old", action="read")
  do
    read(u, '(A)', iostat=ios) msg
    if (ios /= 0) exit   ! EOF or error
    print *, trim(msg)
  end do
  close(u)
end program file_open

格式化 I/O

格式说明符控制 I/O:'(A, I0, F8.2)'。A=字符,I0=整数最小宽度,F8.2=实数宽度 8 带 2 位小数。write(unit, fmt) 写入;read(unit, fmt) 读取。unit=* 表示 stdout/stdin。对于文件,使用 open() 返回的单元。表控 I/O (*) 灵活:read(u, *) a, b, c 自动读取逗号/空格分隔的值。对于 CSV,如果值以逗号分隔,表控读取有效。格式字符串可重用:'(3I4)' 应用 I4 三次。始终使格式与数据类型匹配——不匹配的格式会导致运行时错误。对于混合文本+数字,先读为字符串再解析,或使用显式格式。

fortran
program formatted_io
  implicit none
  integer :: u, n = 42
  real :: pi = 3.14159265
  character(20) :: name = "Alice"

  ! Write formatted data to file
  open(newunit=u, file="output.txt", status="replace")
  write(u, '(A, I0)') "Count: ", n
  write(u, '(A, F8.4)') "Pi: ", pi
  write(u, '(A, A)') "Name: ", trim(name)
  write(u, '(3(I0, 1X))') 1, 2, 3   ! "1 2 3 "
  close(u)

  ! Read formatted data back
  open(newunit=u, file="output.txt", status="old", action="read")
  block
    character(100) :: line
    integer :: i
    do i = 1, 5
      read(u, '(A)') line
      print *, trim(line)
    end do
  end block
  close(u)

  ! Reading structured data
  open(newunit=u, file="data.csv", status="replace")
  write(u, '(I0, ",", I0, ",", I0)') 1, 10, 100
  write(u, '(I0, ",", I0, ",", I0)') 2, 20, 200
  close(u)

  ! Read back as numbers
  open(newunit=u, file="data.csv", status="old", action="read")
  block
    integer :: a, b, c
    ! List-directed read handles commas as separators
    read(u, *) a, b, c
    print *, a, b, c   ! 1 10 100
    read(u, *) a, b, c
    print *, a, b, c   ! 2 20 200
  end block
  close(u)
end program formatted_io

无格式(二进制)I/O

无格式(二进制)I/O 比格式化(文本)I/O 更快更紧凑——无字符串转换。form='unformatted' 启用它。access='stream'(Fortran 2003)提供字节流访问(类似 C 文件 I/O,无记录标记)。access='sequential'(默认)使用记录标记(每次写/读是一个带长度前缀的记录)——在 Fortran 内可移植,但不能移植到其他语言。为了与 C/Python 互操作,使用流访问。二进制文件不可读,但非常适合大型数值数据集。始终在数据之前写入元数据(数组大小、类型信息),以便正确读回。无格式 I/O 保留完整精度(无文本转换的舍入)。

fortran
program binary_io
  implicit none
  integer :: u, i
  real :: arr(5) = [1.0, 2.0, 3.0, 4.0, 5.0]
  real :: arr_in(5)
  integer :: n

  ! Write binary (unformatted) - faster, no format conversion
  open(newunit=u, file="data.bin", status="replace", &
       form="unformatted", access="stream")
  write(u) size(arr)        ! write the count first
  write(u) arr              ! write the whole array
  close(u)

  ! Read binary back
  open(newunit=u, file="data.bin", status="old", &
       form="unformatted", access="stream", action="read")
  read(u) n                 ! read the count
  print *, "Count: ", n
  read(u) arr_in            ! read the array
  print *, arr_in           ! 1 2 3 4 5
  close(u)

  ! Sequential unformatted (default) - with record markers
  open(newunit=u, file="data_seq.bin", status="replace", &
       form="unformatted")   ! access defaults to "sequential"
  do i = 1, 5
    write(u) i, arr(i)      ! each write is a "record"
  end do
  close(u)

  ! Read sequentially
  open(newunit=u, file="data_seq.bin", status="old", &
       form="unformatted", action="read")
  do
    read(u, iostat=i) n, arr_in(1)
    if (i /= 0) exit
    print *, n, arr_in(1)
  end do
  close(u)
end program binary_io

Namelist(分组 I/O)

namelist 为结构化文本 I/O 分组变量——类似 JSON/YAML 但是 Fortran 原生的。用 'namelist /name/ var1, var2, ...' 定义。write(u, nml=name) 以 &NAME var=val, var=val, / 格式输出。read(u, nml=name) 解析回来。namelist 非常适合配置文件:用户编辑文本文件,程序读取它。变量保留声明的值作为默认值;只有文件中的会被覆盖。格式宽容(对空白不敏感,逗号可选)。namelist 支持所有内建类型和数组。iostat 捕获解析错误。这是使 Fortran 程序可配置的最简单方式,无需编写自定义解析器。

fortran
program namelist_demo
  implicit none
  ! Namelist groups variables for easy I/O
  integer :: max_iter = 100
  real :: tolerance = 1.0e-6
  logical :: verbose = .true.
  character(20) :: method = "newton"
  real :: params(3) = [0.1, 0.2, 0.3]

  ! Define a namelist group
  namelist /config/ max_iter, tolerance, verbose, method, params

  ! Write namelist to file
  block
    integer :: u
    open(newunit=u, file="config.nml", status="replace")
    write(u, nml=config)
    close(u)
  end block

  ! The file looks like:
  ! &CONFIG
  !  MAX_ITER=100,
  !  TOLERANCE=1.0000000E-06,
  !  VERBOSE=T,
  !  METHOD="newton",
  !  PARAMS=0.100000, 0.200000, 0.300000,
  ! /

  ! Read namelist from file (overrides defaults)
  block
    integer :: u, ios
    open(newunit=u, file="config.nml", status="old", action="read")
    read(u, nml=config, iostat=ios)
    close(u)
    if (ios /= 0) then
      print *, "Error reading config"
    else
      print *, "max_iter=", max_iter
      print *, "tolerance=", tolerance
      print *, "method=", trim(method)
      print *, "params=", params
    end if
  end block

  ! User can edit config.nml in a text editor
  ! then re-run to pick up new values
end program namelist_demo

内部文件与错误处理

内部文件允许从/向字符串而不是文件读写——Fortran 的 sprintf/sscanf。write(str, fmt) 格式化为字符串;read(str, fmt) 从字符串解析。这是在字符串和数字之间转换的标准方式。始终使用 iostat 进行错误处理:0 = 成功,负 = EOF,正 = 错误。旧式的 end= 和 err= 标签有效,但 iostat 更干净(无 goto)。为稳健解析,每次读取后检查 iostat。内部 I/O 非常适合:构建输出字符串、解析用户输入、转换配置文件值。字符串充当'内部文件'——相同的 I/O 语句,只是目标是字符串。

fortran
program internal_io
  implicit none
  character(100) :: buffer
  integer :: n = 42
  real :: x = 3.14159
  integer :: ios

  ! Internal write: format to a string (like sprintf)
  write(buffer, '(A, I0, A, F8.4)') "n=", n, " x=", x
  print *, trim(buffer)   ! n=42 x=  3.1416

  ! Internal read: parse from a string (like sscanf)
  character(50) :: input = "100 3.14 hello"
  integer :: a
  real :: b
  character(20) :: c
  read(input, *, iostat=ios) a, b, c
  if (ios == 0) print *, a, b, trim(c)   ! 100 3.14 hello

  ! Robust number parsing with error handling
  character(20) :: num_str = "3.14abc"
  real :: val
  read(num_str, *, iostat=ios) val
  if (ios /= 0) then
    print *, "Parse error: '", trim(num_str), "' is not a number"
  else
    print *, "Value: ", val
  end if

  ! End-of-file and error handling on file reads
  block
    integer :: u
    character(100) :: line
    open(newunit=u, file="data.txt", status="old", action="read")
    do
      read(u, '(A)', iostat=ios) line
      if (ios < 0) then
        print *, "End of file"
        exit
      else if (ios > 0) then
        print *, "Read error at line"
        exit
      end if
      print *, trim(line)
    end do
    close(u)
  end block

  ! err= and end= labels (legacy style)
  block
    integer :: u, val
    open(newunit=u, file="nums.txt", status="old", action="read")
    do
      read(u, *, end=100, err=200) val
      print *, val
    end do
100 print *, "Reached EOF"
    close(u)
    goto 300
200 print *, "Read error!"
    close(u)
300 continue
  end block
end program internal_io
09

数值计算

Kind 参数与精度

Kind 参数控制精度/大小。现代可移植方式:使用 iso_fortran_env(int32/int64、real32/real64/real128)。旧式:selected_real_kind(digits, exponent_range)。real32 ≈ 7 位有效数字,real64 ≈ 15 位(双精度),real128 ≈ 33 位(四倍)。关键:始终为字面量附加 kind 后缀(3.14_dp,不是 3.14)——否则字面量先被解析为单精度然后转换,丢失位数。precision() 返回有效位数;range() 返回十进制指数范围。epsilon() 给出机器 epsilon(最小可区分增量)。tiny/huge 给出最小/最大值。对于科学计算,默认使用 real64(双精度)。

fortran
program precision_demo
  use iso_fortran_env, only: int32, int64, real32, real64, real128
  implicit none
  ! Portable kind selection via iso_fortran_env
  integer(kind=int32) :: i32 = 100
  integer(kind=int64) :: i64 = 9223372036854775807_int64
  real(kind=real32) :: r32 = 3.14159265_real32   ! single (~7 digits)
  real(kind=real64) :: r64 = 3.14159265358979_real64  ! double (~15 digits)
  real(kind=real128) :: r128 = 3.14159265358979_real128 ! quad (~33 digits)

  ! Legacy: selected_real_kind (still works)
  integer, parameter :: dp = selected_real_kind(15, 307)  ! double
  integer, parameter :: sp = selected_real_kind(6, 37)    ! single
  real(kind=dp) :: pi = 3.14159265358979_dp

  print *, "real32 precision: ", precision(r32), " range: ", range(r32)
  print *, "real64 precision: ", precision(r64), " range: ", range(r64)
  print *, "real128 precision: ", precision(r128)

  ! Epsilon and tiny
  print *, "epsilon(r32): ", epsilon(r32)   ! ~1.19e-7
  print *, "epsilon(r64): ", epsilon(r64)   ! ~2.22e-16
  print *, "tiny(r64): ", tiny(r64)         ! smallest positive
  print *, "huge(i32): ", huge(i32)         ! 2147483647
  print *, "huge(i64): ", huge(i64)

  ! Always use _kind suffix on literals
  ! WRONG: real(kind=dp) :: x = 3.14  (loses precision!)
  ! RIGHT: real(kind=dp) :: x = 3.14_dp
end program precision_demo

线性代数(matmul、solve、BLAS)

Fortran 有内建线性代数:matmul(矩阵-矩阵/矩阵-向量乘法)、dot_product、transpose。对于求解线性方程组(Ax=b)、特征值、SVD 等,使用 LAPACK(行业标准 Fortran 库):dgesv 求解 Ax=b,dgesvd 做 SVD,dsyev 做特征分解。用 -llapack -lblas 链接。示例展示了 3x3 系统的手动高斯消元——对于实际工作,使用 LAPACK(更快、带主元更准确、处理任何大小)。Fortran 的列主序存储原生匹配 LAPACK 的期望(无需转置)。matmul 已优化,但对于大矩阵,BLAS dgemm 更快。始终检查条件数以确保数值稳定性。

fortran
program linalg
  implicit none
  real(8) :: A(3,3), B(3,3), C(3,3)
  real(8) :: v(3), w(3), x(3)
  real(8) :: det
  integer :: i

  ! Initialize matrices
  A = reshape([1,2,3, 4,5,6, 7,8,10], [3,3])  ! column-major fill
  B = reshape([1,0,0, 0,1,0, 0,0,1], [3,3])   ! identity

  ! Matrix-matrix multiplication
  C = matmul(A, B)        ! A * I = A
  print *, "A * I = A? ", all(abs(C - A) < 1e-10)

  ! Matrix-vector multiplication
  v = [1.0, 2.0, 3.0]
  w = matmul(A, v)
  print *, "A * v = ", w    ! 14 32 53

  ! Dot product
  print *, "v . v = ", dot_product(v, v)   ! 14

  ! Outer product
  C = 0.0
  do i = 1, 3
    C(:,i) = v * v(i)
  end do

  ! Transpose
  C = transpose(A)

  ! Solve linear system Ax = b (need LAPACK or custom)
  ! Using LAPACK dgesv:
  !   call dgesv(n, nrhs, A, lda, ipiv, b, ldb, info)
  ! For demo, manual Gaussian elimination:
  x = solve_3x3(A, v)
  print *, "Solution: ", x

contains
  function solve_3x3(A, b) result(x)
    real(8), intent(inout) :: A(3,3)
    real(8), intent(in) :: b(3)
    real(8) :: x(3), M(3,4), factor
    integer :: k, j
    M(:,1:3) = A
    M(:,4) = b
    ! Forward elimination
    do k = 1, 2
      do i = k+1, 3
        factor = M(i,k) / M(k,k)
        M(i,:) = M(i,:) - factor * M(k,:)
      end do
    end do
    ! Back substitution
    x(3) = M(3,4) / M(3,3)
    x(2) = (M(2,4) - M(2,3)*x(3)) / M(2,2)
    x(1) = (M(1,4) - M(1,2)*x(2) - M(1,3)*x(3)) / M(1,1)
  end function
end program linalg

随机数

call random_number(x) 用均匀 [0,1) 实数填充 x——适用于标量或数组。call random_seed(size=n) 获取种子大小;random_seed(put=seed) 设置种子以实现可重现性(对测试/调试至关重要)。对于 [a,b] 范围内的整数:a + int(r * (b-a+1))。对于高斯(正态)随机数,使用 Box-Muller 变换(所示)或极坐标法。Fortran 没有内建正态分布生成器——自己实现或使用库。对于蒙特卡洛模拟,设置种子以实现可重现性,然后运行多次试验。random_number 不是加密安全的——安全用途请使用加密库。对于并行代码,每个 image 需要不同的种子。

fortran
program random_demo
  implicit none
  real :: r
  integer :: i, n = 10
  real :: arr(10)
  integer :: seed_size
  integer, allocatable :: seed(:)

  ! Initialize random seed (Fortran 2003: random_seed with no args
  ! uses a processor-dependent seed; for reproducibility, set it)
  call random_seed(size=seed_size)
  allocate(seed(seed_size))
  ! Set a fixed seed for reproducibility
  seed = [(12345 + i*6789, i=1, seed_size)]
  call random_seed(put=seed)

  ! Generate uniform [0,1) random reals
  call random_number(r)
  print *, "Single: ", r

  call random_number(arr)   ! fills entire array
  print *, "Array: ", arr

  ! Generate integers in [a, b]
  block
    integer :: a = 1, b = 6
    integer :: dice
    do i = 1, 5
      call random_number(r)
      dice = a + int(r * (b - a + 1))   ! [1, 6]
      print *, "Dice roll: ", dice
    end do
  end block

  ! Normal (Gaussian) via Box-Muller transform
  block
    real :: u1, u2, z1, z2
    integer :: j
    do j = 1, 5
      call random_number(u1)
      call random_number(u2)
      u1 = max(u1, 1e-10)   ! avoid log(0)
      z1 = sqrt(-2.0 * log(u1)) * cos(2.0 * 3.14159265 * u2)
      z2 = sqrt(-2.0 * log(u1)) * sin(2.0 * 3.14159265 * u2)
      print *, "Gaussian: ", z1, z2
    end do
  end block

  ! Monte Carlo: estimate pi
  block
    integer :: inside = 0, total = 100000
    real :: x, y
    do i = 1, total
      call random_number(x)
      call random_number(y)
      if (x*x + y*y <= 1.0) inside = inside + 1
    end do
    print *, "Pi estimate: ", 4.0 * real(inside) / total
  end block
end program random_demo

数值积分与求根

数值积分:辛普森法则比梯形法则更准确(O(h^4) 误差 vs O(h^2))。使用 interface 块将函数作为参数传递。求根:二分法稳健(如果符号变化总是收敛)但慢(线性收敛);牛顿法快(二次收敛)但需要导数且可能发散。对于生产工作,使用 QUADPACK(积分)或 MINPACK(求根)——经过实战检验的 Fortran 库。将函数作为参数传递时,interface 块是必不可少的——它告诉编译器函数的签名。始终设置最大迭代次数以避免无限循环。用函数值和步长容差检查收敛性。

fortran
program numerical
  implicit none
  real(8) :: a, b, result
  integer :: n

  ! Numerical integration: Simpson's rule
  ! Integrate f(x) = x^2 from 0 to 2 (exact: 8/3 ≈ 2.6667)
  a = 0.0; b = 2.0; n = 1000
  result = simpson(f_sq, a, b, n)
  print *, "Integral of x^2 from 0 to 2: ", result   ! ~2.6667

  ! Integrate sin(x) from 0 to pi (exact: 2.0)
  result = simpson(f_sin, 0.0_8, 3.14159265358979_8, 1000)
  print *, "Integral of sin(x) from 0 to pi: ", result  ! ~2.0

  ! Root finding: bisection method
  ! Find root of f(x) = x^2 - 2 (i.e., sqrt(2) ≈ 1.4142)
  result = bisection(f_x2_minus_2, 0.0_8, 2.0_8, 1e-12_8)
  print *, "sqrt(2) = ", result   ! ~1.41421356

  ! Newton's method (needs derivative)
  result = newton(f_x2_minus_2, fp_x2_minus_2, 1.0_8, 1e-12_8)
  print *, "sqrt(2) via Newton: ", result

contains
  ! Function to integrate: x^2
  function f_sq(x) result(y)
    real(8), intent(in) :: x
    real(8) :: y
    y = x * x
  end function

  function f_sin(x) result(y)
    real(8), intent(in) :: x
    real(8) :: y
    y = sin(x)
  end function

  ! Simpson's rule: integral of f from a to b with n intervals
  function simpson(f, a, b, n) result(integral)
    interface
      function f(x) result(y)
        import
        real(8), intent(in) :: x
        real(8) :: y
      end function
    end interface
    real(8), intent(in) :: a, b
    integer, intent(in) :: n
    real(8) :: integral, h, x
    integer :: i
    h = (b - a) / n
    integral = f(a) + f(b)
    do i = 1, n-1
      x = a + i * h
      if (mod(i, 2) == 0) then
        integral = integral + 2.0 * f(x)
      else
        integral = integral + 4.0 * f(x)
      end if
    end do
    integral = integral * h / 3.0
  end function

  function f_x2_minus_2(x) result(y)
    real(8), intent(in) :: x
    real(8) :: y
    y = x*x - 2.0
  end function

  function fp_x2_minus_2(x) result(y)
    real(8), intent(in) :: x
    real(8) :: y
    y = 2.0 * x
  end function

  ! Bisection: find root in [a, b] (f(a) and f(b) must have opposite signs)
  function bisection(f, a, b, tol) result(root)
    interface
      function f(x) result(y)
        import
        real(8), intent(in) :: x
        real(8) :: y
      end function
    end interface
    real(8), intent(in) :: a, b, tol
    real(8) :: root, fa, fb, mid, fmid
    integer :: iter, maxiter = 100
    fa = f(a); fb = f(b)
    if (fa * fb > 0) stop "No sign change in interval"
    do iter = 1, maxiter
      mid = (a + b) / 2.0
      fmid = f(mid)
      if (abs(fmid) < tol .or. (b - a)/2.0 < tol) then
        root = mid
        return
      end if
      if (fa * fmid < 0) then
        b = mid; fb = fmid
      else
        a = mid; fa = fmid
      end if
    end do
    root = (a + b) / 2.0
  end function

  ! Newton's method: x_{n+1} = x_n - f(x)/f'(x)
  function newton(f, fp, x0, tol) result(root)
    interface
      function f(x) result(y)
        import
        real(8), intent(in) :: x
        real(8) :: y
      end function
      function fp(x) result(y)
        import
        real(8), intent(in) :: x
        real(8) :: y
      end function
    end interface
    real(8), intent(in) :: x0, tol
    real(8) :: root, x, fx, fpx
    integer :: iter, maxiter = 100
    x = x0
    do iter = 1, maxiter
      fx = f(x); fpx = fp(x)
      if (abs(fx) < tol) then
        root = x
        return
      end if
      x = x - fx / fpx
    end do
    root = x
  end function
end program numerical

IEEE 算术与异常

ieee_arithmetic 模块(Fortran 2003)提供 IEEE 754 支持:无穷大、NaN(非数字)、信号/静默 NaN、异常标志和舍入模式。NaN 永远不等于任何东西(包括自身)——使用 ieee_is_nan() 测试。无穷大由溢出或除以零产生。异常标志(ieee_divide_by_zero、ieee_overflow、ieee_underflow、ieee_inexact、ieee_invalid)跟踪是否发生了异常——用 ieee_get_flag 检查,用 ieee_set_flag 清除。ieee_set_halting_mode 控制异常是否暂停程序。舍入模式影响浮点运算。将其用于稳健的数值代码:检测 NaN/Inf、优雅处理异常、控制精度。注意:gfortran 中的 -ffast-math 破坏 IEEE 合规性(不要用于依赖这些特性的代码)。

fortran
program ieee_demo
  use ieee_arithmetic
  implicit none
  real :: a, b, c
  logical :: flag

  ! IEEE special values
  a = ieee_value(a, ieee_positive_inf)   ! +Infinity
  b = ieee_value(b, ieee_negative_inf)   ! -Infinity
  c = ieee_value(c, ieee_quiet_nan)      ! NaN

  print *, "Infinity: ", a                ! Infinity
  print *, "NaN: ", c                     ! NaN
  print *, "Inf > 1e30: ", a > 1e30       ! T
  print *, "NaN == NaN: ", c == c         ! F (NaN is never equal!)

  ! Check for special values
  print *, "is_nan(c): ", ieee_is_nan(c)         ! T
  print *, "is_finite(1.0): ", ieee_is_finite(1.0)  ! T
  print *, "is_inf(a): ", ieee_is_finite(a)      ! F

  ! Operations producing special values
  print *, "1.0/0.0: ", 1.0/0.0           ! Infinity (if -ffast-math off)
  print *, "0.0/0.0: ", 0.0/0.0           ! NaN
  print *, "sqrt(-1.0): ", sqrt(-1.0)     ! NaN

  ! IEEE exception flags
  call ieee_set_halting_mode(ieee_divide_by_zero, .false.)  ! don't halt
  b = 1.0 / 0.0   ! sets divide_by_zero flag, returns Inf
  call ieee_get_flag(ieee_divide_by_zero, flag)
  print *, "Divide by zero occurred: ", flag   ! T

  ! Check and clear flags
  call ieee_set_flag(ieee_all, .false.)   ! clear all flags
  b = 1.0 / 0.0
  call ieee_get_flag(ieee_divide_by_zero, flag)
  print *, "Flag after division: ", flag   ! T
  call ieee_set_flag(ieee_all, .false.)   ! clear

  ! Rounding modes
  call ieee_set_rounding_mode(ieee_nearest)   ! default
  call ieee_set_rounding_mode(ieee_down)      ! round toward -inf
  call ieee_set_rounding_mode(ieee_up)        ! round toward +inf
  call ieee_set_rounding_mode(ieee_to_zero)   ! truncate

  ! Comparing NaN-safe
  if (ieee_unordered(c, 1.0)) print *, "c is unordered (NaN)"
end program ieee_demo
10

Coarray 与并行

基本 coarray 声明

Coarray 是 Fortran 的内建并行模型(F2008)。每个 'image' 是一个并行进程。用 [*] 后缀声明。this_image() 返回秩;num_images() 返回总数。用 x[k] 访问远程。sync all 是屏障。编译器:gfortran(带 -fcoarray=lib)、ifort、Cray。

fortran
program coarray_hello
  use iso_fortran_env, only: real64
  implicit none
  real(real64) :: x[*]  ! coarray — one copy per image
  integer :: me

  me = this_image()
  x = real(me, real64)  ! local assignment

  call co_sum(x, result_image=1)  ! reduce sum to image 1
  if (me == 1) print *, 'Sum =', x

  sync all  ! barrier
end program

远程访问与同步

用 [k] 后缀访问远程 coarray——单侧通信。读写在 sync 之前是非阻塞的。sync all 是全局屏障;sync images([1,2]) 等待特定 image。临界区:用 critical...end critical 加锁/解锁。通过一致地排序 sync 来避免死锁。

fortran
program remote_access
  implicit none
  integer :: val[*], neighbor
  integer :: me, n

  me = this_image()
  n = num_images()
  val = me * 10

  sync all  ! ensure all writes complete

  ! read from neighbor (circular)
  neighbor = merge(1, me + 1, me == n)
  print *, 'Image', me, 'sees neighbor', neighbor, 'value', val[neighbor]

  ! write to image 1 from all
  if (me /= 1) val[1] = val[me]
  sync all
  if (me == 1) print *, 'Image 1 received:', val
end program

集合操作

集合操作:co_sum、co_min、co_max、co_broadcast。它们作用于 coarray 并跨 image 归约/广播。result_image 指定谁获得答案(默认:全部)。source_image 用于广播。始终在读取其他 image 的集合结果之前同步。比带 sync 的手动循环更快。

fortran
program collectives
  use iso_fortran_env, only: real64
  implicit none
  real(real64) :: local_sum, global_sum[*]
  integer :: i

  local_sum = 0.0_real64
  do i = 1, 100
    local_sum = local_sum + real(i * this_image(), real64)
  end do

  global_sum = local_sum
  call co_sum(global_sum, result_image=1)
  if (this_image() == 1) print *, 'Total:', global_sum

  ! other collectives: co_min, co_max, co_broadcast
  call co_broadcast(global_sum, source_image=1)
end program

Coarray 派生类型

派生类型可以是 coarray——每个组件按 image 复制。用 p[k]%field 访问远程组件。coarray 中的 allocatable 组件需要 Fortran 2018+(某些编译器可能不支持)。对于粒子数组,使用 type(particle), allocatable :: particles(:)[:]。

fortran
program coarray_types
  implicit none
  type :: particle
    real :: x, y, z
    real :: mass
  end type
  type(particle) :: p[*]
  integer :: me

  me = this_image()
  p%mass = real(me)
  p%x = real(me) * 0.5

  sync all
  ! access component of remote coarray
  if (me == 1) print *, 'Image 2 mass:', p[2]%mass
end program

可分配 coarray 与团队

可分配 coarray 用 [*] 后缀在所有 image 上同时分配。团队(F2018)将 image 拆分为独立组——每个组有自己的 this_image/num_images。form team 创建团队;change team 进入作用域。适用于层次化并行。编译器支持各异(ifort、gfortran 9+)。

fortran
program alloc_coarrays
  use iso_fortran_env, only: team_type
  implicit none
  integer, allocatable :: data(:)[:]
  type(team_type) :: odd_team, even_team
  integer :: me

  me = this_image()
  allocate(data(100)[*])  ! allocate on all images

  ! F2018 teams — split images into groups
  form team(merge(1, 2, mod(me, 2) == 1), odd_team, even_team)
  if (mod(me, 2) == 1) then
    change team(odd_team)
      ! this_image() and num_images() now refer to team
      data(this_image()) = me
    end team
  end if

  deallocate(data)
end program
11

与 C 的互操作

ISO_C_BINDING 基础

iso_c_binding 提供 C 兼容的 kind(c_int、c_double、c_char 等)。bind(C, name='...') 以特定符号名将 Fortran 暴露给 C。C 字符串需要 c_null_char 终止符。interface 块声明 C 函数签名。分别编译 C 和 Fortran,然后链接在一起。

fortran
program c_interop
  use iso_c_binding, only: c_int, c_double, c_char, c_null_char
  implicit none
  interface
    subroutine c_print(msg) bind(C, name='c_print')
      import :: c_char
      character(kind=c_char), dimension(*) :: msg
    end subroutine
  end interface

  call c_print('Hello from Fortran' // c_null_char)
end program

! Corresponding C:
!   void c_print(const char* msg) { printf("%s\n", msg); }

向 C 传递数组

通过 c_loc(获取 C 指针)和 c_ptr 类型传递数组。对标量 C 参数使用 'value' 属性(按值传递,而非引用)。Fortran 数组是列主序;C 是行主序——转置 2D 数组或记录约定。c_f_pointer 将 C 指针转换回 Fortran 指针。

fortran
program array_interop
  use iso_c_binding, only: c_double, c_int, c_loc, c_f_pointer
  implicit none
  real(c_double), target :: arr(10)
  integer(c_int) :: n
  type(c_ptr) :: ptr

  arr = [(real(i), i=1,10)]
  ptr = c_loc(arr(1))  ! get C pointer

  ! call C function: void process(double* arr, int n);
  call process_c(ptr, size(arr, kind=c_int))

  interface
    subroutine process_c(arr, n) bind(C, name='process')
      import :: c_double, c_int, c_ptr
      type(c_ptr), value :: arr
      integer(c_int), value :: n
    end subroutine
  end interface
end program

C 可互操作类型

带 bind(C) 的类型具有 C 兼容的内存布局——向 C 传递结构体时必需。只允许 C 兼容的 kind(无默认 real/integer)。无 allocatable/pointer 组件。定长 char 数组模拟 C 字符串。顺序很重要——没有 bind(C) 时 Fortran 可能重排组件。

fortran
module data_types
  use iso_c_binding, only: c_double, c_int, c_char
  implicit none

  type, bind(C) :: point
    real(c_double) :: x, y
    integer(c_int) :: id
  end type

  type, bind(C) :: string_holder
    character(kind=c_char, len=1) :: name(64)
  end type
end module

! C equivalent:
!   struct point { double x, y; int id; };
!   struct string_holder { char name[64]; };

从 C 调用 Fortran

带 bind(C, name='...') 的子例程可以从 C 通过该名称调用。C 按值传递的标量使用 'value';数组按引用传递(无 value)。函数结果必须是 C 兼容的标量。使用 C 绑定名称以避免编译器名称修饰(下划线、大小写变化)。

fortran
! Fortran:
module fmod
  use iso_c_binding, only: c_double
  implicit none
contains
  subroutine square_array(arr, n) bind(C, name='square_array')
    integer, value :: n
    real(c_double), intent(inout) :: arr(n)
    arr = arr ** 2
  end subroutine
end module

! C caller:
!   extern void square_array(double* arr, int n);
!   double data[5] = {1, 2, 3, 4, 5};
!   square_array(data, 5);

C 函数指针与回调

abstract interface 声明 C 函数签名。procedure(iface) 接受匹配的函数作为参数。直接传递 C 函数指针。c_funloc 获取 Fortran 过程的 C 地址;c_f_procpointer 将 c_funptr 转换回 Fortran 过程。适用于 qsort 式回调。

fortran
module callbacks
  use iso_c_binding, only: c_funloc, c_funptr, c_int
  implicit none

  abstract interface
    function comparator(a, b) bind(C)
      import :: c_int
      integer(c_int), value :: a, b
      integer(c_int) :: comparator
    end function
  end interface

contains
  subroutine sort_with_c(arr, n, cmp) bind(C)
    integer(c_int), value :: n
    integer(c_int), intent(inout) :: arr(n)
    procedure(comparator) :: cmp
    ! ... use cmp(a, b) to compare ...
  end subroutine
end module

! C side:
!   int descending(int a, int b) { return b - a; }
!   sort_with_c(arr, n, descending);
12

面向对象(extends/final)

类型扩展(继承)

类型扩展 = 继承。'extends(parent)' 声明子类。'class(T)' 是多态的(接受 T 或任何扩展);'type(T)' 是精确匹配。'abstract' + 'deferred' = 抽象方法(必须被重写)。'contains' 引入类型绑定过程。通过用相同名称重新声明过程来重写。

fortran
module shapes
  implicit none
  type, abstract :: shape
    real :: x = 0, y = 0
  contains
    procedure(area_iface), deferred :: area
    procedure :: move => shape_move
  end type

  abstract interface
    function area_iface(this) result(a)
      import :: shape
      class(shape), intent(in) :: this
      real :: a
    end function
  end interface

  type, extends(shape) :: circle
    real :: radius
  contains
    procedure :: area => circle_area
  end type

contains
  subroutine shape_move(this, dx, dy)
    class(shape), intent(inout) :: this
    real, intent(in) :: dx, dy
    this%x = this%x + dx
    this%y = this%y + dy
  end subroutine

  function circle_area(this) result(a)
    class(circle), intent(in) :: this
    real :: a
    a = 3.14159 * this%radius ** 2
  end function
end module

多态与 SELECT TYPE

select type 对多态变量进行运行时类型判别。'type is (T)' 匹配精确类型;'class is (T)' 匹配 T 及其扩展。在块内,变量被视为匹配的类型(访问特定组件)。多态数组通过 class(shape) 持有混合类型——但分配必须逐元素进行。

fortran
module polymorph
  use shapes, only: shape, circle, rectangle
  implicit none
contains
  subroutine describe(s)
    class(shape), intent(in) :: s
    select type(s)
    type is (circle)
      print *, 'Circle radius:', s%radius
    type is (rectangle)
      print *, 'Rectangle:', s%width, 'x', s%height
    class is (shape)
      print *, 'Some shape at:', s%x, s%y
    class default
      print *, 'Unknown type'
    end select
  end subroutine

  subroutine process_all(shapes)
    class(shape), intent(in) :: shapes(:)
    integer :: i
    do i = 1, size(shapes)
      call describe(shapes(i))
      print *, 'Area:', shapes(i)%area()
    end do
  end subroutine
end module

Finalizer 与析构函数

final 过程在变量超出作用域时自动运行——类似 C++ 析构函数。用 'final :: name' 定义。必须是接受 TYPE(不是 class)的子例程——无多态。一个类型可以有多个 finalizer(按秩重载)。用于关闭文件、释放内存、释放资源。不能失败/抛出。

fortran
module resources
  implicit none
  type :: file_handle
    integer :: unit = -1
  contains
    final :: close_file
    procedure :: open
  end type
contains
  subroutine open(this, filename)
    class(file_handle), intent(inout) :: this
    character(*), intent(in) :: filename
    open(newunit=this%unit, file=filename, status='old')
  end subroutine

  subroutine close_file(this)
    type(file_handle), intent(inout) :: this
    if (this%unit /= -1) then
      close(this%unit)
      this%unit = -1
    end if
  end subroutine
end module

program demo
  use resources
  type(file_handle) :: f
  call f%open('data.txt')
  ! ... use file ...
end program  ! f goes out of scope -> close_file called automatically

构造器与分配

带类型名称的通用 interface 充当自定义构造器——重载默认结构构造器。多个过程允许不同的参数集。默认构造器(point(x=..., y=...))仍然可用,除非被重写。多态分配:allocate(circle :: shape_var) 在 class(shape) 变量中创建 circle。

fortran
module points
  implicit none
  type :: point
    real :: x, y
  contains
    procedure :: norm
  end type
  interface point
    procedure new_point
    procedure new_point_polar
  end interface
contains
  function new_point(x, y) result(p)
    real, intent(in) :: x, y
    type(point) :: p
    p%x = x
    p%y = y
  end function

  function new_point_polar(r, theta) result(p)
    real, intent(in) :: r, theta
    type(point) :: p
    p%x = r * cos(theta)
    p%y = r * sin(theta)
  end function

  function norm(this) result(n)
    class(point), intent(in) :: this
    real :: n
    n = sqrt(this%x**2 + this%y**2)
  end function
end module

program use_points
  use points
  type(point) :: a, b
  a = point(1.0, 2.0)         ! cartesian
  b = point(3.0, 0.5)         ! polar (same name, different args)
  print *, a%norm(), b%norm()
end program

抽象类型与模板

抽象类型不能被实例化——只能被扩展。延迟过程必须在具体子类中重写。class(*) 是无限多态——持有任何类型(用 select type 恢复)。此模式实现抽象基类和接口。具体容器(list、stack、queue)扩展并实现延迟过程。

fortran
module container
  implicit none
  type, abstract :: container
  contains
    procedure(add_iface), deferred :: add
    procedure(size_iface), deferred :: size
    procedure :: is_empty => container_is_empty
  end type

  abstract interface
    subroutine add_iface(this, item)
      import :: container
      class(container), intent(inout) :: this
      class(*), intent(in) :: item
    end subroutine
    function size_iface(this) result(n)
      import :: container
      class(container), intent(in) :: this
      integer :: n
    end function
  end interface
contains
  function container_is_empty(this) result(e)
    class(container), intent(in) :: this
    logical :: e
    e = (this%size() == 0)
  end function
end module

! Concrete subclass implements add/size
! class(*) allows storing any type (unlimited polymorphic)
13

参数化派生类型

基本 PDT 声明

参数化派生类型(PDT,F2003)类似 C++ 模板。'kind' 参数是编译时的(每个实例固定);'len' 参数是运行时的(可以用 ':' 延迟)。用 allocate(type(params) :: var) 分配延迟长度类型。PDT 无需预处理器技巧即可实现类型安全的通用容器。

fortran
module pdt_types
  implicit none
  type :: matrix(k, n, m)
    integer, kind :: k = kind(1.0)
    integer, len :: n, m
    real(k) :: data(n, m)
  end type

  ! kind parameters: compile-time (like template params)
  ! len parameters: runtime (like allocatable dimensions)
end module

program use_pdt
  use pdt_types
  implicit none
  type(matrix(kind(1.0), 3, 3)) :: small  ! fixed 3x3
  type(matrix(kind(1.d0), :, :)), allocatable :: big  ! deferred

  small%data = 0.0
  allocate(matrix(kind(1.d0), 100, 100) :: big)
  big%data = 0.d0
  deallocate(big)
end program

带 kind 参数的 PDT

kind 参数化类型让你编写一个适用于多种精度的类型。函数返回类型使用 this%k 匹配。注意过程语法 class(array_t(k=*))——kind 参数类型必需。编译器支持:gfortran 9+、ifort。适用于支持单/双/四倍精度的库。

fortran
module typed_array
  implicit none
  type :: array_t(k)
    integer, kind :: k = kind(1.0)
    real(k), allocatable :: data(:)
  contains
    procedure :: sum_values
  end type
contains
  function sum_values(this) result(s)
    class(array_t(k=*)), intent(in) :: this
    real(this%k) :: s
    s = sum(this%data)
  end function
end module

program test
  use typed_array
  type(array_t(kind(1.0)))  :: float_arr
  type(array_t(kind(1.d0))) :: double_arr

  allocate(float_arr%data(10))
  allocate(double_arr%data(10))
  float_arr%data = 1.0
  double_arr%data = 1.d0

  print *, float_arr%sum_values()
  print *, double_arr%sum_values()
end program

带长度参数的 PDT

长度参数化类型将大小作为类型参数携带。fstr(5) 和 fstr(6) 是不同的类型。过程使用 fstr(*) 接受任何长度。结果类型可以取决于输入长度(c 的长度为 a%n + b%n)。适用于固定大小缓冲区、静态大小数组。编译器支持有限——彻底测试。

fortran
module fixed_string
  implicit none
  type :: fstr(n)
    integer, len :: n
    character(len=n) :: str
  end type
contains
  function concat(a, b) result(c)
    type(fstr(*)), intent(in) :: a, b
    type(fstr(a%n + b%n)) :: c
    c%str = a%str // b%str
  end function
end module

program test
  use fixed_string
  type(fstr(5))  :: a = fstr(5)('Hello')
  type(fstr(6))  :: b = fstr(6)(' World')
  type(fstr(11)) :: c

  c = concat(a, b)
  print *, c%str
end program

PDT 可分配数组

PDT 可以包含 allocatable 组件。kind 参数传播到组件类型(real(this%k))。move_alloc 高效转移分配(无拷贝)。在过程中使用 class(stack(k=*)) 接受任何 kind。带 allocatable 组件的 PDT 将通用类型与动态大小结合。

fortran
module stack_t
  implicit none
  type :: stack(k)
    integer, kind :: k = kind(1.0)
    real(k), allocatable :: data(:)
    integer :: top = 0
  contains
    procedure :: push
    procedure :: pop
    procedure :: is_empty
  end type
contains
  subroutine push(this, val)
    class(stack(k=*)), intent(inout) :: this
    real(this%k), intent(in) :: val
    if (this%top == size(this%data)) then
      block
        real(this%k), allocatable :: tmp(:)
        tmp = [this%data, val]
        call move_alloc(tmp, this%data)
      end block
    else
      this%top = this%top + 1
      this%data(this%top) = val
    end if
  end subroutine
end module

PDT 限制与变通

PDT 支持各异——kind 参数广泛支持;长度参数较少。对于运行时大小调整,优先使用 allocatable 组件而非 len 参数。PDT 数组(type(matrix(4,4)) :: arr(10))可能并非在所有编译器上都有效。在目标编译器上测试。为获得最大可移植性,使用预处理器(#define)或通用 interface。

fortran
! Limitations of PDTs:
! - Not all compilers fully support len parameters
! - I/O of PDTs may not work as expected
! - Some F2003 features (e.g., PDT arrays) have spotty support

! Workaround: use allocatable components instead of len params
module alt_matrix
  implicit none
  type :: matrix_alt(k)
    integer, kind :: k = kind(1.0)
    real(k), allocatable :: data(:,:)
  end type
contains
  function make_matrix(k_, n, m) result(mat)
    integer, intent(in) :: n, m
    ! kind must be compile-time constant — can't pass dynamically
    ! workaround: separate constructors per kind
  end function
end module

! Best practice: use kind params (well-supported), avoid len params
! Use allocatable components for runtime sizing instead
14

子模块

基本子模块结构

子模块(F2008)将接口与实现分离。模块声明接口;子模块提供实现。子模块体的更改不会触发依赖项的重新编译——只有接口更改才会。使用 'module procedure' 实现。非常适合编译开销大的大型库。

fortran
! parent_module.f90
module math_lib
  implicit none
  interface
    module function integrate(f, a, b, n) result(s)
      abstract interface
        real function func_t(x)
          real, intent(in) :: x
        end function
      end interface
      procedure(func_t) :: f
      real, intent(in) :: a, b
      integer, intent(in) :: n
      real :: s
    end function
  end interface
end module

! sub_math.f90
submodule(math_lib) math_impl
  implicit none
contains
  module procedure integrate
    integer :: i
    real :: dx, x
    dx = (b - a) / n
    s = 0.5 * (f(a) + f(b))
    do i = 1, n-1
      x = a + i * dx
      s = s + f(x)
    end do
    s = s * dx
  end procedure
end submodule

每个模块多个子模块

一个模块可以有多个子模块——将实现拆分到多个文件。每个子模块以 'submodule(parent) name' 开头。同一父模块中的过程可以相互调用。这启用增量编译:编辑一个子模块,只重新编译它并链接。适用于非常大的模块。

fortran
! big_lib.f90
module big_lib
  implicit none
  interface
    module subroutine sort(arr)
      real, intent(inout) :: arr(:)
    end subroutine
    module function mean(arr) result(m)
      real, intent(in) :: arr(:)
      real :: m
    end function
    module function stddev(arr) result(s)
      real, intent(in) :: arr(:)
      real :: s
    end function
  end interface
end module

! sub_sort.f90 — one submodule
submodule(big_lib) sort_impl
contains
  module procedure sort
    ! quicksort implementation
  end procedure
end submodule

! sub_stats.f90 — another submodule
submodule(big_lib) stats_impl
contains
  module procedure mean
    m = sum(arr) / size(arr)
  end procedure
  module procedure stddev
    block
      real :: mu
      mu = mean(arr)  ! can call sibling module procedures
      s = sqrt(sum((arr - mu)**2) / (size(arr) - 1))
    end block
  end procedure
end submodule

子模块专用内部过程

子模块可以包含通过父模块接口不可见的私有辅助过程。这隐藏了实现细节同时保持它们共处一地。只有 'module procedure' 实现可通过父模块访问。辅助过程保持内部——比将所有内容放在模块的 contains 块中具有更好的封装。

fortran
module sparse_ops
  implicit none
  interface
    module function sparse_multiply(a, b) result(c)
      ! ... sparse matrix types ...
      real, allocatable :: c(:,:)
    end function
  end interface
end module

submodule(sparse_ops) sparse_impl
  implicit none
contains
  module procedure sparse_multiply
    ! call helper — invisible to module users
    call validate_dimensions(a, b)
    c = multiply_kernel(a, b)
  end procedure

  ! private helper — not exposed via parent module
  subroutine validate_dimensions(a, b)
    ! ...
  end subroutine

  function multiply_kernel(a, b) result(c)
    ! ...
  end function
end submodule

继承模块变量

子模块通过宿主关联访问父模块的变量和类型。它们可以读取和修改模块级状态。这对影响实现行为的配置很有用。对模块变量的更改仍需重新编译子模块(及依赖项)。

fortran
module config
  implicit none
  integer :: verbosity = 0
  interface
    module subroutine log_message(msg)
      character(*), intent(in) :: msg
    end subroutine
  end interface
end module

submodule(config) config_impl
  implicit none
  ! submodule can access parent module's variables
contains
  module procedure log_message
    if (verbosity > 0) then
      print *, '[LOG]', msg
    end if
  end procedure
end submodule

! Usage:
!   use config
!   verbosity = 1
!   call log_message('Starting up')

子模块编译优势

关键优势:子模块中的实现更改不会触发只 'use' 父模块的代码的重新编译。只有子模块本身重新编译。这大大加快了大型 Fortran 项目的增量构建。将频繁更改的实现重构到子模块中;将稳定接口保留在父模块中。

fortran
! Without submodules:
!   module big_mod
!     ... 5000 lines of implementation ...
!   end module
!   ! Any change -> recompile big_mod + all users

! With submodules:
!   module big_mod
!     ! just interfaces (~200 lines)
!   end module
!   submodule(big_mod) impl
!     ! 5000 lines of implementation
!   end submodule
!   ! Change impl body -> recompile only submodule
!   ! Change interface -> recompile everything (unavoidable)

! Build system example (Makefile):
!   big_mod.o: big_mod.f90
!   impl.o: impl.f90 big_mod.o
!   user.o: user.f90 big_mod.o  ! NOT impl.o
!   app: user.o impl.o big_mod.o
!   	$(FC) -o app user.o impl.o big_mod.o
15

IEEE 浮点

IEEE 异常与标志

ieee_exceptions 提供对 IEEE 异常标志(overflow、underflow、divide_by_zero、invalid、inexact)的访问。ieee_get_flag 读取;ieee_set_flag 清除。ieee_set_halting_mode 控制异常是否暂停程序。ieee_arithmetic 提供 ieee_is_nan、ieee_is_finite、ieee_is_negative 等。始终在关键计算后检查标志。

fortran
program ieee_exceptions
  use ieee_exceptions
  use ieee_arithmetic, only: ieee_is_nan, ieee_is_finite
  implicit none
  real :: x, y
  logical :: overflow_flag

  call ieee_set_halting_mode(ieee_overflow, .false.)
  x = huge(x) * 10  ! overflow -> Inf, no halt

  call ieee_get_flag(ieee_overflow, overflow_flag)
  print *, 'Overflow occurred:', overflow_flag

  y = 0.0 / 0.0  ! NaN
  print *, 'x is finite:', ieee_is_finite(x)
  print *, 'y is NaN:', ieee_is_nan(y)

  call ieee_set_flag(ieee_all, .false.)  ! clear all flags
end program

NaN 与 Inf 处理

Inf 和 NaN 是 IEEE 特殊值。NaN != NaN(使用 ieee_is_nan 测试)。Inf 通过算术传播。产生 NaN 的运算:0/0、Inf-Inf、0*Inf、sqrt(-1)。用 -ffpe-trap=invalid,zero,overflow 编译以在这些情况下暂停(调试)。生产代码应在风险运算后检查 NaN。

fortran
program nan_inf
  use ieee_arithmetic, only: ieee_value, ieee_nan, &
                             ieee_positive_inf, ieee_negative_inf
  implicit none
  real :: pos_inf, neg_inf, nan_val

  pos_inf = ieee_value(0.0, ieee_positive_inf)
  neg_inf = ieee_value(0.0, ieee_negative_inf)
  nan_val = ieee_value(0.0, ieee_nan)

  print *, 'Inf - Inf =', pos_inf - pos_inf  ! NaN
  print *, '0 * Inf =', 0.0 * pos_inf         ! NaN
  print *, 'Inf > 1e30:', pos_inf > 1e30       ! T
  print *, 'NaN == NaN:', nan_val == nan_val   ! F (always!)

  ! generate via arithmetic
  pos_inf = 1.0 / 0.0  ! may halt without -fno-trapping-math
end program

舍入模式

IEEE 支持 4 种舍入模式:nearest(默认)、down、up、to zero。ieee_set_rounding_mode 在运行时更改它。适用于区间算术(通过向上/向下舍入计算上/下界)。影响所有后续 FP 运算直到更改。完成后恢复为 nearest。某些编译器假设 nearest 进行优化——谨慎使用。

fortran
program rounding_modes
  use ieee_arithmetic, only: ieee_set_rounding_mode, &
                             ieee_nearest, ieee_down, ieee_up, ieee_to_zero
  implicit none
  real :: x, y, result

  x = 1.0 / 3.0
  print *, 'Nearest:', x

  call ieee_set_rounding_mode(ieee_down)
  result = 1.0 / 3.0
  print *, 'Down:', result

  call ieee_set_rounding_mode(ieee_up)
  result = 1.0 / 3.0
  print *, 'Up:', result

  call ieee_set_rounding_mode(ieee_to_zero)
  result = 1.0 / 3.0
  print *, 'To zero:', result

  call ieee_set_rounding_mode(ieee_nearest)  ! restore default
end program

IEEE 特性查询

ieee_features 和 ieee_arithmetic 查询函数让你检查编译器/平台支持哪些 IEEE 特性。ieee_support_nan、ieee_support_inf、ieee_support_rounding、ieee_support_datatype 等。适用于可移植代码——在非 IEEE 系统上优雅降级。大多数现代系统支持所有特性。

fortran
program ieee_inquiry
  use ieee_features
  use ieee_arithmetic, only: ieee_support_nan, ieee_support_inf, &
                             ieee_support_rounding, ieee_nearest
  implicit none

  if (ieee_support_nan(1.0)) print *, 'NaN supported'
  if (ieee_support_inf(1.0)) print *, 'Inf supported'
  if (ieee_support_rounding(ieee_nearest, 1.0)) &
    print *, 'Nearest rounding supported'

  ! Check halting mode
  block
    use ieee_exceptions
    logical :: halting
    call ieee_get_halting_mode(ieee_overflow, halting)
    print *, 'Overflow halts:', halting
  end block
end program

控制 FP 环境

按部分控制 FP 行为:为风险代码禁用暂停,之后检查标志,然后恢复。ieee_all 匹配所有异常。模式:清除标志、运行计算、检查标志、处理错误。对于生产,优先使用显式检查(ieee_is_nan)而非基于标志的检测——标志可能被无关代码设置。

fortran
program fp_env
  use ieee_arithmetic
  use ieee_exceptions
  implicit none
  real :: a, b, c

  ! Save/restore FP state
  block
    use iso_fortran_env, only: real32
    real(real32) :: saved_state
    ! (no portable save/restore in standard — use compiler intrinsics)
  end block

  ! Disable halting for a risky section
  call ieee_set_halting_mode(ieee_all, .false.)
  call ieee_set_flag(ieee_all, .false.)

  a = sqrt(-1.0)  ! NaN, no halt
  b = 1.0 / 0.0   ! Inf, no halt

  if (ieee_is_nan(a)) then
    print *, 'Got NaN, using fallback'
    a = 0.0
  end if

  ! Re-enable halting
  call ieee_set_halting_mode(ieee_divide_by_zero, .true.)
  call ieee_set_halting_mode(ieee_invalid, .true.)
end program
16

性能与优化

数组顺序与连续性

Fortran 数组是列主序:a(i,j) 和 a(i+1,j) 在内存中相邻。循环顺序很重要:最内层循环应遍历第一个索引。错误顺序导致缓存未命中——10 倍以上减速。数组语法(sum、matmul)让编译器优化。使用连续数组(非指针)以获得最佳性能。

fortran
program array_perf
  implicit none
  real, allocatable :: a(:,:), b(:,:)
  integer :: i, j, n
  real :: s

  n = 1000
  allocate(a(n,n), b(n,n))

  ! GOOD: column-major access (Fortran is column-major)
  do j = 1, n
    do i = 1, n
      s = s + a(i,j)
    end do
  end do

  ! BAD: row-major access (cache misses)
  do i = 1, n
    do j = 1, n
      s = s + a(i,j)
    end do
  end do

  ! BEST: array syntax (compiler optimizes)
  s = sum(a)
end program

纯过程与元素过程

pure 过程没有副作用——编译器可以优化、并行化和重排调用。elemental 过程适用于标量和数组(自动向量化)。pure elemental 结合两者。将这些用于数学函数。限制:无 I/O、无全局修改、所有输入使用 intent(in)。启用比常规过程更好的优化。

fortran
module math_ops
  implicit none
contains
  ! pure: no side effects, enables optimization
  pure function square(x) result(y)
    real, intent(in) :: x
    real :: y
    y = x * x
  end function

  ! elemental: works on scalars and arrays element-wise
  pure elemental function clamp(x, lo, hi) result(y)
    real, intent(in) :: x, lo, hi
    real :: y
    y = max(lo, min(x, hi))
  end function
end module

program use_math
  use math_ops
  implicit none
  real :: arr(100), scalar

  arr = clamp(arr, 0.0, 1.0)  ! applies to each element
  scalar = clamp(1.5, 0.0, 1.0)  ! also works on scalar
  arr = square(arr)  ! elemental too (if declared)
end program

编译器优化标志

生产从 -O2 -march=native 开始。-O3 可能有帮助也可能有害——基准测试。-Ofast 破坏 IEEE 合规性(如果 NaN/Inf 重要则不要使用)。-flto 启用跨文件内联(编译较慢,运行更快)。在调试构建中使用 -fcheck=all 和 -ffpe-trap 捕获错误。配置文件引导优化(-fprofile-use)提供 5-15% 加速。

fortran
! gfortran optimization flags:
!   -O0   no optimization (debug)
!   -O1   basic optimization
!   -O2   standard optimization (recommended)
!   -O3   aggressive (may increase code size)
!   -Ofast  -O3 + -ffast-math (breaks IEEE compliance)
!   -march=native  use CPU's full instruction set
!   -flto  link-time optimization (cross-file inlining)
!   -fopenmp  enable OpenMP pragmas
!   -fprofile-generate/use  profile-guided optimization

! Example Makefile:
!   FC = gfortran
!   FFLAGS = -O2 -march=native -Wall -fcheck=all
!   FFLAGS_RELEASE = -O3 -march=native -flto -fno-trapping-math
!   FFLAGS_DEBUG = -O0 -g -fcheck=all -fbacktrace -ffpe-trap=invalid,zero

! ifort equivalents:
!   -O2, -O3, -xHost (= -march=native), -ipo (= -flto)

OpenMP 并行

OpenMP 通过指令(!$omp)添加并行性。'parallel do' 并行化下一个循环。'reduction(+:s)' 安全处理求和。用 -fopenmp(gfortran)或 -qopenmp(ifort)编译。通过 OMP_NUM_THREADS 环境变量设置线程数。最适合具有独立迭代的 CPU 密集型循环。注意虚假共享和负载不平衡。

fortran
program openmp_demo
  use omp_lib
  implicit none
  real, allocatable :: a(:), b(:), c(:)
  integer :: i, n, nthreads

  n = 10000000
  allocate(a(n), b(n), c(n))
  a = 1.0; b = 2.0

  !$omp parallel
  if (omp_get_thread_num() == 0) then
    nthreads = omp_get_num_threads()
    print *, 'Threads:', nthreads
  end if
  !$omp end parallel

  !$omp parallel do
  do i = 1, n
    c(i) = a(i) + b(i)
  end do
  !$omp end parallel do

  ! reduction
  block
    real :: s
    s = 0.0
    !$omp parallel do reduction(+:s)
    do i = 1, n
      s = s + c(i)
    end do
    !$omp end parallel do
    print *, 'Sum:', s
  end block
end program

性能分析与热点检测

system_clock 提供可移植的计时。对于严肃的分析,使用 gprof(用 -pg 编译)、perf(Linux)或 Intel VTune。优化前先分析——意外很常见。专注于热点(占 80% 时间的 20% 代码)。首先优化最内层循环。始终在更改前后进行基准测试——直觉通常是错的。

fortran
! Compile with profiling:
!   gfortran -pg -O2 prog.f90 -o prog
!   ./prog  (generates gmon.out)
!   gprof prog gmon.out > profile.txt

! Or use perf (Linux):
!   perf record ./prog
!   perf report

! Or gprofng (modern):
!   gprofng collect app ./prog
!   gprofng analyze

! Manual timing:
program timing
  use iso_fortran_env, only: real64, int64
  implicit none
  integer(int64) :: start, finish, rate
  real(real64) :: elapsed
  real, allocatable :: a(:)
  integer :: i

  allocate(a(10000000))
  call system_clock(start, rate)
  do i = 1, 100
    a = a + 1.0
  end do
  call system_clock(finish)
  elapsed = real(finish - start, real64) / real(rate, real64)
  print *, 'Elapsed:', elapsed, 'sec'
end program
17

混合语言编程

Fortran 调用 C 库

将 C 库函数包装在 Fortran interface 模块中。使用 bind(C, name='...') 链接到精确的 C 符号。用带 'value' 的 c_ptr 传递指针。对于函数指针参数(如 qsort 的比较器),使用 abstract interface。这让你可以调用任何 C 函数——包括 libc、BLAS、系统调用。

fortran
module c_glue
  use iso_c_binding
  implicit none
  interface
    function c_malloc(size) bind(C, name='malloc')
      import :: c_ptr, c_size_t
      integer(c_size_t), value :: size
      type(c_ptr) :: c_malloc
    end function

    subroutine c_free(ptr) bind(C, name='free')
      import :: c_ptr
      type(c_ptr), value :: ptr
    end subroutine

    subroutine c_qsort(base, nmemb, size, compar) bind(C, name='qsort')
      import :: c_ptr, c_size_t, c_int
      type(c_ptr), value :: base
      integer(c_size_t), value :: nmemb, size
      abstract interface
        function compar_fn(a, b) bind(C)
          import :: c_ptr
          type(c_ptr), value :: a, b
          integer(c_int) :: compar_fn
        end function
      end interface
      procedure(compar_fn) :: compar
    end subroutine
  end interface
end module

通过 extern C 实现 C++ 互操作

C++ 对符号进行名称修饰,因此将 C++ 函数包装在 extern "C" 块中。Fortran interface 然后绑定到未修饰的名称。用 -lstdc++(gfortran)链接或使用 C++ 链接器。这让你可以从 Fortran 使用 C++ 库(STL、Boost、Qt)。对于 C++ 类,编写扁平的 C API 包装器,然后从 Fortran 调用。

fortran
! C++ side (wrapper.cpp):
!   extern "C" {
!     void cpp_process(double* data, int n) {
!       std::vector<double> v(data, data+n);
!       std::sort(v.begin(), v.end());
!       std::copy(v.begin(), v.end(), data);
!     }
!   }

! Fortran side:
module cpp_glue
  use iso_c_binding
  implicit none
  interface
    subroutine cpp_process(data, n) bind(C, name='cpp_process')
      import :: c_double, c_int
      real(c_double), intent(inout) :: data(*)
      integer(c_int), value :: n
    end subroutine
  end interface
end module

program use_cpp
  use cpp_glue
  implicit none
  real(c_double) :: arr(10) = [5.d0, 3.d0, 8.d0, 1.d0, 9.d0, &
                                2.d0, 7.d0, 4.d0, 6.d0, 0.d0]
  call cpp_process(arr, size(arr, kind=c_int))
  print *, arr
end program

! Build: gfortran use_cpp.f90 wrapper.cpp -lstdc++ -o app

通过 f2py 实现 Python 互操作

f2py(numpy 的一部分)自动为 Fortran 生成 Python 绑定。它读取 intent 属性并创建正确的 Python/numpy 接口。intent(out) 成为返回值。数组映射到 numpy 数组(连续时零拷贝。非常适合从 Python 调用的性能关键数值代码。对 F2003+ 特性支持有限。

fortran
! Fortran module (myfuncs.f90):
module myfuncs
  implicit none
contains
  subroutine compute(arr, n, result)
    integer, intent(in) :: n
    real(8), intent(in) :: arr(n)
    real(8), intent(out) :: result
    result = sum(arr**2)
  end subroutine

  function fast_sin(x) result(y)
    real(8), intent(in) :: x
    real(8) :: y
    y = sin(x)
  end function
end module

! Build Python extension:
!   f2py -c myfuncs.f90 -m myfuncs
!   (creates myfuncs.cpython-*.so)

! Python usage:
!   import numpy as np
!   import myfuncs
!   arr = np.array([1.0, 2.0, 3.0])
!   result = myfuncs.myfuncs.compute(arr)
!   y = myfuncs.myfuncs.fast_sin(1.5)

共享库与动态加载

使用 dlopen/dlsym(POSIX)或 LoadLibrary/GetProcAddress(Windows)进行运行时加载插件。包装在 iso_c_binding interface 中。用 c_f_procpointer 将 c_ptr 转换为过程指针。这启用插件架构——在运行时加载不同实现。跨平台:使用 #ifdef 处理特定操作系统的加载器。

fortran
module dynamic_loader
  use iso_c_binding
  implicit none
  interface
    function dlopen(filename, flag) bind(C, name='dlopen')
      import :: c_ptr, c_char, c_int
      character(kind=c_char), dimension(*) :: filename
      integer(c_int), value :: flag
      type(c_ptr) :: dlopen
    end function

    function dlsym(handle, name) bind(C, name='dlsym')
      import :: c_ptr, c_char
      type(c_ptr), value :: handle
      character(kind=c_char), dimension(*) :: name
      type(c_ptr) :: dlsym
    end function

    function dlclose(handle) bind(C, name='dlclose')
      import :: c_ptr, c_int
      type(c_ptr), value :: handle
      integer(c_int) :: dlclose
    end function
  end interface
end module

program plugin
  use dynamic_loader
  implicit none
  type(c_ptr) :: lib, sym
  integer, parameter :: RTLD_NOW = 2

  lib = dlopen('./plugin.so' // c_null_char, RTLD_NOW)
  if (.not. c_associated(lib)) then
    print *, 'Failed to load library'
    stop 1
  end if

  sym = dlsym(lib, 'init' // c_null_char)
  ! call init via c_f_procpointer...
  print *, dlclose(lib)
end program

构建系统集成

CMake 很好地处理混合语言构建。在 project() 中声明所有语言。设置每语言标志。按依赖顺序链接库。CMake 自动跟踪 Fortran 模块依赖(无需手动 .mod 排序)。对于 C++ 标准库链接,每个平台使用正确的标志。如果需要 Fortran 运行时,使用 Fortran 作为主链接器。

fortran
# CMakeLists.txt for mixed Fortran/C/C++ project:
cmake_minimum_required(VERSION 3.18)
project(mixed LANGUAGES Fortran C CXX)

set(CMAKE_Fortran_FLAGS "-O2 -fopenmp")
set(CMAKE_C_FLAGS "-O2")
set(CMAKE_CXX_FLAGS "-O2 -std=c++17")

add_library(fortran_lib STATIC mymod.f90)
add_library(c_lib STATIC helper.c)
add_library(cpp_lib STATIC wrapper.cpp)

# Mixed-language executable
add_executable(app main.f90)
target_link_libraries(app fortran_lib c_lib cpp_lib)

# Fortran needs C runtime
if(APPLE)
  target_link_libraries(app "-lc++")
else()
  target_link_libraries(app stdc++)
endif()

# Module dependency tracking (CMake handles automatically)
#   fortran_lib depends on its own .mod files
#   app depends on fortran_lib's .mod files
18

现代 I/O

Newunit 与安全文件处理

newunit= 避免单元号冲突——编译器选择空闲号码。始终在 open/read/write 后检查 iostat——非零表示错误。iomsg 给出人类可读的错误消息。action=('read'、'write'、'readwrite')防止意外误用。status=('old'、'new'、'replace'、'scratch'、'unknown')控制文件创建。

fortran
program safe_io
  implicit none
  integer :: u, ios
  character(256) :: msg

  ! newunit: compiler picks an unused unit number
  open(newunit=u, file='data.txt', status='old', action='read', &
       iostat=ios, iomsg=msg)
  if (ios /= 0) then
    print *, 'Open failed:', trim(msg)
    stop 1
  end if

  ! read with error handling
  read(u, *, iostat=ios, iomsg=msg) some_value
  if (ios /= 0) then
    print *, 'Read failed:', trim(msg)
  end if

  close(u)
contains
  integer function some_value()
    some_value = 0
  end function
end program

流 I/O(二进制)

流访问(F2003)提供类似 C 的面向字节 I/O——无记录标记。access='stream' 启用它。pos= 在特定字节偏移处读/写。form='unformatted' 用于二进制。非常适合可互操作的二进制文件(从 C/Python 读取 Fortran 写入的文件)。默认顺序访问使用记录标记(不可移植)。

fortran
program stream_io
  implicit none
  integer :: u, i
  real, allocatable :: data(:)

  ! stream access = byte-oriented (like C fread/fwrite)
  open(newunit=u, file='data.bin', access='stream', &
       form='unformatted', status='replace')

  data = [(real(i), i=1,100)]
  write(u) data  ! binary, no record markers
  close(u)

  ! read back
  allocate(data(100))
  open(newunit=u, file='data.bin', access='stream', &
       form='unformatted', status='old', action='read')
  read(u) data
  close(u)

  ! position-based access
  open(newunit=u, file='data.bin', access='stream', &
       form='unformatted', status='old')
  read(u, pos=21) data(1)  ! read 5th real (4 bytes each)
  close(u)
end program

派生类型 I/O

用户定义派生类型 I/O(F2003)让你控制类型如何被读/写。定义具有特定签名的子例程并通过 'generic :: write(formatted)' 绑定。DT 格式代码触发它。启用自定义序列化(CSV、类 JSON、二进制)。iotype 为 'LISTDIRECTED'、'NAMELIST' 或 'DT'(用于格式化 I/O)。

fortran
module person_type
  implicit none
  type :: person
    character(20) :: name
    integer :: age
    real :: height
  end type
contains
  ! custom formatted I/O via DT format
  subroutine write_person(dtv, unit, iotype, v_list, iostat, iomsg)
    class(person), intent(in) :: dtv
    integer, intent(in) :: unit
    character(*), intent(in) :: iotype
    integer, intent(in) :: v_list(:)
    integer, intent(out) :: iostat
    character(*), intent(inout) :: iomsg
    write(unit, '(a,",",i0,",",f0.2)', iostat=iostat) &
      trim(dtv%name), dtv%age, dtv%height
  end subroutine
end module

program use_dtv
  use person_type
  implicit none
  type(person) :: p = person('Alice', 30, 5.7)

  ! DT format triggers custom writer
  print "(DT)", p  ! calls write_person
end program

Namelist I/O

namelist 为变量组提供人类可读的、基于名称的 I/O。格式:&group_name var=value, ... /。读取只更新文件中存在的变量——其他保持其值。非常适合配置文件——用户编辑文本,无需解析器。限制:某些编译器不支持注释,类型支持有限。

fortran
program namelist_demo
  implicit none
  integer :: n_iterations = 100
  real :: tolerance = 1.0e-6
  character(50) :: output_file = 'results.txt'
  logical :: verbose = .true.

  namelist /config/ n_iterations, tolerance, output_file, verbose

  ! write namelist
  open(newunit=u, file='config.nml', status='replace')
  write(u, nml=config)
  close(u)

  ! read namelist (only specified vars are updated)
  open(newunit=u, file='config.nml', status='old', action='read')
  read(u, nml=config)
  close(u)

  print *, 'Iterations:', n_iterations
end program

! config.nml format:
! &config
!   n_iterations = 500
!   tolerance = 1.0e-8
!   verbose = .false.
! /

异步 I/O

异步 I/O(F2003)将 I/O 与计算重叠。write(..., asynchronous='yes') 启动非阻塞操作。wait(unit) 阻塞直到完成。适用于大型数据集——在计算下一块时开始写入。编译器支持各异。使用 inquire(unit=u, pending=...) 检查状态。与双缓冲配对实现流水线。

fortran
program async_io
  implicit none
  integer :: u1, u2, ios
  real, allocatable :: a(:), b(:)

  allocate(a(1000000), b(1000000))
  a = 1.0; b = 2.0

  open(newunit=u1, file='a.bin', access='stream', form='unformatted', &
       asynchronous='yes')
  open(newunit=u2, file='b.bin', access='stream', form='unformatted', &
       asynchronous='yes')

  ! non-blocking writes
  write(u1, asynchronous='yes') a
  write(u2, asynchronous='yes') b

  ! do other work while I/O proceeds...

  ! wait for completion
  wait(u1)
  wait(u2)

  close(u1); close(u2)
end program
19

调试与分析

编译时调试标志

使用 -fcheck=all 进行边界检查(捕获差一错误)。-ffpe-trap 在 NaN/Inf/overflow 时暂停——对数值代码 invaluable。-finit-real=nan 使未初始化变量可见(它们以 NaN 传播)。-fbacktrace 在崩溃时打印堆栈跟踪。始终使用这些标志调试;生产构建时移除(它们会减慢代码)。

fortran
! gfortran debug build:
!   gfortran -O0 -g -fcheck=all -fbacktrace -ffpe-trap=invalid,zero,overflow \
!            -Wall -Wextra -Wpedantic -finit-real=nan prog.f90

! Flag explanations:
!   -O0          no optimization (easier debugging)
!   -g           debug symbols (for gdb)
!   -fcheck=all  bounds, pointer, recursion checks
!   -fbacktrace  print stack trace on error
!   -ffpe-trap   halt on FP exceptions (invalid, zero, overflow)
!   -finit-real=nan  initialize reals to NaN (catch uninitialized)
!   -finit-integer=-9999  initialize integers to sentinel
!   -Wall -Wextra  more warnings

! Runtime error message:
!   At line 42 of file prog.f90
!   Fortran runtime error: Array bound out of bounds for dimension 1

用 iostat 进行错误处理

iostat:负 = EOF,零 = 成功,正 = 错误。iomsg 提供详细信息。先读入字符串,然后解析——将 I/O 错误与解析错误分开。跟踪行号以提供有用的错误消息。始终显式处理错误——静默失败难以调试。使用带非零代码的 stop 向脚本发出失败信号。

fortran
program robust_io
  implicit none
  integer :: u, ios, line_num
  character(256) :: msg, line
  real :: value

  open(newunit=u, file='data.txt', status='old', action='read', &
       iostat=ios, iomsg=msg)
  if (ios /= 0) call error_exit('Open: ' // trim(msg))

  line_num = 0
  do
    line_num = line_num + 1
    read(u, '(a)', iostat=ios) line
    if (ios < 0) exit  ! EOF
    if (ios > 0) then
      print *, 'Read error at line', line_num
      cycle
    end if

    read(line, *, iostat=ios, iomsg=msg) value
    if (ios /= 0) then
      print *, 'Parse error at line', line_num, ':', trim(msg)
      cycle
    end if
    ! process value
  end do
  close(u)
contains
  subroutine error_exit(m)
    character(*), intent(in) :: m
    print *, 'ERROR:', m
    stop 1
  end subroutine
end program

用于 Fortran 的 GDB

GDB 支持 Fortran:数组切片、派生类型组件、模块过程(名称格式:modname__procname)。使用 -g -O0 获得最佳调试体验。'display' 在每次停止时自动打印变量——对循环监视有用。'info locals' 显示所有局部变量。对于模块变量,使用 'print modname::varname'。

fortran
! Compile: gfortran -g -O0 prog.f90 -o prog
! Start: gdb ./prog

! Common GDB commands for Fortran:
!   (gdb) break main           break at main
!   (gdb) break prog.f90:42     break at line 42
!   (gdb) break mymod__my_sub   break at subroutine (note double underscore)
!   (gdb) run                   start program
!   (gdb) next                  step over
!   (gdb) step                  step into
!   (gdb) print arr(5)          print array element
!   (gdb) print arr(1:10)       print array slice
!   (gdb) print mat(2,3)        print 2D array element
!   (gdb) print p%name          print derived type component
!   (gdb) display x             watch variable (auto-print on stop)
!   (gdb) backtrace             show call stack
!   (gdb) info locals           show all local variables
!   (gdb) continue              resume execution

gprof 分析

gprof 在执行期间采样程序计数器。用 -pg 编译所有源文件以获得完整的配置文件。平面配置文件显示时间花在哪里;调用图显示调用层次。专注于高 'self' 时间的函数——那是优化有帮助的地方。注意:-pg 改变计时——类似配置文件的代码在生产中可能表现不同。

fortran
! Compile with profiling:
!   gfortran -pg -O2 prog.f90 -o prog
! Run:
!   ./prog
!   (creates gmon.out)
! Analyze:
!   gprof prog gmon.out > profile.txt
!   gprof prog gmon.out | less

! profile.txt sections:
!   - Flat profile: time per function (self + cumulative)
!   - Call graph: who called whom, how many times
!   - Index: function cross-references

! Key columns:
!   %time  percentage of total time
!   cumulative  running total
!   self    seconds in function (excluding children)
!   calls   number of calls
!   self/call  average time per call (self)

! For multi-threaded: use gprofng (modern) or perf

性能基准测试

正确基准测试:先预热(缓存效应),运行多次迭代,取最小值(最少噪声)。带 count_rate 的 system_clock 给出挂钟时间。对于数组操作报告吞吐量(元素/秒)。在同一台机器上用相同标志比较实现。注意:编译器可能优化掉'未使用'的结果——使用结果(例如,print sum)以防止这种情况。

fortran
program benchmark
  use iso_fortran_env, only: real64, int64
  implicit none
  integer, parameter :: n = 1000000
  integer, parameter :: niter = 100
  real(real64), allocatable :: a(:), b(:), c(:)
  integer(int64) :: start, finish, rate
  real(real64) :: t_start, t_end, min_time
  integer :: i, iter

  allocate(a(n), b(n), c(n))
  a = 1.0_real64; b = 2.0_real64

  ! warmup (cache, JIT-like effects)
  do i = 1, n
    c(i) = a(i) + b(i)
  end do

  min_time = huge(min_time)
  call system_clock(count_rate=rate)
  do iter = 1, niter
    call system_clock(start)
    do i = 1, n
      c(i) = a(i) + b(i)
    end do
    call system_clock(finish)
    t_start = real(start, real64) / rate
    t_end = real(finish, real64) / rate
    min_time = min(min_time, t_end - t_start)
  end do

  print *, 'Best time:', min_time * 1e6, 'us'
  print *, 'Throughput:', real(n) / min_time / 1e9, 'G elem/s'
end program
20

现代 Fortran

自由格式

现代 Fortran(90+)使用自由格式:无列限制。注释以 ! 开头。语句可以用 & 跨越多行。implicit none 对类型安全是强制性的。比固定格式的 Fortran 77 可读性高得多。

fortran
program modern
    implicit none
    integer :: i
    do i = 1, 10
        print *, "Value:", i
    end do
end program modern
! Free form: no column restrictions
! Comments start with !

模块

模块将相关过程和数据分组。use 导入模块。contains 将模块级声明与过程分开。模块提供显式接口,启用类型检查。优先使用模块而非外部过程。

fortran
module math_utils
    implicit none
    contains
    function square(x) result(y)
        real, intent(in) :: x
        real :: y
        y = x * x
    end function square
end module math_utils

program test
    use math_utils
    print *, square(3.0)  ! 9.0
end program test

派生类型

派生类型是用户定义的数据结构(结构体)。% 访问组件。类型绑定过程启用 OOP。构造器创建实例。派生类型可以有默认值并扩展其他类型(继承)。用于复杂数据建模。

fortran
type :: Point
    real :: x, y
end type Point
type(Point) :: p
p = Point(1.0, 2.0)  ! Constructor
p%x = 3.0  ! Component access
print *, p%x, p%y
! Type-bound procedures (OOP)
type :: Circle
    real :: radius
contains
    procedure :: area => circle_area
end type

Intent 属性

intent(in) 参数是只读的(不能修改)。intent(out) 是只写的(由过程设置)。intent(inout) 是读写的。编译器检查 intent 违规。提高代码清晰度并启用优化。始终指定 intent。

fortran
subroutine process(input, output, inout)
    integer, intent(in) :: input    ! Read-only
    integer, intent(out) :: output  ! Write-only
    integer, intent(inout) :: inout ! Read-write
    output = input * 2
    inout = inout + 1
end subroutine

Pure 与 Elemental

pure 函数没有副作用(无 I/O、无可变状态)。编译器可以优化它们。elemental 函数自动适用于标量和数组。它们默认是 pure 的。非常适合数学运算。启用并行执行。

fortran
pure function square(x) result(y)
    real, intent(in) :: x
    real :: y
    y = x * x
end function
! Elemental: works on scalars and arrays
elemental function double_it(x) result(y)
    real, intent(in) :: x
    real :: y
    y = 2.0 * x
end function
! double_it([1,2,3]) returns [2,4,6]
21

数组

数组声明

Fortran 数组默认从 1 开始索引。用 (0:) 指定自定义下界。列主序(第一个索引变化最快)。allocatable 数组在堆上分配,必须被释放。数组常量使用 [ ]。由于描述符,Fortran 数组比 C 数组更高效。

fortran
! Static arrays
real :: a(10)           ! 1D, indices 1-10
real :: b(0:9)          ! 1D, indices 0-9
real :: c(3, 4)         ! 2D, 3 rows x 4 cols
! Allocatable (dynamic)
real, allocatable :: d(:)
allocate(d(100))        ! Allocate
deallocate(d)           ! Free
! Array constants
integer :: nums(5) = [1, 2, 3, 4, 5]

数组操作

Fortran 支持整体数组操作:+、-、*、/、**。元素级数学无需循环。内建函数:sum、product、maxval、minval、any、all、count。由于向量化,比循环快得多。这是 Fortran 在数值计算方面的优势。

fortran
real :: a(5) = [1, 2, 3, 4, 5]
real :: b(5)
b = a * 2          ! Element-wise: [2,4,6,8,10]
b = a + 1          ! [2,3,4,5,6]
print *, sum(a)    ! 15
print *, maxval(a) ! 5
print *, any(a > 3)  ! .true.
print *, count(a > 2) ! 3

数组片段

数组片段使用 (start:end:stride) 语法。:: 表示默认(1 到末尾,步长 1)。负步长反向。多维片段适用于任何维度。片段可以传递给过程。非常强大,切片无需拷贝。

fortran
real :: a(10) = [(i, i=1,10)]
print *, a(3:7)     ! Elements 3 to 7
print *, a(::2)     ! Every other: [1,3,5,7,9]
print *, a(2:8:2)   ! Stride 2: [2,4,6,8]
real :: m(3,3)
m(:, 2)             ! Second column
m(1, :)             ! First row

where 构造

where 是数组级条件赋值。类似向量化的 if。elsewhere 处理 false 的情况。比循环更高效,因为它可以被向量化。用于数组上的元素级条件操作。

fortran
real :: a(5) = [1, -2, 3, -4, 5]
where (a > 0)
    a = a * 2       ! Double positives
elsewhere
    a = 0           ! Zero negatives
end where
! Result: [2, 0, 6, 0, 10]
! Equivalent to a loop with if

动态分配

allocatable 数组是动态大小的。allocate 创建,deallocate 释放。Fortran 2003+ 在作用域结束时自动释放。用 allocated() 检查分配状态。allocatable 数组在赋值时自动重新分配(Fortran 2003+)。比 C 的 malloc/free 安全得多。

fortran
real, allocatable :: matrix(:,:)
integer :: n
n = 100
allocate(matrix(n, n))
matrix = 0.0  ! Initialize all to 0
! ... use matrix ...
deallocate(matrix)
! Automatic deallocation at end of scope
! (Fortran 2003+)
22

I/O 操作

格式化输出

格式字符串控制输出。I5 = 整数宽度 5。F8.3 = 浮点数宽度 8,3 位小数。I0 = 最小宽度。A = 字符串。X = 空格。/ = 换行。write(*,...) 与 print 相同但更灵活。使用格式字符串获得对齐输出。

fortran
integer :: i = 42
real :: x = 3.14159
print "(I5, F8.3)", i, x    ! "   42    3.142"
print "(A, I0)", "Count=", i  ! "Count=42"
write(*, "(3F6.2)") 1.0, 2.0, 3.0
! Format specifiers:
! I: integer, F: float, E: exponential
! A: string, X: space, /: newline

文件 I/O

open 将文件连接到单元。newunit 分配空闲单元号。status:old(必须存在)、new(必须不存在)、replace。iostat 在错误或 EOF 时返回非零。始终检查 iostat 以避免崩溃。close 断开文件连接。

fortran
integer :: unit, ios
open(newunit=unit, file="data.txt", status="old", action="read")
do
    read(unit, *, iostat=ios) value
    if (ios /= 0) exit
    print *, value
end do
close(unit)
! status: old, new, replace, scratch
! action: read, write, readwrite

Namelist

namelist 为 I/O 分组变量。文件格式为 &config n=10, x=3.14 /。适用于配置文件。变量可以按任何顺序。只读/写列出的变量。比解析自定义格式容易得多。

fortran
integer :: n = 10
real :: x = 3.14
namelist /config/ n, x
! Write namelist
open(1, file="config.nml")
write(1, nml=config)
close(1)
! Read namelist
open(1, file="config.nml")
read(1, nml=config)
close(1)

内部文件

内部文件使用字符串作为 I/O 单元。write 到字符串将值转换为文本。read 从字符串解析文本。适用于类型转换和格式化。trim 移除尾部空格。比 C 的 sprintf/sscanf 简单得多。

fortran
character(20) :: str
integer :: num = 42
! Integer to string
write(str, "(I0)") num
print *, trim(str)  ! "42"
! String to integer
read(str, *) num
! Internal files use character variables as units

二进制 I/O

无格式 I/O 写入原始二进制数据。比文本更快更紧凑。access="stream" 用于字节级访问(Fortran 2008)。在不同架构间不可移植(字节序)。用于大型科学数据集。格式化 I/O 用于人类可读的数据。

fortran
! Unformatted (binary) I/O
open(1, file="data.bin", form="unformatted", &
     access="stream")
write(1) array  ! No format, raw bytes
read(1) array2
close(1)
! Faster than formatted I/O
! Smaller file size
! Not portable across architectures
23

并行编程

OpenMP

OpenMP 通过指令并行化循环。!$omp parallel do 将迭代分布到线程。private:每个线程有自己的副本。reduction:合并结果。用 -fopenmp 编译。并行化数值代码的简单方式。

fortran
!$omp parallel do private(i) reduction(+:sum)
do i = 1, n
    sum = sum + a(i) * b(i)
end do
!$omp end parallel do
! Compile: gfortran -fopenmp program.f90
! Environment: OMP_NUM_THREADS=4

Coarray

Coarray(Fortran 2008)是内建的并行数组。每个 image(进程)有自己的副本。[N] 访问另一个 image 的数据。sync all 是屏障。this_image() 返回 image 编号。内建于语言,无需库。

fortran
program coarray_example
    implicit none
    integer :: me[*]  ! Coarray: one per image
    me = this_image()
    sync all  ! Barrier
    if (this_image() == 1) then
        print *, "Image 2 has:", me[2]  ! Remote access
    end if
end program
! Compile: gfortran -fcoarray=single program.f90

MPI 基础

MPI(消息传递接口)是分布式并行的标准。mpi_init/finalize 开始和结束。comm_rank 给出进程 ID。comm_size 给出总进程数。Send/recv 用于通信。可扩展到数千个核心。用于集群。

fortran
program mpi_example
    use mpi
    integer :: rank, size, ierr
    call mpi_init(ierr)
    call mpi_comm_rank(MPI_COMM_WORLD, rank, ierr)
    call mpi_comm_size(MPI_COMM_WORLD, size, ierr)
    print *, "I am rank", rank, "of", size
    call mpi_finalize(ierr)
end program
! Compile: mpifort program.f90

do concurrent

do concurrent(Fortran 2008)表示循环迭代是独立的。编译器可以自动并行化。local 声明私有变量。比 OpenMP 更安全:编译器验证独立性。用于令人尴尬的并行循环。

fortran
do concurrent (i = 1:n) local(tmp)
    tmp = expensive_compute(a(i))
    b(i) = tmp * 2
end do
! Tells compiler iterations are independent
! Can be parallelized automatically
! local: private variable per iteration

归约模式

归约合并每个线程的部分结果。常见:sum、product、max、min。每个线程计算局部部分结果。运行时在最后合并它们。避免数据竞争。对并行数值算法至关重要。

fortran
!$omp parallel do reduction(+:total)
do i = 1, n
    total = total + a(i)
end do
!$omp end parallel do
! Common reductions: +, *, max, min, .and., .or.
! Each thread has a private copy
! Combined at the end
24

数值方法

线性代数

Fortran 有内建矩阵操作。matmul 矩阵相乘。dot_product 计算点积。transpose 转置。这些都高度优化(BLAS 级别)。对于生产,使用 LAPACK。Fortran 是高性能数值计算的首选语言。

fortran
! Matrix multiplication
do i = 1, n
    do j = 1, n
        c(i,j) = sum(a(i,:) * b(:,j))
    end do
end do
! Or use matmul intrinsic
c = matmul(a, b)
! Dot product
dot = dot_product(a, b)
! Transpose
at = transpose(a)

求解 ODE

欧拉法是最简单的 ODE 求解器:y(n+1) = y(n) + dt*f(t,y)。为获得精度,使用 Runge-Kutta(RK4)。interface 块将函数作为参数传递。由于数组操作和性能,Fortran 非常适合科学计算。

fortran
! Euler method: dy/dt = f(t, y)
subroutine euler(f, t0, y0, dt, n, t, y)
    interface
        real function f(t, y)
            real, intent(in) :: t, y
        end function
    end interface
    real, intent(in) :: t0, y0, dt
    integer, intent(in) :: n
    real, intent(out) :: t(n+1), y(n+1)
    integer :: i
    t(1) = t0; y(1) = y0
    do i = 1, n
        t(i+1) = t(i) + dt
        y(i+1) = y(i) + dt * f(t(i), y(i))
    end do
end subroutine

随机数

random_number 生成均匀 [0,1) 实数。random_seed 初始化生成器。对于整数,缩放并转换。Box-Muller 将均匀分布转换为正态分布。对于严肃工作,使用库(例如,Mersenne Twister)。始终设置种子以实现可重现性。

fortran
call random_seed()  ! Seed from system
call random_number(x)  ! x in [0, 1)
! Array of randoms
real :: arr(100)
call random_number(arr)
! Integer in range [1, 6]
integer :: dice
call random_number(r)
dice = int(r * 6) + 1
! Normal distribution (Box-Muller)
call random_number(u1)
call random_number(u2)
z = sqrt(-2*log(u1)) * cos(2*PI*u2)

插值

线性插值估计已知点之间的值。找到区间,然后插值。为获得更平滑的结果,使用三次样条插值。Fortran 的数组操作使这很简洁。始终检查边界以避免外推错误。

fortran
function interp(x, xs, ys) result(y)
    real, intent(in) :: x, xs(:), ys(:)
    real :: y
    integer :: i
    ! Find interval
    i = 1
    do while (i < size(xs) .and. x > xs(i+1))
        i = i + 1
    end do
    ! Linear interpolation
    y = ys(i) + (ys(i+1) - ys(i)) * &
        (x - xs(i)) / (xs(i+1) - xs(i))
end function

数值积分

梯形法则近似积分:梯形之和。更准确:辛普森法则。对于更高维度,使用高斯求积。由于性能,Fortran 在数值积分方面表现出色。始终用已知的解析解验证。

fortran
! Trapezoidal rule
function trapezoid(f, a, b, n) result(integral)
    interface
        real function f(x)
            real, intent(in) :: x
        end function
    end interface
    real, intent(in) :: a, b
    integer, intent(in) :: n
    real :: integral, h
    integer :: i
    h = (b - a) / n
    integral = (f(a) + f(b)) / 2
    do i = 1, n-1
        integral = integral + f(a + i*h)
    end do
    integral = integral * h
end function
25

常见陷阱

基于 1 的索引

Fortran 数组默认从 1 开始索引,与 C/Python(从 0 开始)不同。这在移植代码时会导致差一错误。允许自定义下界(0:9)。在项目内保持一致。用 -fcheck=bounds 编译器标志检查数组边界。

fortran
! Fortran arrays are 1-based by default
real :: a(10)  ! Indices 1 to 10
! a(0) = 1.0  ! Error: out of bounds
a(1) = 1.0    ! OK
! Custom bounds
real :: b(0:9)  ! Indices 0 to 9
b(0) = 1.0    ! OK

隐式类型

没有 implicit none,以 i-n 开头的变量为整数,其他为实数。这会导致微妙的 bug(拼写错误创建新变量)。始终使用 implicit none。现代 Fortran(2018+)可以用 -fimplicit-none 全局设置。这是最重要的 Fortran 最佳实践。

fortran
! BAD: implicit typing (Fortran 77 style)
program bad
    ! i-n start with integer by default
    i = 1      ! integer (implicit)
    x = 3.14   ! real (implicit)
end program
! GOOD: explicit typing
program good
    implicit none  ! Force explicit declaration
    integer :: i
    real :: x
end program

列主序

Fortran 以列主序存储数组:m(1,1)、m(2,1)、m(3,1)、m(1,2), ... 按列访问对缓存友好。错误的循环顺序导致缓存未命中并减慢 10 倍以上。始终使循环顺序与内存布局匹配。与 C 相反(行主序)。

fortran
! Fortran is column-major (first index varies fastest)
real :: m(3, 3)
! Efficient: iterate over first index in inner loop
do j = 1, 3
    do i = 1, 3
        m(i, j) = 0.0  ! Cache-friendly
    end do
end do
! Inefficient: row-major access
do i = 1, 3
    do j = 1, 3
        m(i, j) = 0.0  ! Cache misses
    end do
end do

按引用传递

Fortran 按引用传递参数(类似 C 指针)。除非指定 intent(in),子例程可以修改调用者的变量。没有 intent,意外修改会导致 bug。始终指定 intent。intent(out) 表示过程将设置该值。

fortran
subroutine modify(x)
    integer, intent(inout) :: x
    x = 99  ! Modifies the caller variable
end subroutine
! Fortran passes by reference by default
! intent(in) prevents modification
! Without intent, modification is allowed (dangerous)

浮点精度

默认 real 是单精度(约 7 位),通常不够。科学计算使用双精度。kind(1.0d0) 或 selected_real_kind(15) 定义双精度。始终为字面量添加后缀:3.14_dp。混合精度会导致静默截断。使用 iso_fortran_env 获得可移植的 kind。

fortran
! Single precision (default)
real :: x = 3.14159  ! ~7 digits
! Double precision
real(kind=8) :: y = 3.14159d0  ! ~15 digits
! Or use kind parameter
integer, parameter :: dp = kind(1.0d0)
real(dp) :: z = 3.14159_dp
! Always use _dp or d0 for double literals

这篇内容对您有帮助吗?