代码结构组织#

大多数编程语言允许您将常用的代码收集到过程中,这些过程可以通过从代码的其他部分调用它们来重复使用。

Fortran 有两种形式的过程

  • 子程序:由 call 语句调用

  • 函数:在表达式或赋值中调用,并返回一个值

子程序和函数都可以通过参数关联访问父作用域中的变量;除非指定了 value 属性,否则这类似于按引用调用。

子程序#

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

示例

! 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

此示例使用所谓的显式形状数组参数,因为我们已传递了其他变量来描述数组 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

将此 print_matrix 子程序与在模块外部编写的子程序进行比较。我们不再需要显式传递矩阵维度,而是可以利用假定形状参数,因为模块会为我们生成所需的显式接口。这将导致子程序接口变得更加简单。

要在程序中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 它们的程序单元之前,都需要编译模块。