Fortran编程语法整理
- 01 Fortran 中的程序单元
- 02 子程序与函数
- 03 stop命令后跟说明
- 04 do concurrent
- 05 纯过程(pure procedure)
- 06 Fortran中的elemental 属性
- 07 Fortran函数和子程序中的可选参数
- 08 Fortran内置模块iso_fortran_env
- 09 在Fortran中自定义模块
- 10 Fortran模块中的私有与公有声明
- 11 在导入模块时重命名导入的实体
- 12 在纯过程中数组不能在声明时直接初始化
- 13 可分配数组的一些用法
- 14 Fortran中的空数组
- 15 可分配数组相关用法
- 16 索引和切片
- 17 Fortran内置函数 all
- 18 Fortran内置函数pack
- 参考文献
01 Fortran 中的程序单元
- main program (主程序) —— 只能从操作系统调用的最高级别程序单元;
- function (函数) —— 从表达式调用的可执行子函数,并且总是返回单个结果;
- subroutine (子程序)—— 一种可执行的子程序,可以就地修改多个参数,但不能在表达式中使用;
- module(模块)—— 变量、函数和子例程定义的不可执行集合;
- submodule(子模块)—— 扩展现有模块,用于定义只有该模块可以访问的变量和过程定义,对于更复杂的应用程序和库非常有用
1-1 submodule(子模块)
在Fortran中,子模块(submodule)是模块系统的一部分,用于帮助组织和管理大型代码库。子模块允许将模块分成多个部分,这些部分可以独立开发和维护,从而提高代码的可读性和可维护性。
下面是子模块的一些关键点和示例:
关键点:
- 主模块:定义公共接口、类型和公共变量。
- 子模块:实现主模块中定义的接口和包含实际的代码逻辑。
- 结构:子模块依赖于主模块,子模块中的任何子程序都必须在主模块中声明。
- 可见性:子模块可以访问主模块中的所有公共实体。
示例:
# 主模块(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
详细解释:
- 主模块 (Module)
module my_module
定义了一个名为my_module
的主模块。- 在
contains
部分,声明了一个子程序my_subroutine
,但没有提供实现。这告诉编译器子程序将在子模块中定义。
- 子模块 (Submodule)
submodule (my_module) my_submodule
声明了一个名为my_submodule
的子模块,并指定它从属于my_module
。- 在
contains
部分,实现了my_subroutine
子程序。
- 主程序 (Main Program)
use my_module
使主模块my_module
中的所有公共实体在主程序中可用。call my_subroutine
调用子模块中的my_subroutine
子程序。
这种结构允许更好的代码组织和模块化,使得大型Fortran项目更容易管理和维护。
02 子程序与函数
2-1 概述
![[Pasted image 037.png|500]]
- Fortran中函数可以接收任意数量的输入参数,但是只能返回一个结果。如果有多个结果需要返回可以使用数组或自定义数据类型。
- 对于没有副作用的计算,应该使用函数来完成。
在Fortran以及其他编程语言中,副作用(side effect)指的是在函数执行过程中对其外部状态(如全局变量、输入参数、I/O操作等)的修改。没有副作用的函数(即纯函数)只依赖于其输入参数,并且除了返回值外不会对程序的其他部分产生影响。这种函数的输出仅由输入决定,不会改变任何外部状态。
在Fortran中,当提到“在没有副作用时使用函数”时,通常指的是:
- 函数仅依赖于输入参数:函数的计算结果完全由传递给它的参数决定,没有依赖或修改外部变量。
- 没有修改输入参数:函数不会通过传递的参数修改外部变量的值。
- 没有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
的元素被赋予不同的值。
注意事项:
- 独立性:
DO CONCURRENT
结构的每次迭代必须是独立的,也就是说,每次迭代之间不应有数据依赖性。 - 副作用:避免在
DO CONCURRENT
结构中使用具有副作用的操作(如 I/O 操作),因为这可能导致不可预测的行为。 - 性能:尽管
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
关键字。
\
纯过程的规则:
- 不能改变通过参数传递的任何变量的值。
- 不能更改模块、公共或局部变量的值。
- 不能执行输入/输出(I/O)操作。
- 不能调用非纯过程。
示例:
纯函数(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
是一个纯函数,它接受两个整数参数 a
和 b
,并返回它们的和。因为它不修改任何外部状态,也不执行 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
是一个纯子程序,它交换两个整数参数 a
和 b
的值。尽管它修改了参数的值,但它不修改任何外部状态或执行 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
来交换两个整数的值。
纯过程的优点:
- 可并行化:因为纯过程没有副作用,编译器可以更容易地并行化这些过程。
- 优化:纯过程更容易被编译器优化,因为它们的行为是确定的。
- 可测试性:纯过程由于没有副作用,更容易进行单元测试。
通过使用纯过程,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
在这个示例中:
- 对标量
a
调用了square
函数,并输出结果。 - 对数组
array
调用了square
函数,结果存储在result_array
中,并输出结果。 - 调用了
add_one
子程序,对数组array
的每个元素加一,并输出修改后的数组。
优点:
- 简洁:不需要显式编写循环,代码更加简洁易读。
- 性能:编译器可以更容易地进行向量化和并行化优化,提高性能。
- 通用性:相同的过程可以同时处理标量和数组,提高代码复用性。
通过使用 elemental
关键字,可以简化数组操作的代码,同时利用编译器优化以提高性能。
6-2 elemental 关键字默认对纯过程使用
在Fortran中,elemental
过程必须是纯过程。这是因为 elemental
过程的每个元素操作是独立的,不应该有任何副作用。为了确保这一点,elemental
过程需要符合纯过程的规则。
纯过程的规则复习:
- 不能改变通过参数传递的任何变量的值(除非它们是
intent(out)
或intent(inout)
)。 - 不能更改模块、公共或局部变量的值。
- 不能执行输入/输出(I/O)操作。
- 不能调用非纯过程。
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
在这个示例中:
- 对标量
a
调用了square
函数,并输出结果。 - 对数组
array
调用了square
函数,结果存储在result_array
中,并输出结果。 - 调用了
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
解释
- 子程序中的可选参数:
- 定义子程序
example_subroutine
,其中第二个参数b
被标记为optional
。 - 使用
present(b)
来检查可选参数b
是否被提供。 - 在调用子程序时,可以只传递一个参数
a
,也可以传递两个参数a
和b
。
- 定义子程序
- 子函数中的可选参数:
- 定义子函数
example_function
,其中第二个参数b
被标记为optional
。 - 同样,使用
present(b)
来检查可选参数b
是否被提供。 - 在调用子函数时,可以只传递一个参数
a
,也可以传递两个参数a
和b
。
- 定义子函数
通过这种方式,可以灵活地处理参数的传递,并根据是否提供了可选参数来执行不同的操作。
08 Fortran内置模块iso_fortran_env
Fortran 的内置模块 ISO_FORTRAN_ENV
提供了一些有用的参数和功能,可以用于控制程序的行为和提高代码的可移植性和可维护性。以下是对 ISO_FORTRAN_ENV
模块的一些详细介绍及其常见用途。
8-1常见参数和功能
- 数值种类参数:
REAL32
,REAL64
,REAL128
:分别对应 32 位、64 位和 128 位的实数类型。INT8
,INT16
,INT32
,INT64
:分别对应 8 位、16 位、32 位和 64 位的整数类型。
- 字符种类参数:
CHARACTER_KINDS
:返回一个整数数组,表示可用的字符种类。
- 输入/输出参数:
INPUT_UNIT
:标准输入单元号。OUTPUT_UNIT
:标准输出单元号。ERROR_UNIT
:标准错误输出单元号。
- 文件状态参数:
IOSTAT_END
:表示文件结束的 IOSTAT 值。IOSTAT_EOR
:表示记录结束的 IOSTAT 值。
- 常量:
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 = x = 3.9793d0 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
解释
- 数值种类参数:
- 使用
int32
和real64
定义整数和实数变量,以确保它们具有特定的位数,这有助于提高代码的可移植性。
- 使用
- 输入/输出参数:
- 使用
input_unit
,output_unit
和error_unit
来处理标准 I/O 操作。这使得代码更具可读性,并且减少了硬编码的单元号。
- 使用
8-3 总结
ISO_FORTRAN_ENV
模块提供了许多有用的常量和参数,可以帮助编写更具可移植性和可维护性的 Fortran 代码。通过使用这些参数和功能,可以更加轻松地控制数值类型和 I/O 操作,并确保代码能够在不同的平台上运行一致。
8-4 Fortran的内置模块
Fortran 提供了五个内置模块:iso_fortran_env
、iso_c_binding
、ieee_arithmetic
、ieee_exceptions
和 ieee_features
。iso_fortran_env
提供了一些有用的过程和参数,iso_c_binding
提供了与 C 语言函数和数据结构接口的功能。后面三个模块提供了特定于浮点运算的功能,一般来说,它们不像前两个模块那样有用
在导入自己不熟悉的模块时,最好加上only关键字,否则有时会出现匪夷所思的问题。
09 在Fortran中自定义模块
![[Pasted image 150.png|600]]
建议每个源文件只保留一个模块。
10 Fortran模块中的私有与公有声明
在 Fortran 模块中,可以使用 public
和 private
语句来控制模块内数据和子程序的可见性。public
声明的实体可以被模块外部访问,而 private
声明的实体则只能在模块内部使用。这种控制机制有助于封装和信息隐藏,提高代码的可维护性和可靠性。
10-1 示例
以下是一个包含 public
和 private
声明的 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 解释
- 模块声明:
module my_module
声明了一个名为my_module
的模块。implicit none
禁用隐式变量声明,强制所有变量显式声明。
- 默认可见性:
public
声明将模块的默认可见性设置为公有。private
声明将private_subroutine
设置为私有。
- 公有和私有变量:
public_variable
是公有变量,可以在模块外部访问。private_variable
是私有变量,只能在模块内部访问。
- 公有和私有子程序:
public_subroutine
是公有子程序,可以在模块外部调用。private_subroutine
是私有子程序,只能在模块内部调用。- 在
public_subroutine
中调用了private_subroutine
,这在模块内部是允许的。
- 程序块:
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.9793 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 总结
通过使用 public
和 private
声明,可以有效地控制模块内数据和子程序的可见性。这有助于实现数据的封装和信息隐藏,提高代码的安全性和可维护性。
11 在导入模块时重命名导入的实体
在 Fortran 中,可以通过 use
语句导入模块,并来重命名模块中的实体。这种方法在避免名称冲突以及使代码更加清晰和具有描述性方面非常有用。
11-1 示例:重命名导入的实体
创建一个模块 mod_circle
,并在导入时重命名其中的实体。
模块定义 (mod_circle
)
module mod_circle implicit none public :: circle_area, circle_circumference real, parameter :: pi = 3.9793 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
解释
- 模块定义 (
mod_circle
):- 模块
mod_circle
定义了两个公有函数circle_area
和circle_circumference
,分别用于计算圆的面积和周长。
- 模块
- 使用模块并重命名实体:
use mod_circle, only: area => circle_area, circumference => circle_circumference
:use mod_circle
导入mod_circle
模块。only
子句指定只导入circle_area
和circle_circumference
两个函数。area => circle_area
表示将模块中的circle_area
函数重命名为area
。circumference => circle_circumference
表示将模块中的circle_circumference
函数重命名为circumference
。
- 程序块:
program test_circle
使用了mod_circle
模块,并调用了重命名后的area
和circumference
函数。- 用户输入圆的半径,程序计算并输出面积和周长。
总结
通过重命名导入的实体,避免名称冲突,并使代码更加清晰和易读。在大型项目中,特别是在多个模块可能具有相同函数名或变量名时,这种方法非常有用,可以帮助组织和管理代码。
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
在这个示例中:
- 主程序 (
program pure_procedure_example
):- 声明了一个长度为 5 的整型数组
arr
。 - 调用纯子程序
initialize_array
来初始化数组arr
。
- 声明了一个长度为 5 的整型数组
- 纯子程序 (
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]
a = [integer::]
:将a
初始化为空数组。a = [a, 1]
:重新分配a
,将其大小设置为1,并赋值为[1]
。a = [a, 2]
:再次重新分配a
,将其大小设置为2,并赋值为[1, 2]
。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会自动进行以下操作:
- 自动释放当前分配的内存:
- 如果目标数组已经分配并且大小不同,Fortran会自动释放(deallocate)当前数组的内存。
- 自动重新分配内存:
- 然后,Fortran会重新分配(allocate)数组,使其大小匹配新的赋值操作所需的大小。
- 赋值:
- 最后,Fortran将新值赋给重新分配的数组。
这个过程在赋值语句中是隐式完成的,因此不需要显式调用deallocate
。
优点和设计意图:
- 简化代码:
- 自动重新分配特性使代码更加简洁和易读,减少了显式内存管理的代码冗余。
- 减少错误:
- 自动管理内存分配和释放,减少了由于手动管理内存而引入的错误,如内存泄漏或重复释放内存。
- 提高代码可维护性:
- 代码更加模块化和抽象化,程序员可以专注于业务逻辑,而不是繁琐的内存管理。
自动分配 vs. 显式分配:
- 显式分配:
- 使用
allocate
和deallocate
进行手动内存管理。适用于需要精确控制内存使用的场景。
- 使用
- 自动分配:
- 在赋值操作时自动处理内存分配和重新分配。适用于大多数场景,提供了更高层次的内存管理抽象。
自动分配和重新分配功能是Fortran语言设计中的一个重要特性,旨在简化内存管理,提高代码的可靠性和可维护性。通过这个特性,程序员不需要显式调用deallocate
来释放内存,因为Fortran会在需要时自动处理这些操作。这种自动化处理不仅减少了编码工作量,还降低了内存管理相关错误的风险。
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
语句支持两个可选参数:stat
和errmsg
。通过这些参数,你可以捕获并处理分配失败的错误。
参数解释
- stat:一个整数参数,用于指示
allocate
语句的状态。如果分配成功,stat
将为零;如果分配失败,stat
将为一个非零正整数。 - errmsg:一个字符串参数,用于存储错误信息。如果发生错误(即
stat
为非零),errmsg
将包含错误信息;否则,errmsg
的值未定义。
使用示例
以下是一个示例,展示了如何使用stat
和errmsg
来处理分配失败的情况:
program allocation_example implicit none integer, allocatable :: u(:) integer :: stat character(len=256) :: errmsg ! 试图分配一个非常大的数组 allocate(u(), 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
使用内置错误处理机制有以下好处:
- 控制程序行为:你可以决定在分配失败时程序应如何继续运行。例如,可以尝试分配较小的内存块,或者优雅地退出程序,而不是让程序突然中止。
- 调试和诊断:通过捕获和输出错误信息,可以更容易地诊断和调试内存分配问题。
- 提高程序健壮性:处理错误可以提高程序的健壮性,防止因未处理的错误导致的崩溃或意外行为。
通过使用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
大于end
且stride
大于0,或者start
小于end
且stride
小于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.
到此这篇fortran入门_fortran属于系统软件的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!版权声明:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权、违法违规、事实不符,请将相关资料发送至xkadmin@xkablog.com进行投诉反馈,一经查实,立即处理!
转载请注明出处,原文链接:https://www.xkablog.com/fortranbc/2251.html