接上一篇:认识FORTRAN:从入门到放弃(1)
内容这么多不一定全部用的到,有一个大概印象就行,到时候遇到问题了可以再查询。

Fortran过程控制

1.Fortran的有两种方式实现调用外部过程,分别是子程序(subroutine)和函数子程序(function subprogram)。

子程序通过 CALL 语句调用,并且可以通过调用参数来返回多个结果。函数子程序则通过在表达式中引入函数名来进行调用,它的结果是单个数值,该值用来为表达式赋值,就像数学中的函数那样。

子程序

2.子程序的声明格式如下:

SUBROUTINE subtine_name(argument_list)
  ...
  (Declaration section)
  ...
  (Execution section)
  ...
  RETURN
END SUBROUTINE subtine_name

其中 argument_list 中不能附带参数类型,但在子程序内容的声明部分必须要为相应的参数声明类型
子程序就相当于一个独立的程序,只是可以带参数,所以在声明部分的前面同样需要单独加上IMPLICIT NONE

3.子程序的调用格式如下:

CALL subroutine_name(argument_list)

4.子程序的参数传递方式为传址传递,相当于C语言中传递指针,所以子程序会直接修改原变量的值,如果想在子程序内部不对传递进来的参数进行修改,可以在声明部分使用INTENT属性声明,相当于在子程序内容将相应参数修改为 const 类型,eg:

!加法子程序,并改变传递进去的某个变量的值
SUBROUTINE add(a, b, c)
  IMPLICIT NONE
  REAL :: a
  REAL, INTENT(IN) :: b !添加IN发生后b将不能作为左值,且不能被修改
  REAL, INTENT(OUT) :: c !添加OUT属性后,c将可以作为左值,且可以被修改
  c = a + b
  a = 5.0 !调用后原程序对应的变量值将变成5.0,但不能以同样的方式修改b,否则会编译报错
  RETURN
END SUBROUTINE add

调用时参数类型必须一致

5.INTENT的所有属性:

  • INTENT(IN) 形参仅用于向子程序传递输入数据
  • INTENT(OUT) 形参仅用于将结果返回给调用程序
  • INTENT(INOUT)/INTENT(IN OUT) 形参即用来向子程序输入数据,也用来将结果返回给调用程序

6.与C中一样,为子程序传递数组时,需要传递数组的大小

7.传递字符变量给子程序时可以使用*声明字符变量的长度,当在子程序内容需要使用到字符串的长度时可以使用LEN()函数来获取字符串长度,声明方式如下:

SUBROUTINE sample (string)
CHARACTER ( len=* ), INTENT(IN)  :: string

模块

1.Fortran可以使用模块来在不同的程序之间共享数据,模块相当于C中的头文件,它可以用来共享数据,同样可以用于共享子程序,声明方式是使用 MODULE,引用方法是使用 USE module_name。下面例子说明使用其共享数据。eg:

MODULE shared_data
IMPLICIT NONE
SAVE
INTEGER, PARAMETER :: num_vals = 5 
REAL, DIMENSION(num_vals) :: values
END MODULE shared_data

!使用模块
PROGRAM test_module
USE shared_data
IMPLICIT NONE
values = 2
CALL sub
END PROGRAM test_module

!SUBROUTINE
SUBROUTINE sub
USE shared_data
IMPLICIT NONE
WRITE (*,*) values
END SUBROUTINE sub

SAVE 语句能够保证在模块中声明的数据在不同的过程间的引用中被保护。sub子程序中的 values 将全部为2,因为该变量是与主程序共享的

2.模块中需要定义子程序和函数时必须使用关键字 CONTAINS,这些子程序和过程被称作模块过程。eg:

MODULE my_subs
IMPLICIT NONE
(Declare shared data here)
CONTAINS
  SUBROUTINE sub (a, b, c, k, error)
  IMPLICIT NONE
  REAL, DIMENSION(3), INTENT(IN) :: a
  REAL, INTENT(IN) :: b, c
  REAL, INTENT(OUT) :: x
  LOGICAL, INTENT(OUT) :: error
  ...
  END SUBROUTINE sub
END MODULE my_subs

函数

1.Fortran函数是这样一个过程:它的结果只能是单个数值、逻辑值、字符串或数组之一。有两种不同类型的函数:内部函数(intrinsic function)和用户自定义的函数(user-defined function)或函数子程序(function subprograms)。其中内部函数即是指Fortran自带的 SIN(X)LOG(X) 等。

2.自定义Fortran函数的通用格式如下:

[TYPE] FUNCTION name (argument_list)
  ...
  (Declaration section must declare type of name)
  ...
  (Execution section)
  ...
  name = expr
  RETURN
END FUNCTION [name]

其中,TYPE 表示函数的返回值类型,使用 IMPLICIT NONE 语句时,必须要指名函数的返回类型,如果未使用 IMPLICIT NONE 语句,那么函数的返回类型将默认使用Fortran的内置规则。RETURN 语句只是表示结束本函数,将执行权限归还给调用函数,所以 RETURN 并不是必须有。函数的返回值是通过给函数名赋值而实现,所以函数中必须至少有一次让函数名出现在赋值语句的左侧来指定函数的返回值。

3.函数的声明可以采用以下两种等价格式之一:

INTEGER FUNCTION my_function(i, j)

FUNCTION my_function(i,j)
INTEGER :: my_function

4.函数在被调用时,也必须在调用函数前面对其进行类型声明,声明方式如下:

TYPE :: function_name

5.使用函数计算两个实数之和的例子:

PROGRAM test_func
    IMPLICIT NONE
    REAL :: func
    REAL :: i = 2, j = 5
    WRITE(*,*) func(i,j)
    PAUSE
END PROGRAM test_func

REAL FUNCTION func(a,b)
    IMPLICIT NONE
    REAL, INTENT(IN) :: a, b
    func = a + b
END FUNCTION func

6.函数的参数传递过程也是地址传递过程。

7.过程名相当于也是个指针,包含子程序和函数。所以可以将过程作为参数传递给其他过程,类似C中的函数指针。

8.必须在被调用函数(或子程序)和主程序中使用 EXTERNEL关键字来声明用户自定义函数(或过程)才可以当作调用参数传递。EXTERNEL 使用方法与其他属性一样,但也有一个自己的专有格式,如下:

REAL, EXTERNEL :: func_1, func_2
!专有格式
EXTERNEL func_1, func_2

多维数组

1.二维数组的声明方式举例:

!声明一个由3行6列构成的实数数组,总共有18个元素。第一个下标从1~3,第二个下标是1~6
REAL, DIMENSION(3,6) :: sum 

!声明一个101行21列的整数数组,总共2121个元素,第一个下标有效值是0~100,第二个是0~20
INTEGER, DIMENSION(0:100, 0:20) :: hist

!声明一个7行10列的数组,总共70个元素。数组的类型是字符型,每个数组元素包含6个字符。第一个下标从-3到3,第二个1到10
CHARACTER (len=6), DIMENSION(-3:3, 10) :: counts

2.Fortran中的数组存储方式是列优先,与CUDA的网络线程分配是类似的,而C/C++则是以行优先。也就是说Fortran中二给数组的内存在分配时是先为第一列分配内存,然后是第二列…

3.二维数组初始化方式:赋值语句、类型声明语句或是Fortran的 READ 语句。

4.赋值语句初始化二维数组方式:

INTEGER, DIMENSION(4,3) :: istat
DO i=1,4
  DO j=1,3
    istat(i,j) = j
  END DO
END DO
!等价于下面使用数组构造器生成赋值的数组
istat = RESHAPE((/1,1,1,1,2,2,2,2,3,3,3,3/), (/4,3/))

内置的 RESHAPE 函数的使用格式为output = RESHAPE(array1, array2),它会改变一个数组的结构。其中 array1 包含了要被改变结构的数据,array2 是一个描述新结构的一维数组。array2 中元素的个数是输出数组的维数,其元素值是每个维度的宽度,且 array1 中元素个数必须与 array2 所描述的元素个数相同。
由于数组生成器只能产生一维数组,所以需要使用这种方式。上例中就是将一个一维数据,改变成一个4行3列的二维数组,注意数组存储是列优先。

5.声明时直接初始化数组时初始值必须按列序优先排列,即首先是第一列的4个元素,然后第二列。eg:

INTEGER, DIMENSION(4,3) :: istat(4,3) = RESHAPE((/1,1,1,1,2,2,2,2,3,3,3,3/), (/4,3/))

6.用 READ 语句初始化二维数组。如果在一条 READ 语句的参数列表中出现了没有下标的数组名,那么程序将会按照数组元素在计算机内存中的逻辑顺序为数组的所有元素赋值。eg:

!如果文件INITIAL.DAT中含有数据1 1 1 1 2 2 2 2 3 3 3 3
INTEGER, DIMENSION(4,3) :: istat
OPEN(7, FILE='INITIAL.DAT', STATUS='OLD',ACTION='READ')
READ(7,*) istat

!等价于使用嵌套的隐式DO循环实现的例子
!假设INITIAL.DAT中含有数据1 2 3 1 2 3 1 2 3 1 2 3 
INTEGER :: i,j
INTEGER, DIMENSION(4,3) :: istat
OPEN(7, FILE='INITIAL.DAT', STATUS='OLD',ACTION='READ')
READ(7,*) ((istat(i,j), j=1,3), i=1,4)

7.二维数组也可以使用下标三元组或下标向量的方式使用部分数组元素。例如 a(:,1) 表示选用的是数组的第一列,a(1,:) 选用数组的第一行,a(1:3, 1:3:5)选用数组的第一到第三行,和第一、三、五列。

8.Fortran最多可支持到7维数组。使用方式与二维一样,分配方式是按列优先。

9.很多函数都支持对整个数组操作,即将一个数组名传递给函数时,相当于对数组中的每一个元素都执行该函数。

10.加掩码的数组赋值:where结构使用方法如下:

[name:] WHERE (mask_expr1)
  Array Assignment Statement(s) !Block 1
ELSEWHERE (mask_expr2) [name]
  Array Assignment Statement(s) !Block 2
ELSEWHERE
  Array Assignment Statement(s) !Block 3
END WHERE [name]

这里的每个 mask_expr 是一个逻辑数组,它和数组执行语句中处理的数组具有同样的结构。该结构使得 Block1中的操作用于 mask_expr1TRUE 的所有数组元素上。当 mask_expr1FALSEmask_expr2TRUE 时,Block2 的操作将应用于所有数组元素上,同理操作 Block3。Fortran90中不允许有ELSEWHERE 子句。例如:

!对数组value中值大于0的元素求对数
WHERE(value > 0.)
  logval = LOG(value)
ELSEWHERE
  logval = -99999.
END WHERE

11.也可以使用单行WHERE语句:

WHERE (mask_expr) Array Assignment Statement

12.Fortran95/2003中 FORALL 的用法

!求nxm数组work中所有元素的倒数  
FORALL (i=1:n, j=1:m, work(i,j)/=0.)
  work(i,j) = 1./work(i,j)
END FORALL

FORALL 语句中的每个索引都是通过下标三元组的形式的来指定的:subscript_1 : subscript_2 : stride

动态数组

1.动态数组需要在定义是使用 ALLOCATABLE 属性来说明,然后使用 ALLOCATE 语句来实际分配内存,使用 DEALLOCATE 语句来释放分配的内存。例如:

!声明一个100x11的数组,其中第二维下标从0~10
REAL, ALLOCATABLE, DIMENSION(:,:) :: arr
ALLOCATE(arr(100,0:10), STAT=status)

其中STAT=子句是可选的,用于检查返回值状态,如果分配成功,状态为0,如果失败则返回一个基于编译器的正数。

2.可以使用内置函数 ALLOCATED( ) 在使用前测试数据是否已经成功分配空间,在没有分配空间之前,对其进行的任何操作都是非法的。用法:

REAL, ALLOCATABLE, DIMENSION(:)  :: input_data
...
IF(ALLOCATED(input_data)) THEN
  READ(8,*) input_data
ELSE
  WRITE(*,*) 'Warning:Array not allocated!'
END IF

3.DEALLOCATE 用法:DEALLOCATE(list of arrays to deallocate, STAT=status),例如:

DEALLOCATE(arr1, STAT=status)

派生数据类型-结构体

1.Fortran中的派生数据类型相当于C/C++中的结构体变量,注意只是相当于,因为在其派生数据类型中,只能由基本的内置数据类型或者已定义的用户定义数据类型,但不能包含有函数或子程序,只能用于存放数据。

派生数据类型使用关键字TYPE来定义,定义方式为:

TYPE [::] type_name
  component definitions
  ...
END TYPE [type_name]

2.派生数据类型的类型成员通过百分号%来引用,其相当于C/C++中的间接引用运算符->,例如:

!定义派生数据类型person
TYPE :: person
  CHARACTER(len=14) :: first_name
  CHARACTER :: middle_initial
  CHARACTER(len=14) :: last_name
  INTEGER :: age
END TYPE person
!声明类型为person的变量john
TYPE (person) :: john
!将john的age元素设置为28
John%age = 28

长风破浪会有时,直挂云帆济沧海。