Fortran 编程整理

01 Fortran 中的程序单元

  • main program (主程序) —— 只能从操作系统调用的最高级别程序单元;
  • function (函数) —— 从表达式调用的可执行子函数,并且总是返回单个结果;
  • subroutine (子程序)—— 一种可执行的子程序,可以就地修改多个参数,但不能在表达式中使用;
  • module(模块)—— 变量、函数和子例程定义的不可执行集合;
  • submodule(子模块)—— 扩展现有模块,用于定义只有该模块可以访问的变量和过程定义,对于更复杂的应用程序和库非常有用

1-1 submodule(子模块)

在Fortran中,子模块(submodule)是模块系统的一部分,用于帮助组织和管理大型代码库。子模块允许将模块分成多个部分,这些部分可以独立开发和维护,从而提高代码的可读性和可维护性。

下面是子模块的一些关键点和示例:

关键点

  1. 主模块:定义公共接口、类型和公共变量。
  2. 子模块:实现主模块中定义的接口和包含实际的代码逻辑。
  3. 结构:子模块依赖于主模块,子模块中的任何子程序都必须在主模块中声明。
  4. 可见性:子模块可以访问主模块中的所有公共实体。

示例

# 主模块(Module)
module my_module
  implicit none
  ! 在这里声明子模块将实现的接口
  contains
    procedure :: my_subroutine
end module my_module
# 子模块(Submodule)
submodule (my_module) my_submodule
  implicit none
  contains
    subroutine my_subroutine
      ! 子程序实现
      print *, "Hello from submodule!"
    end subroutine my_subroutine
end submodule my_submodule
# 使用子模块
program main
  use my_module
  implicit none
  call my_subroutine
end program main

详细解释:

  1. 主模块 (Module)

    • module my_module 定义了一个名为 my_module 的主模块。
    • contains 部分,声明了一个子程序 my_subroutine,但没有提供实现。这告诉编译器子程序将在子模块中定义。
  2. 子模块 (Submodule)

    • submodule (my_module) my_submodule 声明了一个名为 my_submodule 的子模块,并指定它从属于 my_module
    • contains 部分,实现了 my_subroutine 子程序。
  3. 主程序 (Main Program)

    • use my_module 使主模块 my_module 中的所有公共实体在主程序中可用。
    • call my_subroutine 调用子模块中的 my_subroutine 子程序。

这种结构允许更好的代码组织和模块化,使得大型Fortran项目更容易管理和维护。

02 子程序与函数

2-1 概述

![[Pasted image 20240522135037.png|500]]

图1 子程序与函数概述
  • Fortran中函数可以接收任意数量的输入参数,但是只能返回一个结果。如果有多个结果需要返回可以使用数组或自定义数据类型。
  • 对于没有副作用的计算,应该使用函数来完成。

在Fortran以及其他编程语言中,副作用(side effect)指的是在函数执行过程中对其外部状态(如全局变量、输入参数、I/O操作等)的修改。没有副作用的函数(即纯函数)只依赖于其输入参数,并且除了返回值外不会对程序的其他部分产生影响。这种函数的输出仅由输入决定,不会改变任何外部状态。

在Fortran中,当提到“在没有副作用时使用函数”时,通常指的是:

  1. 函数仅依赖于输入参数:函数的计算结果完全由传递给它的参数决定,没有依赖或修改外部变量。
  2. 没有修改输入参数:函数不会通过传递的参数修改外部变量的值。
  3. 没有I/O操作:函数不进行输入输出操作(如读取文件或打印输出,打印到屏幕上也是一种副作用)。

2-2 函数与子程序特性

(1)为函数结果指定不同名称

可以使用 result 属性为函数结果指定一个不同的名称,而不是直接使用函数名称:

INTEGER FUNCTION SUM(A, B)
	INTEGER, INTENT(IN) :: A, B
	SUM = A + B
END FUNCTION SUM

! 指定不同的函数返回名称
INTEGER FUNCTION SUM(A, B) RESULT(RES)
	INTEGER, INTENT(IN) :: A, B
	RES = A + B
END FUNCTION SUM

一个函数应该只做一件事。

函数适合于不会产生副作用的计算,而子例程则更适合于需要就地修改变量的情况。但要注意这只是最佳实践,而不是硬性规定,Fortran语法是允许函数中就地修改变量的。

03 stop命令后跟说明

if(grid_size <=  0) stop 'grid_size must be > 0'

04 do concurrent

在Fortran中,concurrent 是用在并行编程中的关键字,通常在 DO CONCURRENT 结构中使用。DO CONCURRENT 结构允许程序员明确表示循环迭代之间可以独立并行执行,从而提高计算效率。

4-1 do concurrent 的使用

基本示例:

program concurrent_example
  implicit none
  integer :: i
  integer, dimension(10) :: array

  ! Initialize the array
  array = 0

  ! DO CONCURRENT loop
  do concurrent (i = 1:10)
    array(i) = i * i
  end do

  ! Print the array
  print *, array
end program concurrent_example

在这个示例中,DO CONCURRENT 循环用于对数组 array 的每个元素进行平方计算。由于每次迭代之间没有数据依赖关系,编译器可以将这些迭代并行化,以提高执行效率。

带条件的并行循环

program concurrent_with_conditions
  implicit none
  integer :: i
  integer, dimension(10) :: array

  ! Initialize the array
  array = 0

  ! DO CONCURRENT loop with conditions
  do concurrent (i = 1:10)
    if (mod(i, 2) == 0) then
      array(i) = i * 2
    else
      array(i) = i * 3
    end if
  end do

  ! Print the array
  print *, array
end program concurrent_with_conditions

这个示例展示了带条件语句的 DO CONCURRENT 循环。根据 i 是否为偶数,数组 array 的元素被赋予不同的值。

注意事项:

  1. 独立性DO CONCURRENT 结构的每次迭代必须是独立的,也就是说,每次迭代之间不应有数据依赖性。
  2. 副作用:避免在 DO CONCURRENT 结构中使用具有副作用的操作(如 I/O 操作),因为这可能导致不可预测的行为。
  3. 性能:尽管 DO CONCURRENT 可以显式指示并行性,实际的性能提升仍然依赖于编译器的实现和底层硬件支持。

高级示例:

program concurrent_advanced
  implicit none
  integer :: i, j
  integer, dimension(5, 5) :: matrix

  ! Initialize the matrix
  matrix = 0

  ! DO CONCURRENT nested loop
  do concurrent (i = 1:5, j = 1:5)
    matrix(i, j) = i * j
  end do

  ! Print the matrix
  do i = 1, 5
    print *, matrix(i, :)
  end do
end program concurrent_advanced

这个高级示例展示了嵌套的 DO CONCURRENT 循环,用于初始化一个 5x5 的矩阵。每个元素 matrix(i, j) 的值为 i * j

通过使用 DO CONCURRENT,Fortran 程序可以更好地利用多核处理器的并行计算能力,从而提高计算性能。

05 纯过程(pure procedure)

在Fortran中,纯过程(pure procedure)是一类没有副作用的过程。这意味着纯过程不能更改外部变量、不能执行 I/O 操作,也不能调用任何非纯过程。纯过程的主要优点是它们更容易进行并行化和优化,因为编译器可以更好地预测其行为。

纯过程可以是子程序(subroutine)或函数(function)。要声明一个过程为纯过程,可以使用 pure 关键字。
\

纯过程的规则:

  1. 不能改变通过参数传递的任何变量的值。
  2. 不能更改模块、公共或局部变量的值。
  3. 不能执行输入/输出(I/O)操作。
  4. 不能调用非纯过程。

示例:

纯函数(Pure Function)

module math_module
  implicit none
contains
  pure function add(a, b) result(c)
    integer, intent(in) :: a, b
    integer :: c

    c = a + b
  end function add
end module math_module

在这个示例中,add 是一个纯函数,它接受两个整数参数 ab,并返回它们的和。因为它不修改任何外部状态,也不执行 I/O 操作,所以它是纯的。

纯子程序(Pure Subroutine)

module math_module
  implicit none
contains
  pure subroutine swap(a, b)
    integer, intent(inout) :: a, b
    integer :: temp

    temp = a
    a = b
    b = temp
  end subroutine swap
end module math_module

在这个示例中,swap 是一个纯子程序,它交换两个整数参数 ab 的值。尽管它修改了参数的值,但它不修改任何外部状态或执行 I/O 操作,因此它仍然是纯的。

使用纯过程

program main
  use math_module
  implicit none
  integer :: x, y, z

  x = 3
  y = 4

  z = add(x, y) ! 调用纯函数
  print *, "Sum:", z

  call swap(x, y) ! 调用纯子程序
  print *, "Swapped values:", x, y
end program main

在这个示例中,我们定义了一个主程序 main,它使用 math_module 中的纯过程。它首先调用纯函数 add 来计算两个整数的和,然后调用纯子程序 swap 来交换两个整数的值。

纯过程的优点:

  1. 可并行化:因为纯过程没有副作用,编译器可以更容易地并行化这些过程。
  2. 优化:纯过程更容易被编译器优化,因为它们的行为是确定的。
  3. 可测试性:纯过程由于没有副作用,更容易进行单元测试。

通过使用纯过程,Fortran程序可以提高代码的可靠性、可维护性和性能。

06 Fortran中的elemental 属性

在Fortran中,elemental 属性用于声明可以对数组的每个元素进行操作的过程(函数或子程序)。带有 elemental 属性的过程可以对标量和数组参数进行统一处理,而无需显式编写循环。这使得代码更简洁,并且可以利用编译器的自动向量化和并行化优化。

6-1 elemental 关键字的使用

elemental 函数

module math_module
  implicit none
contains
  elemental function square(x) result(y)
    real, intent(in) :: x
    real :: y

    y = x * x
  end function square
end module math_module

在这个示例中,square 函数使用了 elemental 关键字,可以对标量或数组的每个元素进行平方计算。

elemental 子程序

module math_module
  implicit none
contains
  elemental subroutine add_one(x)
    real, intent(inout) :: x

    x = x + 1.0
  end subroutine add_one
end module math_module

在这个示例中,add_one 子程序使用了 elemental 关键字,可以对标量或数组的每个元素加一。

使用 elemental 子程序

program main
  use math_module
  implicit none
  real :: a, b
  real, dimension(5) :: array, result_array

  ! 标量操作
  a = 3.0
  b = square(a)
  print *, "Square of a:", b

  ! 数组操作
  array = [1.0, 2.0, 3.0, 4.0, 5.0]
  result_array = square(array)
  print *, "Square of array:", result_array

  ! 对数组应用elemental子程序
  call add_one(array)
  print *, "Array after add_one:", array
end program main

在这个示例中:

  1. 对标量 a 调用了 square 函数,并输出结果。
  2. 对数组 array 调用了 square 函数,结果存储在 result_array 中,并输出结果。
  3. 调用了 add_one 子程序,对数组 array 的每个元素加一,并输出修改后的数组。

优点

  1. 简洁:不需要显式编写循环,代码更加简洁易读。
  2. 性能:编译器可以更容易地进行向量化和并行化优化,提高性能。
  3. 通用性:相同的过程可以同时处理标量和数组,提高代码复用性。

通过使用 elemental 关键字,可以简化数组操作的代码,同时利用编译器优化以提高性能。

6-2 elemental 关键字默认对纯过程使用

在Fortran中,elemental 过程必须是纯过程。这是因为 elemental 过程的每个元素操作是独立的,不应该有任何副作用。为了确保这一点,elemental 过程需要符合纯过程的规则。

纯过程的规则复习

  1. 不能改变通过参数传递的任何变量的值(除非它们是 intent(out)intent(inout))。
  2. 不能更改模块、公共或局部变量的值。
  3. 不能执行输入/输出(I/O)操作。
  4. 不能调用非纯过程。

elemental 过程示例

elemental 函数

module math_module
  implicit none
contains
  elemental pure function square(x) result(y)
    real, intent(in) :: x
    real :: y

    y = x * x
  end function square
end module math_module

在这个示例中,square 函数是 elemental 的,并且是纯函数 (pure)。

elemental 子程序:

module math_module
  implicit none
contains
  elemental pure subroutine add_one(x)
    real, intent(inout) :: x

    x = x + 1.0
  end subroutine add_one
end module math_module

在这个示例中,add_one 子程序是 elemental 的,并且是纯子程序 (pure)。

使用 elemental 过程:

program main
  use math_module
  implicit none
  real :: a, b
  real, dimension(5) :: array, result_array

  ! 标量操作
  a = 3.0
  b = square(a)
  print *, "Square of a:", b

  ! 数组操作
  array = [1.0, 2.0, 3.0, 4.0, 5.0]
  result_array = square(array)
  print *, "Square of array:", result_array

  ! 对数组应用elemental子程序
  call add_one(array)
  print *, "Array after add_one:", array
end program main

在这个示例中:

  1. 对标量 a 调用了 square 函数,并输出结果。
  2. 对数组 array 调用了 square 函数,结果存储在 result_array 中,并输出结果。
  3. 调用了 add_one 子程序,对数组 array 的每个元素加一,并输出修改后的数组。

结论
为了确保 elemental 过程的每次操作是独立且没有副作用的,Fortran 语言要求 elemental 过程必须是纯过程。这样可以保证 elemental 过程在处理数组时能够安全并行化,并提高代码的可读性和可维护性。

impure elemental 了解一下。

07 Fortran函数和子程序中的可选参数

在Fortran中,子函数(functions)和子程序(subroutines)可以通过使用optional属性来定义可选参数。这样在调用子函数或子程序时选择性地提供这些参数。以下是如何在Fortran中使用可选参数的示例:

子程序中的可选参数

program optional_args_example
  implicit none

  call example_subroutine(5)
  call example_subroutine(5, 10)

contains

  subroutine example_subroutine(a, b)
    integer, intent(in) :: a
    integer, optional, intent(in) :: b

    if (present(b)) then
      print *, "a =", a, ", b =", b
    else
      print *, "a =", a, ", b is not provided"
    end if
  end subroutine example_subroutine

end program optional_args_example

子函数中的可选参数

program optional_args_example
  implicit none

  print *, "Result with one argument: ", example_function(5)
  print *, "Result with two arguments: ", example_function(5, 10)

contains

  function example_function(a, b) result(res)
    integer, intent(in) :: a
    integer, optional, intent(in) :: b
    integer :: res

    if (present(b)) then
      res = a + b
    else
      res = a
    end if
  end function example_function

end program optional_args_example

解释

  1. 子程序中的可选参数:

    • 定义子程序example_subroutine,其中第二个参数b被标记为optional
    • 使用present(b)来检查可选参数b是否被提供。
    • 在调用子程序时,可以只传递一个参数a,也可以传递两个参数ab
  2. 子函数中的可选参数:

    • 定义子函数example_function,其中第二个参数b被标记为optional
    • 同样,使用present(b)来检查可选参数b是否被提供。
    • 在调用子函数时,可以只传递一个参数a,也可以传递两个参数ab

通过这种方式,可以灵活地处理参数的传递,并根据是否提供了可选参数来执行不同的操作。

08 Fortran内置模块iso_fortran_env

Fortran 的内置模块 ISO_FORTRAN_ENV 提供了一些有用的参数和功能,可以用于控制程序的行为和提高代码的可移植性和可维护性。以下是对 ISO_FORTRAN_ENV 模块的一些详细介绍及其常见用途。

8-1常见参数和功能

  1. 数值种类参数:

    • REAL32, REAL64, REAL128:分别对应 32 位、64 位和 128 位的实数类型。
    • INT8, INT16, INT32, INT64:分别对应 8 位、16 位、32 位和 64 位的整数类型。
  2. 字符种类参数:

    • CHARACTER_KINDS:返回一个整数数组,表示可用的字符种类。
  3. 输入/输出参数:

    • INPUT_UNIT:标准输入单元号。
    • OUTPUT_UNIT:标准输出单元号。
    • ERROR_UNIT:标准错误输出单元号。
  4. 文件状态参数:

    • IOSTAT_END:表示文件结束的 IOSTAT 值。
    • IOSTAT_EOR:表示记录结束的 IOSTAT 值。
  5. 常量:

    • ERROR_UNIT:标准错误输出单元的单元号。
    • OUTPUT_UNIT:标准输出单元的单元号。
    • INPUT_UNIT:标准输入单元的单元号。

8-2 示例

以下是使用 ISO_FORTRAN_ENV 模块的一些示例:

示例 1: 使用数值种类参数

program kind_example
  use iso_fortran_env, only: int32, real64
  implicit none

  integer(int32) :: i
  real(real64) :: x

  i = 123456789
  x = 3.141592653589793d0

  print *, "Integer value: ", i
  print *, "Real value: ", x

end program kind_example

示例 2: 使用 I/O 参数

program io_example
  use iso_fortran_env, only: input_unit, output_unit, error_unit
  implicit none

  integer :: num

  print *, "Please enter a number: "
  read(input_unit, *) num
  write(output_unit, "('You entered: ', I0)") num

  if (num < 0) then
    write(error_unit, "('Error: Negative number entered.')")
  end if

end program io_example

示例3:打印编译器版本和编译选项

program print_compiler_info
	use iso_fortran_env
	implicit none
	print *, 'Compiler version: ', compiler_version()
	print *, 'Compiler options:', compiler_options()
end program print_compiler_info

解释

  1. 数值种类参数:

    • 使用 int32real64 定义整数和实数变量,以确保它们具有特定的位数,这有助于提高代码的可移植性。
  2. 输入/输出参数:

    • 使用 input_unitoutput_uniterror_unit 来处理标准 I/O 操作。这使得代码更具可读性,并且减少了硬编码的单元号。

8-3 总结

ISO_FORTRAN_ENV 模块提供了许多有用的常量和参数,可以帮助编写更具可移植性和可维护性的 Fortran 代码。通过使用这些参数和功能,可以更加轻松地控制数值类型和 I/O 操作,并确保代码能够在不同的平台上运行一致。

8-4 Fortran的内置模块

Fortran 提供了五个内置模块:iso_fortran_enviso_c_bindingieee_arithmeticieee_exceptionsieee_featuresiso_fortran_env 提供了一些有用的过程和参数,iso_c_binding 提供了与 C 语言函数和数据结构接口的功能。后面三个模块提供了特定于浮点运算的功能,一般来说,它们不像前两个模块那样有用

在导入自己不熟悉的模块时,最好加上only关键字,否则有时会出现匪夷所思的问题。

09 在Fortran中自定义模块

![[Pasted image 20240615094150.png|600]]

图2 自定义fortran模块的结构

建议每个源文件只保留一个模块。

10 Fortran模块中的私有与公有声明

在 Fortran 模块中,可以使用 publicprivate 语句来控制模块内数据和子程序的可见性。public 声明的实体可以被模块外部访问,而 private 声明的实体则只能在模块内部使用。这种控制机制有助于封装和信息隐藏,提高代码的可维护性和可靠性。

10-1 示例

以下是一个包含 publicprivate 声明的 Fortran 模块示例:

module my_module
  implicit none

  ! 默认可见性为 public
  public
  private :: private_subroutine

  ! 公有变量和子程序
  integer :: public_variable
  public :: public_subroutine

  ! 私有变量和子程序
  integer, private :: private_variable

contains

  ! 公有子程序
  subroutine public_subroutine()
    print *, "This is a public subroutine."
    call private_subroutine()
  end subroutine public_subroutine

  ! 私有子程序
  subroutine private_subroutine()
    print *, "This is a private subroutine."
  end subroutine private_subroutine

end module my_module

program test
  use my_module
  implicit none

  ! 访问公有变量和子程序
  public_variable = 10
  call public_subroutine()

  ! 尝试访问私有变量和子程序(会导致编译错误)
  ! private_variable = 20
  ! call private_subroutine()

end program test

10-2 解释

  1. 模块声明:

    • module my_module 声明了一个名为 my_module 的模块。
    • implicit none 禁用隐式变量声明,强制所有变量显式声明。
  2. 默认可见性:

    • public 声明将模块的默认可见性设置为公有。
    • private 声明将 private_subroutine 设置为私有。
  3. 公有和私有变量:

    • public_variable 是公有变量,可以在模块外部访问。
    • private_variable 是私有变量,只能在模块内部访问。
  4. 公有和私有子程序:

    • public_subroutine 是公有子程序,可以在模块外部调用。
    • private_subroutine 是私有子程序,只能在模块内部调用。
    • public_subroutine 中调用了 private_subroutine,这在模块内部是允许的。
  5. 程序块:

    • program test 使用了 my_module 模块。
    • 可以访问和修改 public_variable,并调用 public_subroutine
    • 尝试访问或调用私有变量和子程序会导致编译错误(被注释掉的部分)。

10-3 推荐用法

可以单独使用private或public单词作为语句,这将分别将模块中的所有实体声明为private或public。如果两者都不存在,则假定所有实体都是公共的,除非显式地声明为私有的。可以使用private属性隐藏库内部的变量和过程,这些变量和过程用户不应该访问。一般来说,良好的编程实践是将所有内容声明为私有,并显式地将公共变量和过程声明为私有。

示例

module mod_circle
  implicit none
  
  ! 将所有实体默认声明为私有
  private

  ! 只公开 circle_area 和 circle_circumference 这两个接口函数
  public :: circle_area, circle_circumference

  ! 私有常量 π
  real, parameter :: pi = 3.141592653589793

contains

  ! 公有函数:计算圆的面积
  real function circle_area(radius)
    real, intent(in) :: radius
    circle_area = pi * radius * radius
  end function circle_area

  ! 公有函数:计算圆的周长
  real function circle_circumference(radius)
    real, intent(in) :: radius
    circle_circumference = 2.0 * pi * radius
  end function circle_circumference

end module mod_circle

program test_circle
  use mod_circle
  implicit none

  real :: radius, area, circumference

  ! 输入半径
  print *, "Enter the radius of the circle:"
  read *, radius

  ! 计算面积和周长
  area = circle_area(radius)
  circumference = circle_circumference(radius)

  ! 打印结果
  print *, "Area of the circle: ", area
  print *, "Circumference of the circle: ", circumference

end program test_circle

10-4 总结

通过使用 publicprivate 声明,可以有效地控制模块内数据和子程序的可见性。这有助于实现数据的封装和信息隐藏,提高代码的安全性和可维护性。

11 在导入模块时重命名导入的实体

在 Fortran 中,可以通过 use 语句导入模块,并来重命名模块中的实体。这种方法在避免名称冲突以及使代码更加清晰和具有描述性方面非常有用。

11-1 示例:重命名导入的实体

创建一个模块 mod_circle,并在导入时重命名其中的实体。

模块定义 (mod_circle)

module mod_circle
  implicit none

  public :: circle_area, circle_circumference

  real, parameter :: pi = 3.141592653589793

contains

  ! 公有函数:计算圆的面积
  real function circle_area(radius)
    real, intent(in) :: radius
    circle_area = pi * radius * radius
  end function circle_area

  ! 公有函数:计算圆的周长
  real function circle_circumference(radius)
    real, intent(in) :: radius
    circle_circumference = 2.0 * pi * radius
  end function circle_circumference

end module mod_circle

使用模块并重命名实体

program test_circle
  use mod_circle, only: area => circle_area, circumference => circle_circumference
  implicit none

  real :: radius, area_value, circumference_value

  ! 输入半径
  print *, "Enter the radius of the circle:"
  read *, radius

  ! 计算面积和周长
  area_value = area(radius)
  circumference_value = circumference(radius)

  ! 打印结果
  print *, "Area of the circle: ", area_value
  print *, "Circumference of the circle: ", circumference_value

end program test_circle

解释

  1. 模块定义 (mod_circle):

    • 模块 mod_circle 定义了两个公有函数 circle_areacircle_circumference,分别用于计算圆的面积和周长。
  2. 使用模块并重命名实体:

    • use mod_circle, only: area => circle_area, circumference => circle_circumference
      • use mod_circle 导入 mod_circle 模块。
      • only 子句指定只导入 circle_areacircle_circumference 两个函数。
      • area => circle_area 表示将模块中的 circle_area 函数重命名为 area
      • circumference => circle_circumference 表示将模块中的 circle_circumference 函数重命名为 circumference
  3. 程序块:

    • program test_circle 使用了 mod_circle 模块,并调用了重命名后的 areacircumference 函数。
    • 用户输入圆的半径,程序计算并输出面积和周长。

总结
通过重命名导入的实体,避免名称冲突,并使代码更加清晰和易读。在大型项目中,特别是在多个模块可能具有相同函数名或变量名时,这种方法非常有用,可以帮助组织和管理代码。

12 在纯过程中数组不能在声明时直接初始化

在 Fortran 中,所谓的“纯过程”(pure procedure)是指不会有副作用的过程,即不修改任何全局状态、不进行 I/O 操作等。纯过程一般用于并行计算和高性能计算中,以保证线程安全和可预测性。

在 Fortran 2003 及之后的版本中,纯过程可以使用局部变量进行数组初始化,但不能在声明时直接进行初始化。相反,纯过程中的数组初始化需要在过程中显式进行。下面是一个示例,展示了如何在纯过程中初始化数组。

纯过程中的数组初始化示例

program pure_procedure_example
    implicit none
    integer, dimension(5) :: arr

    call initialize_array(arr)
    print *, arr

contains

    pure subroutine initialize_array(a)
        integer, dimension(:), intent(out) :: a
        integer :: i

        do i = 1, size(a)
            a(i) = i
        end do
    end subroutine initialize_array

end program pure_procedure_example

在这个示例中:

  1. 主程序 (program pure_procedure_example)

    • 声明了一个长度为 5 的整型数组 arr
    • 调用纯子程序 initialize_array 来初始化数组 arr
  2. 纯子程序 (pure subroutine initialize_array)

    • 该子程序被标记为 pure,表示它不会有副作用。
    • 使用 intent(out) 指定数组 a 为输出参数。
    • 使用一个循环来初始化数组 a 的每个元素。

通过这种方式,我们可以在纯过程中进行数组的初始化,而不会违反纯过程的约束。这样做确保了过程没有副作用,使其可以安全地在并行计算中使用。

  • 隐式保存

好的在过程中的声明语句中添加 save 属性会使声明的变量在过程调用之间保存其值。换句话说,过程会“记住”该保存变量的值。现在,这里有一个转折:如果你在声明语句中初始化一个变量,这将隐式地给声明添加 save 属性。具有 save 属性的变量将在过程调用之间保持其值在内存中。由于这是一个副作用,所以它不能用于纯过程。
不推荐使用 save 属性或依赖隐式保存功能在调用之间保持状态。在主程序和模块中,这是无害的,你可以安全地在声明时初始化。在过程中,建议不要使用隐式保存行为,因为它容易导致代码错误。

13 可分配数组的一些用法

13-1 分配数组a,范围从 is 到 ie

integer :: is = -5, ie = 10 
allocate(a(is  :  ie))

13-2 从另一个数组分配数组

allocate语句的可选参数

  • mold:一个变量或表达式,具有与被分配对象相同的类型。使用mold参数分配数组时,分配的数组会与mold参数指定的数组具有相同的形状和类型,但不会初始化其元素。
  • source:类似于mold,但使用source参数分配数组时,分配的数组不仅会具有相同的形状和类型,还会用source参数指定的数组的值进行初始化。

示例解释

使用mold参数

real, allocatable :: a(:), b(:)
allocate(b(10:20))
allocate(a, mold=b)
a = 0
  • 这里,首先分配了数组b,它的范围是从10到20。
  • 然后使用mold=b分配数组a。此时,数组a会与b具有相同的形状和类型,但不会被初始化。
  • a = 0用于手动初始化数组a的元素。

使用source参数

real, allocatable :: a(:), b(:)
b = [1.0, 2.0, 3.0]
allocate(a, source=b)
  • 这里,首先初始化数组b,包含三个元素:1.0, 2.0和3.0。
  • 然后使用source=b分配数组a。此时,数组a会与b具有相同的形状和类型,并且会用b的值进行初始化。

提示

  • 无论选择哪种分配方式,始终在分配后立即初始化数组。这可以减少在表达式中意外使用未初始化数组的风险,否则可能会得到无意义的结果。

额外说明

  • 从Fortran 2003开始,Fortran提供了一种方便的特性,即在赋值时进行分配(allocation on assignment),这使得显式分配数组变得不那么必要。

13-3 在赋值时自动分配和重新分配

在赋值时自动分配和重新分配(Automatic allocation on assignment)。这是Fortran在处理动态数组时提供的方便功能,可以自动管理数组的大小和分配。

自动分配和重新分配
如果将一个数组赋值给一个可分配的数组变量(allocatable array variable),目标数组变量会根据右侧数组的大小自动进行分配。如果目标数组已经分配过,且当前大小不同于源数组,它将被重新分配以匹配源数组的大小。

示例代码解释
图中的示例展示了如何在赋值过程中自动分配和重新分配数组:

integer, allocatable :: a(:)

! 初始化一个空数组
a = [integer::]

! 重新分配为大小为1的数组,并赋值为[1]
a = [a, 1]

! 重新分配为大小为2的数组,并赋值为[1, 2]
a = [a, 2]

! 重新分配为大小为4的数组,并赋值为[1, 2, 2, 4]
a = [a, 2 * a]
  1. a = [integer::]:将a初始化为空数组。
  2. a = [a, 1]:重新分配a,将其大小设置为1,并赋值为[1]
  3. a = [a, 2]:再次重新分配a,将其大小设置为2,并赋值为[1, 2]
  4. a = [a, 2 * a]:将a重新分配为大小为4的数组,并将其赋值为[1, 2, 2, 4]。这一步中,2 * a表示将数组a中的每个元素乘以2,并将结果追加到数组a的末尾。

明显的区别

  • 显式分配(explicit allocation):使用allocate语句明确地分配数组的大小。如果在已分配的对象上再次使用allocate语句,会引发运行时错误。
  • 赋值时分配(allocation on assignment):如果数组已分配且大小不同于源数组的大小,将自动重新分配数组以匹配源数组的大小。

在Fortran中,自动分配和重新分配特性使得开发者不需要显式地调用deallocate来释放现有的数组空间。这个特性简化了内存管理,减少了显式内存管理的复杂性和可能导致的错误。以下是为什么在这种情况下不需要显式调用deallocate的详细解释:

自动重新分配的机制
当你对一个可分配数组(allocatable array)进行赋值操作时,Fortran会自动检查目标数组是否已经分配以及其大小是否合适。如果发现目标数组已经分配且大小不同于新赋值所需的大小,Fortran会自动进行以下操作:

  1. 自动释放当前分配的内存

    • 如果目标数组已经分配并且大小不同,Fortran会自动释放(deallocate)当前数组的内存。
  2. 自动重新分配内存

    • 然后,Fortran会重新分配(allocate)数组,使其大小匹配新的赋值操作所需的大小。
  3. 赋值

    • 最后,Fortran将新值赋给重新分配的数组。

这个过程在赋值语句中是隐式完成的,因此不需要显式调用deallocate

优点和设计意图

  1. 简化代码

    • 自动重新分配特性使代码更加简洁和易读,减少了显式内存管理的代码冗余。
  2. 减少错误

    • 自动管理内存分配和释放,减少了由于手动管理内存而引入的错误,如内存泄漏或重复释放内存。
  3. 提高代码可维护性

    • 代码更加模块化和抽象化,程序员可以专注于业务逻辑,而不是繁琐的内存管理。

自动分配 vs. 显式分配

  • 显式分配

    • 使用allocatedeallocate进行手动内存管理。适用于需要精确控制内存使用的场景。
  • 自动分配

    • 在赋值操作时自动处理内存分配和重新分配。适用于大多数场景,提供了更高层次的内存管理抽象。

自动分配和重新分配功能是Fortran语言设计中的一个重要特性,旨在简化内存管理,提高代码的可靠性和可维护性。通过这个特性,程序员不需要显式调用deallocate来释放内存,因为Fortran会在需要时自动处理这些操作。这种自动化处理不仅减少了编码工作量,还降低了内存管理相关错误的风险。

避免频繁地分配
小心频繁分配!数组在内存中是连续的。这有利有弊。一方面,索引数组是一个快速操作(时间常数,O(1)),因为计算机可以仅根据索引预测元素在内存中的位置。另一方面,插入、追加或删除元素总是会触发整个数组的重新分配!该操作的成本与数组大小成正比(时间线性,O(n))。对于小数组,这可能无关紧要。但是,将一个元素附加到一个包含1亿个元素的数组中,将触发约400 MB的重新分配!如果你不小心,这很容易变成应用程序的性能瓶颈。

14 Fortran中的空数组

在Fortran中,“空数组”指的是一个大小为零的数组。这意味着该数组没有任何元素。空数组在Fortran中的表示和使用非常灵活,可以用于各种情境,如初始化、条件处理和动态调整数组大小。以下是详细的解释和一些示例,展示如何在Fortran中使用空数组。

14-1 空数组的定义和初始化

在Fortran中,可以通过显式赋值或初始化表达式来定义和初始化空数组。

定义空数组
通过显式的分配和声明来定义空数组:

program empty_array_example
    implicit none
    integer, allocatable :: a(:)

    ! 初始化为空数组
    a = [integer::]

    ! 打印数组大小
    print *, 'Size of a:', size(a)  ! 输出: Size of a: 0
end program empty_array_example

在上面的例子中,a被初始化为空数组,其大小为0。

动态分配空数组
也可以通过动态分配的方式创建空数组:

program allocate_empty_array
    implicit none
    integer, allocatable :: b(:)

    ! 动态分配大小为0的数组
    allocate(b(0))

    ! 打印数组大小
    print *, 'Size of b:', size(b)  ! 输出: Size of b: 0
end program allocate_empty_array

使用空数组
空数组在多种情况下都有用处,例如初始化变量、条件处理和调整数组大小等。

追加元素到空数组
可以使用空数组作为初始数组,并动态追加元素:

program append_to_empty_array
    implicit none
    integer, allocatable :: a(:)

    ! 初始化为空数组
    a = [integer::]

    ! 追加元素
    a = [a, 1]
    a = [a, 2]
    a = [a, 3]

    ! 打印数组元素
    print *, 'Elements of a:', a  ! 输出: Elements of a: 1 2 3
end program append_to_empty_array

检查数组是否为空
可以通过检查数组的大小来确定数组是否为空:

program check_empty_array
    implicit none
    integer, allocatable :: a(:)

    ! 初始化为空数组
    a = [integer::]

    ! 检查数组是否为空
    if (size(a) == 0) then
        print *, 'Array a is empty.'
    else
        print *, 'Array a is not empty.'
    end if
end program check_empty_array

14-2 空数组的实际应用

初始化数据
在程序中,初始化数组为空可以简化代码的逻辑,尤其是在处理动态数据时:

program initialize_data
    implicit none
    integer, allocatable :: data(:)

    ! 初始化为空数组
    data = [integer::]

    ! 读取数据并追加到数组(假设有一个函数get_data来获取数据)
    do
        call get_data(data)
        if (no_more_data()) exit
    end do

    ! 打印数据
    print *, 'Data:', data
contains
    subroutine get_data(arr)
        integer, allocatable, intent(inout) :: arr(:)
        integer :: new_value

        ! 假设这里从某个源获取新数据
        read *, new_value

        ! 追加新数据
        arr = [arr, new_value]
    end subroutine get_data

    logical function no_more_data()
        ! 假设这里有逻辑判断是否还有数据
        no_more_data = .false.
    end function no_more_data
end program initialize_data

空数组在Fortran中是一个重要的概念,用于表示大小为零的数组。通过灵活使用空数组,可以简化初始化、条件处理和动态调整数组大小的逻辑,提高代码的可读性和可维护性。了解如何定义、检查和使用空数组,对于编写高效和可靠的Fortran程序是非常有帮助的。

15 可分配数组相关用法

15-1 检查分配状态

有时,知道变量的分配状态是有用的;也就是说,无论当前是否已分配。为此,可以使用内置的已分配函数。

代码示例

real, allocatable :: a(:)
print *, allocated(a)              ! 将输出 F
allocate(a(10))               
print *, allocated(a)              ! 将输出T
deallocate(a)
print *, allocated(a)              ! 将输出F

尝试分配一个已经分配的变量,或者释放一个没有分配的变量,都会触发一个运行时错误。因此,在显式地分配或释放变量之前,应总是检查分配状态。

15-2 捕获数组分配和释放错误

背景
在Fortran编程中,动态内存分配是常见的操作。分配(allocate)和释放(deallocate)内存时,有时会发生错误,例如:

  • 试图分配超过可用内存的数组
  • 分配已分配的对象
  • 释放已释放的对象

当这些错误发生时,默认情况下,程序将中止运行。然而,Fortran提供了内置的错误处理机制,可以让你在分配失败时执行自定义的错误处理逻辑,而不是直接让程序中止。

内置错误处理机制
allocate语句支持两个可选参数:staterrmsg。通过这些参数,你可以捕获并处理分配失败的错误。

参数解释

  • stat:一个整数参数,用于指示allocate语句的状态。如果分配成功,stat将为零;如果分配失败,stat将为一个非零正整数。
  • errmsg:一个字符串参数,用于存储错误信息。如果发生错误(即stat为非零),errmsg将包含错误信息;否则,errmsg的值未定义。

使用示例
以下是一个示例,展示了如何使用staterrmsg来处理分配失败的情况:

program allocation_example
    implicit none
    integer, allocatable :: u(:)
    integer :: stat
    character(len=256) :: errmsg

    ! 试图分配一个非常大的数组
    allocate(u(1000000000), stat=stat, errmsg=errmsg)

    if (stat /= 0) then
        ! 分配失败,输出错误信息
        print *, 'Allocation failed with status:', stat
        print *, 'Error message:', trim(errmsg)
        ! 根据需求,可以选择退出程序或采取其他措施
        stop
    else
        ! 分配成功,进行后续操作
        print *, 'Allocation successful.'
        ! 释放数组
        deallocate(u)
    end if
end program allocation_example

使用内置错误处理机制有以下好处:

  1. 控制程序行为:你可以决定在分配失败时程序应如何继续运行。例如,可以尝试分配较小的内存块,或者优雅地退出程序,而不是让程序突然中止。
  2. 调试和诊断:通过捕获和输出错误信息,可以更容易地诊断和调试内存分配问题。
  3. 提高程序健壮性:处理错误可以提高程序的健壮性,防止因未处理的错误导致的崩溃或意外行为。

通过使用Fortran的内置错误处理机制,你可以在分配和释放内存时更好地控制程序的行为。这不仅可以帮助你捕获和处理错误,还能提高程序的健壮性和可维护性。

16 索引和切片

16-1 索引和切片数组

单个元素索引
要选择单个元素,可以在括号内使用一个整数索引,例如:

adjclose(1)

这将引用数组adjclose的第一个元素。

切片(选取子数组)
要选择一组元素,可以使用起始索引和结束索引,用冒号分隔,例如:

subset = adjclose(5:10)

这将选择adjclose数组从第5个元素到第10个元素,并将结果赋值给subset。在这种情况下,subset将被自动分配为包含6个元素的数组,对应于adjclose数组从索引5到10的值。

步长切片
可以指定步长来跳过一定数量的元素,例如:

subset = adjclose(5:10:2)

这将选择adjclose数组的第5、第7和第9个元素。

特殊情况

  • 如果stride(步长)未给出,默认为1。
  • 如果start大于endstride大于0,或者start小于endstride小于0,结果为一个空数组。
  • 如果start等于end,则切片包含单个元素,例如a(5:5)是一个包含单个元素的数组,而不是标量。

反转数组

function reverse(array)
    real, dimension(:), intent(in) :: array
    real, dimension(size(array)) :: reverse

    reverse = array(size(array):1:-1)
end function reverse

这个函数接受一个实数一维数组作为输入,并返回一个反转顺序的相同数组。可以通过切片操作array(size(array):1:-1)实现数组的反转。

计算股票收益
通过反转数组adjclose,我们可以计算股票在某个时间段内的收益:

adjclose = reverse(adjclose)
gain = adjclose(size(adjclose)) - adjclose(1)

这将数组的顺序反转,使得最早的记录排在最前面。然后,计算数组的最后一个元素和第一个元素之间的差值,表示股票的绝对收益。

使用内置的size函数可以获得数组的大小,以便于引用数组的最后一个元素。

17 Fortran内置函数 all

Fortran的all函数是一个内置的逻辑函数,用于检测数组中所有元素是否都满足特定的条件。它通常与布尔条件一起使用,并返回一个逻辑值(.true..false.)。

17-1 语法

result = all(mask)
result = all(mask, dim)
  • mask:一个逻辑数组或逻辑表达式。
  • dim(可选):一个整数,指定要在其上进行操作的维度。

17-2 返回值

  • 如果不指定dim参数,all函数会返回一个逻辑值,表示数组中所有元素是否都满足条件。
  • 如果指定了dim参数,all函数会返回一个逻辑数组,其中每个元素表示在指定维度上是否所有元素都满足条件。

17-3 示例

以下是一些使用all函数的示例:

示例 1:基本使用

program test_all
    implicit none
    logical :: result
    logical, dimension(5) :: array

    array = [.true., .true., .true., .true., .true.]
    result = all(array)
    print *, "All elements are true:", result  ! 输出: All elements are true: T

    array(3) = .false.
    result = all(array)
    print *, "All elements are true:", result  ! 输出: All elements are true: F
end program test_all

示例 2:带逻辑表达式

program test_all_expression
    implicit none
    logical :: result
    integer, dimension(5) :: array

    array = [1, 2, 3, 4, 5]
    result = all(array > 0)
    print *, "All elements are positive:", result  ! 输出: All elements are positive: T

    array(3) = -3
    result = all(array > 0)
    print *, "All elements are positive:", result  ! 输出: All elements are positive: F
end program test_all_expression

示例 3:多维数组

program test_all_multidim
    implicit none
    logical :: result
    integer, dimension(2, 3) :: array

    array = reshape([1, 2, 3, 4, 5, 6], shape(array))
    result = all(array > 0)
    print *, "All elements are positive:", result  ! 输出: All elements are positive: T

    result = all(array > 0, dim=1)
    print *, "All elements in each column are positive:", result  ! 输出: All elements in each column are positive: T T T

    array(2, 2) = -5
    result = all(array > 0)
    print *, "All elements are positive:", result  ! 输出: All elements are positive: F

    result = all(array > 0, dim=2)
    print *, "All elements in each row are positive:", result  ! 输出: All elements in each row are positive: T F
end program test_all_multidim

17-4 实际应用

在实际应用中,all函数可以用于各种需要检查数组中所有元素是否都满足某个条件的场景。例如,在数据验证、数组比较等操作中,all函数非常有用。

示例 4:数据验证

program validate_data
    implicit none
    logical :: valid
    integer, dimension(10) :: data

    data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

    ! 检查所有数据是否都在1到10之间
    valid = all(data >= 1 .and. data <= 10)
    if (valid) then
        print *, "Data is valid."
    else
        print *, "Data is not valid."
    end if
end program validate_data

Fortran的all函数是一个非常强大的工具,用于检查数组中所有元素是否都满足某个条件。通过all函数,可以简化代码逻辑,提高代码的可读性和可维护性。无论是简单的一维数组还是复杂的多维数组,all函数都能提供有效的解决方案。

18 Fortran内置函数pack

Fortran中的pack函数是一个非常有用的工具,用于从数组中提取满足特定条件的元素,并将这些元素打包到一个新数组中。这个函数在数据筛选和处理方面非常强大,尤其是在处理大型数据集时。

18- 1 语法

result = pack(array, mask [, vector])
  • array:一个要从中提取元素的数组。
  • mask:一个逻辑数组或逻辑表达式,与array的形状相同。它指定了要打包哪些元素。
  • vector(可选):一个数组,用于指定在mask.false.时要填充的默认值。如果省略,则结果数组的大小等于mask.true.值的个数。

返回值
pack函数返回一个一维数组,该数组包含了array中所有满足mask条件的元素。如果指定了vector,结果数组的大小将与vector相同,并在mask.false.的地方填充vector的对应元素。

18-2 示例

以下是几个使用pack函数的示例:
示例 1:基本使用

program test_pack
    implicit none
    integer, dimension(10) :: array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    logical, dimension(10) :: mask
    integer, dimension(:), allocatable :: result

    ! 创建一个掩码,选择偶数元素
    mask = mod(array, 2) == 0

    ! 使用pack函数提取偶数元素
    result = pack(array, mask)

    ! 打印结果
    print *, 'Even elements:', result  ! 输出: Even elements: 2 4 6 8 10
end program test_pack

示例 2:带vector参数

program test_pack_with_vector
    implicit none
    integer, dimension(10) :: array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    logical, dimension(10) :: mask
    integer, dimension(10) :: vector = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
    integer, dimension(:), allocatable :: result

    ! 创建一个掩码,选择偶数元素
    mask = mod(array, 2) == 0

    ! 使用pack函数提取偶数元素,并使用vector填充其他元素
    result = pack(array, mask, vector)

    ! 打印结果
    print *, 'Packed elements with vector:', result  ! 输出: Packed elements with vector: 2 4 6 8 10 16 18 20
end program test_pack_with_vector

示例 3:多维数组

program test_pack_multidim
    implicit none
    integer, dimension(3, 3) :: array = reshape([1, 2, 3, 4, 5, 6, 7, 8, 9], [3, 3])
    logical, dimension(3, 3) :: mask
    integer, dimension(:), allocatable :: result

    ! 创建一个掩码,选择大于5的元素
    mask = array > 5

    ! 使用pack函数提取大于5的元素
    result = pack(array, mask)

    ! 打印结果
    print *, 'Elements greater than 5:', result  ! 输出: Elements greater than 5: 6 7 8 9
end program test_pack_multidim

18-3 实际应用

数据筛选
pack函数可以用于从数据集中提取满足特定条件的子集,例如:

program filter_data
    implicit none
    integer, dimension(100) :: data
    logical, dimension(100) :: mask
    integer, dimension(:), allocatable :: filtered_data
    integer :: i

    ! 初始化数据
    data = [(i, i = 1, 100)]

    ! 创建掩码,选择大于50的元素
    mask = data > 50

    ! 使用pack函数提取大于50的元素
    filtered_data = pack(data, mask)

    ! 打印结果
    print *, 'Filtered data:', filtered_data
end program filter_data

条件处理
在处理需要条件判断的数据时,pack函数可以简化代码逻辑:

program condition_handling
    implicit none
    integer, dimension(10) :: array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    logical, dimension(10) :: mask
    integer, dimension(:), allocatable :: result

    ! 创建一个掩码,选择奇数元素
    mask = mod(array, 2) /= 0

    ! 使用pack函数提取奇数元素
    result = pack(array, mask)

    ! 打印结果
    print *, 'Odd elements:', result  ! 输出: Odd elements: 1 3 5 7 9
end program condition_handling

Fortran的pack函数是一个强大的工具,用于从数组中提取满足特定条件的元素,并将这些元素打包到一个新数组中。通过使用pack函数,可以简化数据筛选和处理的代码逻辑,提高代码的可读性和效率。无论是处理一维数组还是多维数组,pack函数都能提供有效的解决方案。

参考文献

[1] Curcic M. Modern Fortran: Building efficient parallel applications[M]. Manning Publications, 2020.

  • 7
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值