learnxinyminutes-docs/zh-cn/fortran.md
2024-12-08 23:20:53 -07:00

442 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
filename: learnfortran-cn.f90
contributors:
- ["Robert Steed", "https://github.com/robochat"]
translators:
- ["Corvusnest", "https://github.com/Corvusnest"]
---
Fortran 是最古老的计算机语言之一。它由 IBM 开发于 1950 年用于数值运算Fortran 为 "Formula
Translation" 的缩写)。虽然该语言已年代久远,但目前仍用于高性能计算,如天气预报。
该语言仍在持续发展,并且基本保持向下兼容。知名的版本为 Fortran 77, Fortran 90,
Fortran 95, Fortran 2008, Fortran 2015 和 Fortran 2023。
这篇概要将讨论 Fortran 2008 的一些特征。因为它是目前所广泛采用的标准版本,并且与最新版本的内容
也基本相同(而 Fortran 77 则是一个非常不同的版本)。
```fortran
! 这是一个注释
program example ! 声明一个名为 example 的程序
! 代码只能存在于程序、函数、子程序或模块中
! 使用缩进不是必需的,但推荐使用
! 声明变量
! =========
! 所有的声明必须在语句和表达式之前
implicit none ! 防止动态声明变量(推荐!)
! implicit none 推荐在每个函数/程序/模块中重新声明...
! 注意 - Fortran 中对大小写不敏感
real z
REAL Z2
real :: v, x ! 警告:默认的初始值取决于编译器!
real :: a = 3, b = 2E12, c = 0.01
integer :: i, j, k = 1, m
real, parameter :: PI = 3.1415926535897931 ! 声明一个常数
logical :: y = .TRUE., n = .FALSE. ! 布尔类型
complex :: w = (0, 1) ! 单位虚数
character(len=3) :: month ! 字符串,长度为 3 个字符
real :: array(6) ! 声明一个包含 6 个实数的数组
real, dimension(4) :: arrayb ! 另一种声明数组的方式
integer :: arrayc(-10:10) ! 具有自定义索引的数组
real :: array2d(3, 2) ! 多维数组
! 这些分隔符 '::' 并不总是必需的,但推荐使用
! 还有许多其他的变量属性:
real, pointer :: p ! 声明一个指针
integer, parameter :: LP = selected_real_kind(20)
real(kind=LP) :: d ! 长精度变量
! 警告:在声明过程中初始化变量会在函数中造成问题,
! 因为这会自动暗示 'save' 属性,
! 在函数调用之间保存变量的值一般情况下,
! 除了常量外,声明和初始化的代码应该分开!
! 字符串
! =======
character :: a_char = 'i'
character(len=6) :: a_str = "qwerty"
character(len=30) :: str_b
character(len=*), parameter :: a_long_str = "This is a long string."
! 使用 (len=*) 可以自动计算长度,但只适用于常量
str_b = a_str//" keyboard" ! 使用 // 运算符连接字符串
! 赋值和算术
! =============
Z = 1 ! 对上面声明的变量 z 进行赋值(对大小写不敏感)
j = 10 + 2 - 3
a = 11.54/(2.3*3.1)
b = 2**3 ! 幂运算
! 流程控制语句和操作符
! ===================
! 单行 if 语句
if (z == a) b = 4 ! 条件始终需要在括号中
if (z /= a) then ! z 不等于 a
! 其他的比较操作符包括 < > <= >= == /=
b = 4
else if (z .GT. a) then ! z 大于 a
! 文本等价于符号操作符中的 .LT. .GT. .LE. .GE. .EQ. .NE.
b = 6
else if (z < a) then ! 'then' 必须在本行上
b = 5 ! 执行块必须在新的一行上
else
b = 10
end if ! end 语句后需要 'if'(或可以使用 'endif'
if (.NOT. (x < c .AND. v >= a .OR. z == z)) then ! 布尔运算符
inner: if (.TRUE.) then ! 可以对 if 结构命名
b = 1
end if inner ! then 必须命名对应的 endif 语句
end if
i = 20
select case (i)
case (0, 1) ! 当 i == 0 或 i == 1 时
j = 0
case (2:10) ! 当 i 在 2 到 10 之间(包括边界)时
j = 1
case (11:) ! 当 i >= 11 时
j = 2
case default
j = 3
end select
month = 'jan'
! 条件可以是整数、逻辑、或字符类型
! Select 结构也可以命名
monthly:select case(month)
case ("jan")
j = 0
case default
j = -1
end select monthly
do i = 2, 10, 2 ! 循环从 2 到 10包括以 2 为步长
innerloop: do j = 1, 3 ! 循环也可以命名
exit ! 退出循环
end do innerloop
cycle ! 跳到下一个循环迭代
end do
! 虽然存在 Goto 语句,但它被强烈不推荐
goto 10
stop 1 ! 立即停止代码(并返回指定的条件代码)
10 j = 201 ! 这一行被标记为 10 行
! 数组
! =====
array = (/1, 2, 3, 4, 5, 6/)
array = [1, 2, 3, 4, 5, 6] ! 使用 Fortran 2003 的表示法
arrayb = [10.2, 3e3, 0.41, 4e-5]
array2d = reshape([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], [3, 2])
! Fortran 数组索引从 1 开始
! (默认情况下,但对于特定数组可以定义不同的索引)
v = array(1) ! 取数组的第一个元素
v = array2d(2, 2)
print *, array(3:5) ! 打印从第三个到第五个元素(包括)
print *, array2d(1, :) ! 打印二维数组的第一列
array = array*3 + 2 ! 可以对数组应用数学表达式
array = array*array ! 数组操作是逐元素进行的
! array = array*array2d ! 这两个数组是不兼容的
! 有许多内置函数可用于数组
c = dot_product(array, array) ! 这是点积
! 使用 matmul() 进行矩阵运算
c = sum(array)
c = maxval(array)
print *, minloc(array)
c = size(array)
print *, shape(array)
m = count(array > 0)
! 循环数组(通常使用 product() 函数)
v = 1
do i = 1, size(array)
v = v*array(i)
end do
! 条件性地执行逐元素赋值
array = [1, 2, 3, 4, 5, 6]
where (array > 3)
array = array + 1
elsewhere(array == 2)
array = 1
elsewhere
array = 0
end where
! 隐含 do 循环是创建数组的紧凑方式
array = [(i, i=1, 6)] ! 创建一个数组 [1,2,3,4,5,6]
array = [(i, i=1, 12, 2)] ! 创建一个数组 [1,3,5,7,9,11]
array = [(i**2, i=1, 6)] ! 创建一个数组 [1,4,9,16,25,36]
array = [(4, 5, i=1, 3)] ! 创建一个数组 [4,5,4,5,4,5]
! 输入/输出
! =========
print *, b ! 将变量 'b' 打印到命令行
! 可以对打印的输出进行格式化
print "(I6)", 320 ! 打印 ' 320'
print "(I6.4)", 3 ! 打印 ' 0003'
print "(F6.3)", 4.32 ! 打印 ' 4.320'
! 字母表示预期的类型,后面的数字表示用于打印值的字符数
! 字母可以是 I整数F实数E工程表示法
! L逻辑A字符串...
print "(I3)", 3200 ! 打印 '***',因为该数字不适合
! 可以有多个格式规范
print "(I5,F6.2,E6.2)", 120, 43.41, 43.41
print "(3I5)", 10, 20, 30 ! 整数的三次重复(字段宽度为 5
print "(2(I5,F6.2))", 120, 43.42, 340, 65.3 ! 格式重复组合
! 我们还可以从终端读取输入
read (*, *) v
read (*, "(2F6.2)") v, x ! 读取两个数字
! 写入文件
open (unit=12, file="records.txt", status="replace")
! 文件通过 'unit number' 引用,这个数字可以在 9:99 范围内选择
! Status 可以是 {'old','replace','new'} 中的一个
write (12, "(F10.2,F10.2,F10.2)") c, b, a
close (12)
! 读取文件
open (newunit=m, file="records.txt", status="old")
! 文件通过 'new unit number' 引用,编译器为您选择一个整数
read (unit=m, fmt="(3F10.2)") a, b, c
close (m)
! 还有更多功能可用,超出了本文所讨论的范围,
! 还有由于与旧版本的 Fortran 的向后兼容性而存在的替代方案
! 内置函数
! ===========
! Fortran 大约有 200 个语言内部的函数/子程序
! 例如 -
call cpu_time(v) ! 将 'v' 设置为以秒为单位的时间
k = ior(i, j) ! 两个整数的位 OR 运算
v = log10(x) ! 以 10 为底的对数
i = floor(b) ! 返回小于或等于 x 的最接近的整数
v = aimag(w) ! 复数的虚部
! 函数和子程序
! ==============
! 子程序运行一些代码,并可以对输入值产生副作用或修改输入值
call routine(a, c, v) ! 子程序调用
! 函数采用一系列输入参数,并返回一个单个值
! 不过,输入参数可能仍会被修改,并且会执行副作用
m = func(3, 2, k) ! 函数调用
! 函数调用还可以在表达式中使用
print *, func2(3, 2, k)
! 一个纯函数是一个不修改其输入参数,
! 也不会引起任何副作用的函数
m = func3(3, 2, k)
contains ! 包含程序内部定义的子程序的区域
! Fortran 有几种稍微不同的方式来定义函数
integer function func(a, b, c) ! 函数返回一个整数值
! implicit none ! 子变量域可以不再声明 implicit none
integer, intent(in) :: a, b, c ! 在函数内部定义输入参数的类型
if (a >= 2) then
func = a + b + c ! 返回变量默认为函数名
return ! 随时可以从函数返回当前值
end if
func = a + c
! 在函数的末尾不需要 return 语句
end function func
function func2(a, b, c) result(f) ! 返回变量声明为 'f'
integer, intent(in) :: a, b ! 可以声明和强制约定变量
! 不会被函数修改
integer, intent(inout) :: c
integer :: f ! 函数返回类型在函数内部声明
integer :: cnt = 0 ! 注意:初始化暗示变量在函数调用之间保存
!
f = a + b - c
c = 4 ! 修改输入变量的值
cnt = cnt + 1 ! 计算函数调用的次数
end function func2
pure function func3(a, b, c) ! 纯函数不能有副作用
integer, intent(in) :: a, b, c
integer :: func3
func3 = a*b*c
end function func3
subroutine routine(d, e, f)
real, intent(inout) :: f
real, intent(in) :: d, e
f = 2*d + 3*e + f
end subroutine routine
end program example ! 程序定义结束--------------------------
! 函数和子程序在程序列表之外声明,在程序之间以及模块中声明时,需要使用 interface 声明(即使它们在同一源文件中)(见下面)将它们定义在模块或程序的 'contains' 部分更容易
elemental real function func4(a) result(res)
! elemental 函数是一个纯函数,它采用标量输入变量,
! 但也可以在数组上独立应用,并返回一个新的数组
real, intent(in) :: a
res = a**2 + 1.0
end function func4
! 模块
! =======
! 模块是在可重用性中将相关的声明、函数和子程序结合在一起的有用方式
module fruit
real :: apple
real :: pear
real :: orange
end module fruit
module fruity
! 声明的顺序必须是:模块、接口、变量
!(也可以在程序中声明模块和接口)
use fruit, only: apple, pear ! 使用 fruit 模块中的 apple 和 pear
implicit none ! 导入模块之后
private ! 将一些内容私有化(默认为公共)
! 显式将一些变量/函数声明为公共
public :: apple, mycar, create_mycar
! 将一些变量/函数声明为模块私有(本例中是多余的)
private :: func4
! 接口
! ========
! 在模块内部(最好放在 'contains' 部分)显式声明外部函数/过程
interface
elemental real function func4(a) result(res)
real, intent(in) :: a
end function func4
end interface
! 可以使用命名接口定义重载函数
interface myabs
! 可以使用 'module procedure' 关键字包括模块内已经定义的函数
module procedure real_abs, complex_abs
end interface
! 派生数据类型
! ==================
! 可以创建自定义的结构化数据集合
type car
character(len=100) :: model
real :: weight !(千克)
real :: dimensions(3) ! 即,长度-宽度-高度(米)
character :: colour
contains
procedure :: info ! 将过程绑定到类型
end type car
type(car) :: mycar ! 声明自定义类型的变量
! 请查看 create_mycar() 程序的用法
! 注意:模块中没有可以执行的语句
contains
subroutine create_mycar(mycar)
! 演示派生数据类型的用法
type(car), intent(out) :: mycar
! 使用 '%' 运算符访问类型元素
mycar%model = "Ford Prefect"
mycar%colour = 'r'
mycar%weight = 1400
mycar%dimensions(1) = 5.0 ! 默认索引从 1 开始!
mycar%dimensions(2) = 3.0
mycar%dimensions(3) = 1.5
end subroutine create_mycar
subroutine info(self)
class(car), intent(in) :: self
! 使用 'class' 关键字将过程绑定到类型
print *, "Model : ", self%model
print *, "Colour : ", self%colour
print *, "Weight : ", self%weight
print *, "Dimensions: ", self%dimensions
end subroutine info
real pure function real_abs(x)
real, intent(in) :: x
if (x < 0) then
real_abs = -x
else
real_abs = x
end if
end function real_abs
real pure function complex_abs(z)
complex, intent(in) :: z
! 长行可以使用继续字符 '&' 进行延续
complex_abs = sqrt(real(z)**2 + &
aimag(z)**2)
end function complex_abs
end module fruity
```
### 更多资源
了解更多的 Fortran 信息:
+ [wikipedia](https://en.wikipedia.org/wiki/Fortran)
+ [Fortran-lang Organization](https://fortran-lang.org/)
+ [Fortran_95_language_features](https://en.wikipedia.org/wiki/Fortran_95_language_features)
+ [fortranwiki.org](http://fortranwiki.org)
+ [www.fortran90.org/](http://www.fortran90.org)
+ [list of Fortran 95 tutorials](http://www.dmoz.org/Computers/Programming/Languages/Fortran/FAQs%2C_Help%2C_and_Tutorials/Fortran_90_and_95/)
+ [Fortran wikibook](https://en.wikibooks.org/wiki/Fortran)
+ [Fortran resources](http://www.fortranplus.co.uk/resources/fortran_resources.pdf)
+ [Mistakes in Fortran 90 Programs That Might Surprise You](http://www.cs.rpi.edu/~szymansk/OOF90/bugs.html)