说明:子程序可看做无返回值的函数,为了方便叙述,如未特别说明,文中将函数(function)和子程序(subroutine)统称为函数。
函数的接口信息用于告诉编译器应该如何正确调用该函数,它包括参数和返回值的数量、类型等信息。因此每个函数都必须具有相应的接口信息,缺省情况具有隐式声明,而使用interface则可显式声明函数的接口信息。
Interface 的主要功能:
1、明确外部函数(external procedure) 或虚函数(dummy procedure)的接口信息,包括:输入输出参数的类型和属性、返回值类型、函数属性;
2、定义通用过程名,即函数重载(overload);
3、操作符(+,-,*,/,et al)和赋值符(=)重载。
下面将分别对以上三种功能进行说明。
1、接口界面块
内部函数(contains)、模块(module)中的函数,以及Fortran标准函数 (如:sind、abs等) 均自动包含显式接口,不需要也不能再次声明接口信息,因此上述情况不在讨论之中。我们建议将外部函数封装在module中使用。
外部函数缺省具有隐式接口,对一些常规函数,用户不必显示声明其接口信息,编译器也能正确识别。但当外部函数的形参具有ALLOCATABLE, ASYNCHRONOUS, OPTIONAL, POINTER, TARGET, VALUE, VOLATILE属性时,必须显示声明接口信息。下列情况之一也必须使用接口界面块:
● 外部函数返回值为指针、数组或可变长度字符串;
● 形参为数组片段;
● 参数个数不确定或包含可选参数;
● 改变参数传递顺序;
● 子程序中扩展了赋值号的使用范围。
接口界面块的使用较为简单,在接口界面块(interface-end interface)之间写入函数形参类型说明语句,即去掉所有可执行语句后的剩余部分。下面的例子给出了函数返回数组时以及具有可选参数时使用interface的例子:
01 |
program Fcode_cn |
02 |
integer :: i = 0 , j = 1 , k = 2 , m ( 2 ) |
03 |
|
04 |
interface ! 接口块 |
05 |
subroutine sub 1 ( i , j , k ) |
06 |
integer , optional :: k |
07 |
integer i , j |
08 |
end subroutine |
09 |
|
10 |
function func 1 ( j , k ) |
11 |
integer j , k |
12 |
integer func 1 ( 2 ) |
13 |
end function |
14 |
end interface |
15 |
|
16 |
m = func 1 ( j , k ) |
17 |
print * , m ! m=3,-1 |
18 |
|
19 |
call sub 1 ( i , j , k ) |
20 |
print * , i ! i=3 |
21 |
|
22 |
call sub 1 ( i , j ) |
23 |
print * , i ! i=1 |
24 |
|
25 |
pause |
26 |
end |
27 |
|
28 |
function func 1 ( j , k ) |
29 |
integer j , k |
30 |
integer func 1 ( 2 ) |
31 |
func 1 ( 1 ) = j + k |
32 |
func 1 ( 2 ) = j - k |
33 |
end function |
34 |
|
35 |
subroutine sub 1 ( i , j , k ) |
36 |
integer , optional :: k |
37 |
integer i , j |
38 |
if ( present ( k ) ) then |
39 |
i = j + k |
40 |
else |
41 |
i = j |
42 |
end if |
43 |
end subroutine |
2、函数重载
某些情况下,我们需要对不同类型或不同数量的参数做相似或相同的操作,由于参数类型、数量不同,我们需要编写不同的函数来处理。比如求绝对值,如果参数是4字节整数,我们需要调用iabs函数;如果参数是4字节或8字节实数,我们需要分别调用abs或dabs函数。
由于需要记住多个功能相同或相近的函数,增加了我们使用这些函数的难度,同时也增加了出错的可能性,比如将实数传递给iabs函数。
上述函数的功能相同,只是参数类型或个数不同,那么可否使用同一个函数名来执行它们呢?当然可以,这就是函数重载。函数重载允许通过调用通用过程名来执行特定函数。当用户调用通用过程名时,编译器首先检查传入参数的类型和数量,再调用与之匹配(类型和数量相同)的特定函数来执行具体任务。
例如我们建立通用过程名abs来求绝对值,用户在任何情况都只需调用abs,编译器会自动选用合适的特定函数执行对应操作:当传入参数是4字节整数,就调用iabs函数;如果是8字节实数,则调用dabs函数。
下面用求绝对值的例子说明函数重载功能。为与Fortran内在函数abs区别开来,我们在函数名后添加“_f”。
01 |
module abs_module |
02 |
implicit none |
03 |
interface abs_f |
04 |
module procedure abs_f , dabs_f , iabs_f |
05 |
end interface |
06 |
contains |
07 |
function abs_f ( x ) |
08 |
real ( 4 ) abs_f , x |
09 |
if ( x < 0.0 _ 4 ) then |
10 |
abs_f = - x |
11 |
else |
12 |
abs_f = x |
13 |
end if |
14 |
end function abs_f |
15 |
|
16 |
function dabs_f ( x ) |
17 |
real ( 8 ) dabs_f , x |
18 |
if ( x < 0.0 _ 8 ) then |
19 |
dabs_f = - x |
20 |
else |
21 |
dabs_f = x |
22 |
end if |
23 |
end function dabs_f |
24 |
|
25 |
function iabs_f ( x ) |
26 |
integer ( 4 ) iabs_f , x |
27 |
if ( x < 0 ) then |
28 |
iabs_f = - x |
29 |
else |
30 |
iabs_f = x |
31 |
end if |
32 |
end function iabs_f |
33 |
end module abs_module |
34 |
|
35 |
program Fcode_cn |
36 |
use abs_module |
37 |
real ( 4 ) :: x = -2.0 _ 4 |
38 |
real ( 8 ) :: y = -3.0 _ 8 |
39 |
integer ( 4 ) :: z = -4 |
40 |
|
41 |
print * , abs_f ( x ) |
42 |
print * , dabs_f ( y ) , abs_f ( y ) |
43 |
print * , iabs_f ( z ) , abs_f ( z ) |
44 |
|
45 |
pause |
46 |
end |
执行结果:
2.000000
3.00000000000000 3.00000000000000
4 4
代码分析:我们使用interface创建了一个函数重载,其通用过程名(interface之后的标识符)为abs_f,包含三个特定过程abs_f, dabs_f, iabs_f。需要注意,通用过程名可以与其中一个特定过程名一致,也可以不一致;但特定过程名之间必须不同。每个特定过程的形参类型和数量不能完全一致。
interface abs_f
module procedure abs_f, dabs_f, iabs_f
end interface
在程序执行过程中,调用通用过程名时,编译器首先检查实参的类型和数量,并与特定过程的接口信息相匹配。如果匹配成功,则调用相应特定函数(故dabs_f(y)和abs_f(y)的结果是一致的);否则编译器会报错。
比如调用 abs_f(12_8), 由于我们没有给出针对8字节整数求绝对值的特定函数,编译器则会报错。
3、操作符和赋值符(=)重载
常规运算中,我们经常用到算术操作符(+,-,*,/,**)、关系操作符(<,<=,>,>=,==,/=)以及赋值符(=),同时也会发现其使用具有一定的局限性:只能对特定的数据类型进行运算,不能直接用于派生数据类型。
比如两个时间相减,我们不能直接使用“-”进行操作,而需要编写特定的函数。针对这一问题,Fortran90引入了操作符重载功能。我们先看下面一段代码:
01 |
module time_class |
02 |
implicit none |
03 |
type time !定义一个时间类结构体 |
04 |
integer ( 1 ) hour , minute , second |
05 |
end type |
06 |
interface operator ( - ) !重载操作符- |
07 |
module procedure timeMinus |
08 |
end interface |
09 |
interface assignment ( = ) !重载赋值符= |
10 |
module procedure assign_time |
11 |
end interface |
12 |
interface operator ( .minus. ) !自定义操作符.minus. |
13 |
module procedure timeMinus |
14 |
end interface |
15 |
contains |
16 |
! 两个时间相减 |
17 |
function timeMinus ( time 1 , time 2 ) |
18 |
type ( time ) , intent ( in ) :: time 1 , time 2 |
19 |
type ( time ) timeminus |
20 |
integer n |
21 |
n = ( time 1 .hour - time 2 .hour ) * 3600 + ( time 1 .minute - time 2 .minute ) * 60 + ( time 1 . second - time 2 . second ) |
22 |
if ( n < 0 ) n = n +3600 * 24 |
23 |
timeminus. second = mod ( n , 60 ) |
24 |
n = n / 60 |
25 |
timeminus.minute = mod ( n , 60 ) |
26 |
timeminus.hour = n / 60 |
27 |
end function |
28 |
! 如果time类数据正确,将其赋值给res;否则输出错误提示 |
29 |
subroutine assign_time ( res , time 1 ) |
30 |
type ( time ) , intent ( in ) :: time 1 |
31 |
type ( time ) , intent ( out ) :: res |
32 |
if ( time 1 .hour >= 0 .and. time 1 .hour <= 23 ) then |
33 |
if ( time 1 .minute >= 0 .and. time 1 .minute <= 59 ) then |
34 |
if ( time 1 . second >= 0 .and. time 1 . second <= 59 ) then |
35 |
res.hour = time 1 .hour |
36 |
res.minute = time 1 .minute |
37 |
res. second = time 1 . second |
38 |
return |
39 |
end if |
40 |
end if |
41 |
end if |
42 |
write ( * , * ) "time类数据错误." |
43 |
end subroutine |
44 |
end module |
45 |
|
46 |
program fcode_cn |
47 |
use time_class |
48 |