Dr.Shi's Studio.

Fortran学习笔记(一)

Word count: 4.5kReading time: 17 min
2023/03/07
loading

基础知识

一个完整的 Fortran 代码,一般来说是以 program code_name 开始、以 end program code_name 结束的首末声明,变量声明以及需要执行的命令或者操作三个部分组成。通用的 Fortran 代码文件的文件后缀可以是 .f90、.f、.g95 等等,但通常为了统一和适应不同版本的 Fortran 编译环境建议采用 .f90 后缀。和 C、C++ 语言比较类似,Fortran 代码也是通过编译执行。比如对如下所示的示例代码执行 gfortran code_name.f90 -o code_name 即可生成可执行程序 code_name,再执行 ./code_name 则会输出代码执行结果如下。

1
2
3
4
5
6
7
8
9
program code_name
implicit none
real x, y, z
x = 2
y = 3
z = x + y * 5
print *, x, y
print *, 'z = ', z
end program code_name

输出

1
2
  2.00000000       3.00000000    
z = 17.0000000

这里我们发现在上面给出的示例代码中有一行 implicit none,明明本来的代码已经符合三个部分了,为什么要多加这一行呢?这是因为在旧 Fortran 中存在隐式类型声明,为了规避在 Fortran 95 以后的版本中可能出现错误,特意在每个开头声明之后都加上这么一行。这样我们在写代码的过程中如果使用到了未提前声明或者打错字符的变量名,编译程序就会报错告知我们。这对于形成良好的代码习惯和增强代码可维护性有很大的帮助,因此此行声明必不可少。

运算符

Fortran 语言和别的高级编程语言一样,都拥有完整的运算符,包括简单的加减乘除、复杂的乘方等运算。如下表所示是 Fortran 语言中的运算符的写法和使用方法,其中运算符的计算优先顺序与数学计算中一致:乘方运算 > 乘除运算 > 加减运算。

运算符 运算含义 示例 数学含义
+ x+y x与y的和
- x-y x与y的差
* x*y x与y的积
/ x/y x与y的商
** 乘方 x**y x的y次方
- -x x的负数

数据类型与变量声明

Fortran 语言的数据类型比较简单,没有像其他高级编程语言那样分得那么细,主要分为整数型和实数型两个大类。整数型的计算与 C、C++、Python 中有点类似,整数与整数的商还是一个整数。即使当不整除的时候,为了保持结果和参与运算的变量类型一致 Fortran 编译器会只取正常数学运算结果的整数部分,如下示例所示。实数型其实与数学意义上的实数是一致的,既包括整数也包括小数,所以当不整除的除法中的两个变量有一个为实数型时,Fortran 编译器会将两个变量类型对应成实数型并计算出正常的带小数的结果,如下示例所示。 因为所有变量都要在使用之前声明类型,所以当属于同一种类型时我们可以按照下面那样写在同一行,不同变量之间用逗号分隔开。为了代码美观,建议在逗号与后一个变量名之间留一个空格。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
!!! 两个整数型相除
integer x, y
x = 2
y = 3
print *, x/y ! 结果为 0
print *, y/x ! 结果为 1

!!! 被除数为实数型,除数为整数型
real m
integer n
m = 10
n = 3
print *, m/n ! 结果为 3.33333325

!!! 被除数为整数型,除数为实数型
integer m
real n
m = 10
n = 3
print *, m/n ! 结果为 3.33333325

!!! 被除数和除数均为实数型
real m, n
m = 10
n = 3
print *, m/n ! 结果为 3.33333325

所有数据类型总结如下:

数据类型 标识符 示例
整数型 integer 2
实数型 real 2.0
双精度实数型 double precision 2.0
复数型 complex (1.0, 2.0)
双精度复数型 complex(kind(0d0)) (1.0, 2.0)
布尔型 logical true/false
字符串型 character "Text"

数学函数总结如下:

写法 函数名称
sqrt(x) 平方根
sin(x) 正弦函数
cos(x) 余弦函数
tan(x) 正切函数
asin(x) 反正弦函数
pcos(x) 反余切函数
atan(x) 反正切函数
atna2(y,x) 反正切函数
exp(x) 指数函数
log(x) 对数函数
log10(x) 常用对数函数
sinh(x) 双曲正弦函数
cosh(x) 双曲余弦函数
tanh(x) 双曲正切函数
real(n) 实数化
abs(n) 绝对值
mod(m,n) 求余
int(x) 整数化
mint(x) 整数化
sign(x,s) 符号变更
real(z) 实部
imag(z) 虚步
cmplx(x,y) 复数化
conjg(z) 共轭复数

打印输出 Fortran 的打印输出其实已经在前面的示例代码中已经提到了,就是所谓的 print 语句。print 语句必须紧接着 *,其含义是按照标准格式输出,如果缺失这个的话在编译时就会报出 Error: Expected comma in I/O list at (1) 的错误。正确的打印输出示例代码如下:

1
2
3
4
real m
m = 5
print *, 'm = ', m, ' m*2 = ', m*2
print *, 'm = ',m, ' m*2 = ',m*2

输出

1
2
m =    5.00000000       m*2 =    10.0000000
m = 5.00000000 m*2 = 10.0000000
这里输出字符串的时候引用字符串的符号可以使用',也可以使用"。本质上没有什么区别,只是需要成对使用,不然就会报错。

数组定义 Fortran也有数组一样的数据类型来支持这一需求。如下所示,我们可以直接采用原有的整数型、实数型和复数型定义数组。当括号中只有一个数字时,表示是一维数组;当括号中有逗号分割的两个数字时,表示是二维数组;三维数组以此类推。二维数组可以用于表示我们所熟知的矩阵、行列式等数据类型。

1
2
3
integer m(10)         ! 长度为 10 的整数型数组
real x(20), y(4, 5) ! 长度为 20 的实数型数组, 4 行 x 5 列的二维实数型数组
complex matrix(5, 5) ! 5 行 x 5 列的二维复数型数组

这里需要注意的是,在定义数组的时候不仅可以用正整数,也可以用负整数和 0。比如 real n(-3:5) 表示定义一个长度为 9、序号从 -3 到 5 (包括 0)的一维数组,real m(-2:1, 0:4) 表示定义一个 3 行 x 4 列、行序号从 -2 到 0、列序号从 1 到 4 的二维数组。一般来说,为了与实际数学运算中的理解保持一致,建议采用正整数来定义数组更加方便。

Fortran 数组中的序号是从 1 开始的,即写作 n(1),这点与其他高级编程语言略有不同,不过符合人类的数学认识。因此如下所示,当定义一个长度为 10 的数组 n 时,数组 n 的最后一个元素就是 n(10)。在实际的存储中,我们能够很容易理解一维数组是按照 1 到 10 的顺序从左往右排列的,但是在二维数组中又会是怎么样呢?是行优先还是列优先呢?一般来说,在我们接触过的高级编程语言中几乎都是行优先的,即先存储第一行再存储第二行,以此类推。但是,在 Fortran 中则是列优先的,也就是说 Fortran 的二维数组存储时会先存储第一列再存储第二列,再以此类推。

1
2
real n(10)     ! n(1) ~ n(10)
real m(4, 4) ! m(1, 1) m(2, 1) m(3, 1) ... m(4, 4)

当我们想要去选取数组中的某一个元素时,可以直接通过对应的序号进行选取。但是如果想要选多个元素或者某一行、某一列时,这就有点不同了。比如说,现在有一个长度为 10 的一维数组 n,我们想要获取到从第 3 个元素到第 5 个元素的 3 个元素,应该使用 : 来将序号的上下限分隔开同时选中,即 n(3:5)。如果有一个 3 行 x 4 列 的二维数组 m,我们想要获取到第 2 行到第 3 行的所有元素,则应该使用 m(2:3, 1:4) 来选取。

跨行和注释

有的时候我们编写的计算式可能会比较长,这个时候为了代码和公式的高可阅读性,我们通常采用 Fortran 语言所提供的跨行功能。比如说如下所示的原打印输出,就可以在中途使用 & 符号来声明下一行是前一行的后续,这样的执行结果是一致的。

1
2
3
4
5
print *, zhang, qian, sun, li, zhou, wu, zhen, wang

!!! 可以换成
print *, zhang, qian, sun, li &
, zhou, wu, zhen, wang
上面的例子是变量的跨行输出,如果是一个长字符串,也是可以使用一样的方式的,只是需要在中断的前一行末尾和下一行开头同时加上 & 符号,如下所示。
1
2
3
4
5
print *, 'Fortran is so good for the scientific calculation.'

!!! 可以换成
print *, 'Fortran is so good &
&for the scientific calculation.'

我们之前在定义变量的时候已经提过,为了代码整洁性可能会同时将同一类型的变量放置在同一行,之间用逗号分割即可。如果是在给变量赋值的时候,是不是也能够将赋值式放置在同一行呢?原则上来说,如果赋值式比较短的话,是可以将多行赋值式缩短在同一行的,它们之间使用 ; 进行连接。这里值得注意的是,最后一个赋值式后面一定不要多加 ;,如下所示。

1
2
3
4
5
6
7
!!! 修改前
x = 3
y = 4
z = 5

!!! 修改后
x = 3; y = 4; z = 5

Fortran 语言中的注释其实在上面的内容中也已经接触到了,! 之后的内容将会被 Fortran 编译器认为是注释内容。一般来说,一个 ! 的注释其实就已经足够了,但是为了区分行注释与行末注释,建议在行注释的时候使用三个 !,而在行末注释时使用一个 !。当然如果存在相邻多行同时进行注释,或者在一个可视窗口内有多个行末注释,建议协调成同一列以增强代码整洁性。

循环语句

do循环 Fortran 的最简单的循环语句称为do语句,其形式如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
!!! 不设置步长,默认为 1
do 整数型变量 = 起始值, 结束值
......
......
enddo

!!! 示例 1
integer a(10)
do n = 1, 10 ! 计算 1,2,3,4,5,6,7,8,9,10
a(n) = n
enddo

!!! 设置步长,可以设置正整数和负整数,不能为 0
do 整数型变量 = 初始值, 结束值, 步长
......
......
enddo

!!! 示例 2
integer a(5)
do n = 1, 10, 2 ! 计算 1,3,5,7,9
a(nint(n/2)) = n
enddo

!!! 示例 3
integer a(5)
do n = 10, 1, -2 ! 计算 10,8,6,4,2
a(n/2) = n
enddo

在使用 do 循环语句的时候,我们需要注意在结束循环语句后循环条件变量 n 仍然存在。如果后续计算中还使用到了相同变量,务必要进行重新初始化赋值,否则可能造成数据上的污染。我们可以拿示例 3 举个例子,当循环结束后,n 的值其实为 0 (不是 2)。虽然此时的 n 值并不满足循环继续的条件,但是已经完成了对步长的迭代计算,所以如果后续还是使用相同的变量 n,我们期待的 n 初始值与循环结束后的 n 值就可能不一样。建议在将某个变量设置为循环条件变量之后,循环外尽量不要使用同一个变量,从而避免这一可能存在的干扰。

do 循环语句实际上也可以存在嵌套,即一个 do 循环语句包含了其他的 do 循环语句,如下示例 4 所示。其中需要注意的是,在多重 do 循环语句中,循环语句的条件变量不要使用相同的,否则很可能出现了我们意料之外的问题。建议同一个 do 循环嵌套语句中使用不同的条件变量。如果不在同一个 do 循环嵌套语句中,使用相同的条件变量产生问题的可能性比较小。

1
2
3
4
5
6
7
8
9
!!! 示例 4
integer a(10)
integer b(10, 5)
do n = 1, 10
a(n) = n
do m = 1, 10, 2
b(n, nint(m/2)) = a(n) + m
enddo
enddo

while 循环

1
2
3
4
5
6
7
8
9
10
11
12
13
do while (条件)
......
......
enddo

!!! 示例 5
!!! 计算并输出 10,9,8,7,6,5,4,3,2,1 的平方
integer n
n = 10
do while (n > 0)
print *, n**2
n = n - 1
enddo

if 语句 if 语句最简单的方式就是,将条件判断语句与执行语句放在同一行,如下所示。这也是因为执行语句比较短,放置在同一行反而代码更加美观。当然一般来说执行语句可能不是一行,甚至是多分支、多重的,因此也有单分支 if 语句、双分支 if 语句、多重 if 语句,如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
!!! 最简单的 if 语句
if (条件) 执行语句

!!! 示例 6
integer n
integer i
n = 10
i = -4
if (i < 0) n = 5
print *, n


!!! 单分支 if 语句
if (条件) then
......
......
endif

!!! 示例 7
if (i < 0) then
n = 5
endif
print *, n


!!! 双分支 if 语句 (一般)
if (条件) then
......
else
......
endif

!!! 示例 8
if (i > 0) then
n = 8
else
n = 5
endif
print *, n


!!! 多重 if 语句
if (条件 1) then
.....
else if (条件 2) then
......
else
.....
endif
endif

!!! 示例 9
if (i > 0) then
n = 8
else if (i > -2) then
n = 6
else
n = 5
endif
endif
print *, n

在使用条件语句对条件变量进行比较判断时所使用的比较符号 |比较条件符号|含义| |:--------:|:--:| |==|等于| |/=|不等于| |>|大于| |>=|大于等于| |<|小于| |<=|小于等于|

当我们使用条件语句的时候还有一类使用场景,就是判断一个变量值是否在一个区间,即存在一个下限值和一个上限值。如果使用我们上述的 if 语句恐怕只有多重 if 语句才能满足我们的需求了,但是这代码写起来可能有点显得多余,毕竟我们在逻辑上是想要同时判断变量值与上下限值的大小。所以 Fortran 语言的 if 语句中也存在逻辑运算符 .and.、.or. 和 .not.,具体如下表所示。

逻辑运算符 含义
条件1.and.条件2 同时满足条件1和条件2
条件1.or.条件2 满足条件1或条件2
.not条件 不满足条件
1
2
3
4
5
6
!!! 示例 10
if (i < 0 .and. i > -2) then
n = 5
else
n = 10
endif

我们在其他高级编程语言中可能已经习惯了类似于 -2 < n < 0 这样的写法,有的时候在 Fortran 语言中也会不自觉地写成这样。但是在 Fortran 语言中这种写法是不被支持的,我们需要使用逻辑运算符 .and. 来把上下限值判断连接起来,而不能写在一起。

goto 语句

如下所示,这里的 goto 语句的用法与汇编语言的 goto 语句有点类似,指定一个行号即可无条件跳转到该行执行。如果是像下面这样调用 goto 语句的行在跳转到的行之前,那么它们之间的行都会被完全忽略。如示例 11 所示,a = 15 的赋值语句会被忽略所以 a 的值是在 goto 语句执行之前赋的值 10;同理,b = 11 的赋值语句在 goto 语句和跳转行之间会被忽略,跳转行的 b = 20 生效。如示例 12 所示,当 goto 语句跳转到它之前行执行时,就会造成无限循环,每次执行到 goto 语句就会又跳转到前面,这样 goto 语句后面的命令都不会被执行。我们在使用 goto 语句时对跳转的行一定要非常注意,一不小心很容易造成死循环。建议使用 goto 语句的时候跳转到其后的行,杜绝跳转到前面的行。

为了提高代码可读性,我们不想将 goto 语句跳转的行直接指向一个操作或者命令,这时我们就可以用 continue 来代替跳转行,如示例所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
goto 行号

行号 操作或命令

!!! 示例 11
a = 10
goto 8
b = 11
a = 15
8 b = 20
print *, a, b ! 此处输出 a 的值为 10,b 的值为 20

!!! 示例 12

8 a = 10
b = 11
a = a + b
goto 8
b = b - a ! 永远不会被执行
print *, a, b ! 永远不会被执行

!!! 示例 13
8 continue
a = 10
b = 11
a = a + b
goto 8

exit 语句 当我们在使用 do 循环语句时,如果想要在某个与循环条件无关的条件满足时提前退出循环,就要使用 if 语句和 exit 语句的组合了。如示例 14 所示,当 sum 的值超过 10 时循环就会被提前终止。由于 1+2+3+4=10,所以 i 加到 5 才会使得 sum 值超过 10,此时的 sum 则为 1+2+3+4+5=15,而 i 也因为提前退出循环而不会执行自增操作,所以此时输出的 i 值为最后的 5。如果将原来的 exit 语句换成 goto 语句,并将 goto 语句指向的跳转行设为循环外的下一行,则最后的效果也是一样的,如示例所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
!!! 示例 14
sum = 0
do i = 1, 10
sum = sum + i
if (sum > 10) exit
enddo
print *, i, sum ! 输出 i 的值为 5,sum 的值为 15

!!! 示例 15
sum = 0
do i = 1, 10
sum = sum + i
if (sum > 10) goto 8
enddo
8 print *, i, sum ! 输出 i 的值仍为 5,sum 的值仍为 15
CATALOG
  1. 1. 基础知识
  2. 2. 运算符
  3. 3. 数据类型与变量声明
  4. 4. 循环语句