为什么我必须在Fortran中隐式指定函数的双精度返回值



我是Fortran的新手,正在尝试common块。我的代码是简单的

program main
implicit double precision (p)
real * 8 :: x, y
common /yvalue/ y
x = 3d0
y = 3d0
print *, power(x)
end program main
function power(x)
implicit none
real * 8 :: power
real * 8 :: x, y
common /yvalue/ y
power = x ** y
end function power

它是有效的,但如果我注释掉第二行,它隐式地声明以p开头的变量是双精度的,编译器会抱怨以下

Error: Return type mismatch of function ‘power’ at (1) (REAL(4)/REAL(8))

我确实明白,返回值power默认情况下是一个单精度变量,但为什么在函数中声明power为双精度是不够的?为什么在main中写入real * 8 power也不起作用?

当您试图在代码中调用的过程(函数或子例程)位于program的主体之外且不是任何module的一部分时,它被命名为外部函数(或子程序)。

Fortran是一种静态类型语言,因此在编译时必须知道所有变量和函数的类型。因此,如果你想在程序中引用一个外部函数,那么程序必须有一种方法来知道它的返回类型。你有3个()选项,我会列出它们,从最坏的开始:


  1. WORST:依赖于一个隐式类型规则,该规则恰好将外部函数的返回类型与调用方中与其标识符相关联的类型相匹配(就像您在示例中所做的那样)

为什么你不应该那样做 因为这是癌症。它让代码的含义变得模糊,你不知道这个名字指的是什么。在某些情况下,它甚至可能看起来像一个数组变量,而不是一个函数。此外,在这种情况下,编译器不会检查参数的一致性,因此,如果没有打开特定的编译器选项,代码将在运行时失败,或者更糟的是,会给出错误的结果。此外,隐式键入现在很少有用,大多数时候都是自讨苦吃。始终使用implicit none

正如您所指出的,根据隐式类型的默认规则,任何名称以p开头的变量都将是默认的real类型(在编译器中,它是real(4))。当您将函数结果声明为real*8,编译器将其解释为real(8)时(请参阅最后一条注释),就会出现错误。


  1. BAD:在调用方的规范区域中声明函数的名称和类型

这样做就像声明一个变量一样,如下所示:

program main
implicit none
real*8 :: x, y, power

顺便说一下,属性external可以应用于像您这样的外部过程。它不仅可以为过程提供一些属性(可以作为实际参数传递,消除内部过程的歧义),还可以使标识符的来源更加清晰。

program main
implicit none
real*8 :: x, y, power
external :: power

为什么你不应该那样做编译器也没有参数检查。这严重限制了您与外部函数通信的选项:参数不能采用形状、假定秩、多态、参数化、共数组,也不能在被叫方声明为allocatableoptionalpointertargetasynchronousvolatilevalue;返回类型不能是数组、指针或可分配的;函数不能作为参数传递,为elemental,如果为pure,则不能在此类上下文中使用。这一切的原因是缺少一个显式接口


  1. ACCEPTABLE:为调用方中的外部函数指定一个interface

像这样:

program main
implicit none
interface
real*8 function power(y)
real*8 :: y
end function
end interface

这样,编译器就可以知道声明的所有细节,而我提到的所有限制都不适用。完全自由,代码清晰!

为什么你不应该那样做因为有更好的方法,那就是使用modules!好吧,在不能使用模块的情况下这样做是完全可以的,例如,在使用已经存在的大型旧代码时。缺点是,在两个不同的地方有几乎相同的代码,它们必须始终匹配。


奖金:更好:使用模块。

program main
use :: aux_module
implicit none
real*8 :: x, y
common /yvalue/ y
x = 3d0
y = 3d0
print *, power(x)
end
module aux_module
implicit none
contains
function power(x)
real*8 :: power
real*8 :: x, y
common /yvalue/ y
power = x ** y
end
end

为什么你一定要这么做因为有了模块,接口是自动和隐式可用的(更少的代码重复,没有限制);模块可以单独重新编译和更新,而不会破坏代码。此外,您可以在模块的作用域中声明共享变量,并避免使用common声明。更好的代码版本是:

program main
use aux_module
implicit none
real*8 :: x
x = 3d0
y = 3d0
print *, power(x)
end
module aux_module
implicit none
real*8 :: y
contains
function power(x)
real*8 :: power
real*8 :: x
power = x ** y
end
end

甚至可以选择在contains之后将您的功能直接包含到program中。只有当您不打算在其他程序单元中重用此函数时,才建议使用此功能@IanBush的回答涵盖了这个案例。

最后注意:看看这个答案,看看为什么real*8语法是非标准的,应该避免。

如注释中所述,简单地在函数自身的作用域中声明函数,以及在被调用的作用域中将解决您的问题。然而,我也想阻止您使用常见的、隐式的类型和完全非标准的real*8。因此,这里是您的程序在一个更现代的方言的版本

ian@eris:~/work/stackoverflow$ cat power.f90
Program power_program
Implicit None
Integer, Parameter :: wp = Selected_real_kind( 14, 70 )
Real( wp ) :: x, y
x = 3.0_wp
y = 3.0_wp
! Return type and kind of the function power in scope 
! due to the implicit interface
Write( *, '( 3( a, 1x, f0.6, 1x ) )' ) &
'x =', x, 'y = ', y, 'x**y = ', power( x, y )
Contains
Pure Function power( x, y ) Result( r )
Real( wp ) :: r
Real( wp ), Intent( In ) :: x
Real( wp ), Intent( In ) :: y
r = x ** y
End Function power
End Program power_program
ian@eris:~/work/stackoverflow$ gfortran -std=f2003 -Wall -Wextra -O power.f90
ian@eris:~/work/stackoverflow$ ./a.out
x = 3.000000 y =  3.000000 x**y =  27.000000
ian@eris:~/work/stackoverflow$ 

最新更新