组织代码结构#

大多数编程语言允许你将常用代码收集到 procedures 中,这些代码可以通过从其它代码部分 calling 来重用。

Fortran 有两种形式的过程:

  • 子例程:由 call 语句调用

  • 函数:在返回值的表达式或赋值中调用

子程序和函数都可以通过 argument association 访问父范围内的变量;除非指定了 value 属性,否则这类似于按引用调用。

子程序#

子程序输入参数,称为 dummy arguments,在子程序名称后面的括号中指定;虚拟参数类型和属性在子例程的主体中声明,就像局部变量一样。

例子:

! Print matrix A to screen
subroutine print_matrix(n,m,A)
  implicit none
  integer, intent(in) :: n
  integer, intent(in) :: m
  real, intent(in) :: A(n, m)

  integer :: i

  do i = 1, n
    print *, A(i, 1:m)
  end do

end subroutine print_matrix

注意声明虚拟参数时附加的 intent 属性;这个可选属性向编译器表示参数是“只读”(intent(in))“只写”(intent(out))还是“读写”( intent(inout)) 在过程中。在这个例子中,子程序不会修改它的参数,因此所有的参数都是 intent(in)

始终为虚拟参数指定 intent 属性是一种很好的做法;这允许编译器检查意外错误并提供自文档。

我们可以使用 call 语句从程序中调用此子例程:

program call_sub
  implicit none

  real :: mat(10, 20)

  mat(:,:) = 0.0

  call print_matrix(10, 20, mat)

end program call_sub

这个例子使用了一个所谓的 explicit-shape 数组参数,因为我们传递了额外的变量来描述数组 A 的维度;如果我们将子例程放在后面描述的模块中,这将不是必需的。

函数#

! L2 Norm of a vector
function vector_norm(n,vec) result(norm)
  implicit none
  integer, intent(in) :: n
  real, intent(in) :: vec(n)
  real :: norm

  norm = sqrt(sum(vec**2))

end function vector_norm

在生产代码中,应使用内部函数 norm2

要执行此函数:

program run_fcn
  implicit none

  real :: v(9)
  real :: vector_norm

  v(:) = 9

  print *, 'Vector norm = ', vector_norm(9,v)

end program run_fcn

函数不修改其参数是一种很好的编程习惯 —— 也就是说,所有函数参数都应该是 intent(in)。这样的函数被称为 pure 函数。如果你的过程需要修改其参数,请使用子例程。

模块#

Fortran 模块包含程序、过程和其它模块可以通过 use 语句访问的定义。它们可以包含数据对象、类型定义、过程和接口。

  • 模块允许受控范围扩展,从而明确实体访问

  • 模块自动生成现代程序所需的显式接口

建议始终将函数和子例程放在模块中。

例子:

module my_mod
  implicit none

  private  ! All entities are now module-private by default
  public public_var, print_matrix  ! Explicitly export public entities

  real, parameter :: public_var = 2
  integer :: private_var

contains

  ! Print matrix A to screen
  subroutine print_matrix(A)
    real, intent(in) :: A(:,:)  ! An assumed-shape dummy argument

    integer :: i

    do i = 1, size(A,1)
      print *, A(i,:)
    end do

  end subroutine print_matrix

end module my_mod

Compare this print_matrix subroutine with that written outside of a module. We no longer have to explicitly pass the matrix dimensions and can instead take advantage of assumed-shape arguments since the module will generate the required explicit interface for us. This results in a much simpler subroutine interface.

要在程序中 use 模块:

program use_mod
  use my_mod
  implicit none

  real :: mat(10, 10)

  mat(:,:) = public_var

  call print_matrix(mat)

end program use_mod

示例: 显式导入列表

use my_mod, only: public_var

示例: 别名导入

use my_mod, only: printMat=>print_matrix

每个模块都应该写在一个单独的 .f90 源文件中。模块需要在任何 use 它们的程序单元之前编译。

Optional arguments#

An advantage of placing subroutines and functions in modules is that they can have optional arguments. In a procedure with an argument declared optional, the present function is used to test if the argument was set in the caller. Optional arguments that are not present may not be accessed within the procedure. Here is a generalization of the vector_norm function that can use powers other than 2 to compute the Lp norm.

module norm_mod
  implicit none
  contains
  function vector_norm(vec,p) result(norm)
    real, intent(in) :: vec(:)
    integer, intent(in), optional :: p ! power
    real :: norm
    if (present(p)) then ! compute Lp norm
      norm = sum(abs(vec)**p) ** (1.0/p)
    else ! compute L2 norm
      norm = sqrt(sum(vec**2))
    end if
  end function vector_norm
end module norm_mod

program run_fcn
  use norm_mod
  implicit none

  real :: v(9)

  v(:) = 9

  print *, 'Vector norm = ', vector_norm(v), vector_norm(v,2)
  print *, 'L1 norm = ', vector_norm(v,1)

end program run_fcn