陷阱#
Fortran 语法通常简单且一致,但与许多其他语言一样,它也有一些缺陷。有时是由于遗留原因(Fortran 标准的演变强烈强调向后兼容性,因为大量的遗留代码仍在积极使用),有时是由于真正的缺陷(在某些时候做出了糟糕的选择,如果不破坏向后兼容性,则很难甚至不可能纠正),有时仅仅是因为 Fortran 不是 C 或 C++ 或 Python 或其他任何语言:它有自己的逻辑,其功能有时可能会让习惯于其他语言的开发人员感到惊讶。
所有代码片段均使用 gfortran 13 编译。
隐式类型#
program foo
integer :: nbofchildrenperwoman, nbofchildren, nbofwomen
nbofwomen = 10
nbofchildrenperwoman = 2
nbofchildren = nbofwomen * nbofchildrenperwoman
print*, "number of children:", nbofchildrem
end program
程序编译并执行,结果为
number of children: 0
等等……Fortran 无法将两个整数相乘?当然不是……问题在于打印变量名称时输入错误:nbofchildreM
而不是 nbofchildreN
。但是为什么编译器没有发现这个错误呢?好吧,因为默认情况下 Fortran 使用隐式类型:当遇到尚未显式声明类型的变量时,编译器会根据名称的首字母推断类型。以 I、J、K、L、M、N 开头的变量名称类型为 INTEGER
,其他所有变量名称类型为 REAL
(因此有了经典的笑话“GOD
是 REAL
,除非声明为 INTEGER
”)。
隐式类型与 Fortran 一样古老,在那个时代没有显式类型。尽管它在快速编写一些测试代码时仍然很方便,但这种做法很容易出错,因此不建议使用。强烈建议的最佳实践是在所有程序单元(主程序、模块和独立例程)的开头始终禁用隐式类型,方法是声明 implicit none
(在 Fortran 90 中引入)。
program foo
implicit none
integer :: nbofchildrenperwoman, nbofchildren, nbofwomen
nbofwomen = 10
nbofchildrenperwoman = 2
nbofchildren = nbofwomen * nbofchildrenperwoman
print*, "number of children:", nbofchildrem
end program
现在编译失败了,可以快速更正输入错误
7 | print*, "number of children:", nbofchildrem
| 1
Error: Symbol 'nbofchildrem' at (1) has no IMPLICIT type; did you mean 'nbofchildren'?
隐式保存#
subroutine foo()
implicit none
integer :: c=0
c = c+1
print*, c
end subroutine
program main
implicit none
integer :: i
do i = 1, 5
call foo()
end do
end program
习惯于 C/C++ 的人预计此程序会打印 5 次 1
,因为他们将 integer :: c=0
解释为声明和赋值的串联,就像它一样
integer :: c
c = 0
但事实并非如此。此程序实际上输出
1
2
3
4
5
integer :: c=0
实际上是一次性的编译时初始化,它使变量在对 foo()
的调用之间保持持久性。它实际上等价于
integer, save :: c=0
save
属性等效于 C 的 static
属性,用于使变量持久化,并且在变量初始化的情况下隐式使用。与遗留(并且仍然有效)语法相比,这是一种现代化的语法(在 Fortran 90 中引入)。
integer c
data c /0/
老 Fortran 程序员只知道现代化语法等效于遗留语法,即使未指定 save
。但事实上,隐式保存可能会误导习惯于 C 逻辑的新手。因此,通常建议始终指定 save
属性
integer, save :: c=0 ! save could be omitted, but it's clearer with it
注意:派生类型组件的初始化表达式是完全不同的情况
type bar
integer :: c = 0
end type
在此,c
组件在每次实例化 type(bar)
变量时初始化为零(运行时初始化)。
浮点字面常量#
以下代码片段定义了一个双精度常量 x
(在大多数系统上,它是一个 IEEE754 64 位浮点数,具有 15 位有效数字)
program foo
implicit none
integer, parameter :: dp = kind(0d0)
real(kind=dp), parameter :: x = 9.3
print*, precision(x), x
end program
输出为
15 9.3000001907348633
因此,x
具有预期的 15 位有效数字,但打印的值从第 8 位开始就错误了。原因是浮点字面常量隐式具有默认的实数种类,该种类通常是 IEEE754 单精度浮点数(约 7 位有效数字)。实数 \(9.3\) 没有精确的浮点数表示,因此首先将其近似为单精度直至第 7 位,然后在分配给 x
之前将其转换为双精度。但是,先前丢失的数字显然不会恢复。
解决方案是显式指定常量的种类
real(kind=dp), parameter :: x = 9.3_dp
现在输出在第 15 位是正确的
15 9.3000000000000007
浮点字面常量(再次)#
现在假设您需要一个浮点常量为 1/3(三分之一)。您可以编写
program foo
implicit none
integer, parameter :: dp = kind(0d0)
real(dp), parameter :: onethird = 1_dp / 3_dp
print*, onethird
end program
然后输出为(!)
0.0000000000000000
原因是 1_dp
和 3_dp
是整数字面常量,尽管 _dp
后缀应该表示浮点种类。因此,除法是整数除法,结果为 0。这里的陷阱是标准允许编译器对 REAL
和 INTEGER
类型使用相同的种类值。例如,使用 gfortran,在大多数平台上,值 \(8\) 既是双精度种类又是 64 位整数种类,因此 1_dp
是一个完全有效的整数常量。相比之下,NAG 编译器默认使用唯一的种类值,因此在上面的示例中,1_dp
会产生编译错误。
表示浮点常量的正确方法是始终包含点
real(dp), parameter :: onethird = 1.0_dp / 3.0_dp
然后输出为
0.33333333333333331
打印中的前导空格#
program foo
implicit none
print*, "Hello world!"
end program
输出
% gfortran hello.f90 && ./a.out
Hello world!
请注意额外的前导空格,它在源代码的字符串中不存在。从历史上看,第一个字符包含早期打印机的回车控制代码,并且本身不会打印。空格“ ”指示打印机在打印内容之前执行 CR+LF 序列,并由 Fortran print*
语句自动添加。一些编译器仍然这样做,尽管现代输出设备既不拦截也不使用控制字符,因此该字符被“打印”。如果此前导空格存在问题(很少出现),则可以使用显式格式而不是 *
(表示“让编译器决定如何格式化输出”)。
print "(A)", "Hello world!"
在这种情况下,编译器不再添加前导空格
% gfortran hello.f90 && ./a.out
Hello world!
文件名扩展名#
假设我们将上面的“Hello world”程序放在源文件 hello.f
中。大多数编译器都会产生许多编译错误
% gfortran hello.f
hello.f:1:1:
program foo
1
Error: Non-numeric character in statement label at (1)
hello.f:1:1:
implicit none
1
Error: Non-numeric character in statement label at (1)
hello.f:2:1:
print*, "Hello world!"
1
Error: Non-numeric character in statement label at (1)
...[truncated]
原因是 .f
扩展名根据广泛接受的约定“保留”用于遗留的“固定源形式”,该形式是为穿孔卡片系统设计的。特别是,第 1-6 列保留用于标签、延续字符和注释,实际的语句和指令必须位于第 7-72 列。自由源形式消除了固定源形式的所有限制,但由于后者与前者共存,因此大多数编译器采用的约定是默认使用 .f90
扩展名表示自由形式源。请注意,这通常可以通过一些编译器开关更改,并且 Fortran 包管理器 (fpm) 的最新版本默认认为所有源都是自由形式,而不管扩展名如何。
注意:一个常见的误解是,.f90
源文件仅限于 Fortran 90 标准修订版,不能包含在较新修订版(Fortran 95/2003/2008/2018)中引入的功能。这是错误的,并且完全没有关系:选择 .f90
的唯一原因是在 Fortran 90 修订版中引入了自由格式。 .f
和 .f90
源文件都可以包含来自任何标准修订版的功能。