【Fortran】二维、多维数组及相应高级特性

目录

    • Fortran中的高级数组及应用
      • (1) 二维数组
        • 1) 声明
        • 2) 存储及初始化
        • 3) 数组操作
      • (2) 多维数组
      • (3) 对数组使用内置函数
      • (4) WHERE结构(逻辑条件)
      • (5) FORALL结构(下标索引和逻辑条件)
      • (6) 静态和动态数组
      • (7) 自动数组(临时数组)
      • (8) 自动数组(临时)和可分配数组(动态)的区别


Fortran中的高级数组及应用


(1) 二维数组

1) 声明

与一维数组声明接近,二维数组声明如:

REAL , DIMENSION(3,6)::array   ! 3行6列实数数组,有效下标分别为1~3和1~6
INTEGER , DIMENSION(0:100,0:20)   ! 101行21列整数数组,有效下标分别为0~100和0~20
CHARACTER(len=6) , DIMENSION(-2:2,10)   ! 5行10列字符数组,有效下标分别为-2~2和0~10

2) 存储及初始化

数组是以列为主顺序进行存储的。

对于MN列的二维数组,共M×N个数据,存储的顺序是N1列、N2列…,按照列来进行内存分配。数据初始化和I/O语句的使用都是基于该存储特性。

二维数据的初始化原理主要有两种:DO循环存储和列顺序内存存储,前者包含后者,相应的具体方法有以下三种:

  1. 赋值语句初始化

    1. 嵌套DO循环
      对于M×N二维数组,如果数据有规律,示意如下:

      INTEGER,DIMENSION(M , N) :: array
      DO i = 1,M    ! 这是以行为主顺序存储
      	DO j = 1,N
      		array(i,j) = j
      	END DO
      END DO
      
      INTEGER,DIMENSION(M , N) :: array
      DO i = 1,N    ! 这是以列为主顺序存储
      	DO j = 1,M
      		array(j,i) = j
      	END DO
      ENDDO
      
    2. RESHAPE函数
      可将一维数组old_array转化成MN列的二维数组,使用方式为:RESHAPE(old_array , [M,N]),如:

      new_array = RESHAPE([ 1 ,1 ,1 ,1 ,2 ,2 ,2 ,2 ,3 ,3 ,3 ,3],[4,3])
      

      需注意的是,按照列顺序进行存储,因此new_array中第一列元素均为1,第二列均为2。

  2. 类型声明初始化
    使用方式与上述相同,不同的是在声明部分初始化,如:
    INTEGER , DIMENSION(4,3)::new_array(4,3) = RESHAPE([ 1 ,1 ,1 ,1 ,2 ,2 ,2 ,2 ,3 ,3 ,3 ,3],[4,3])

  3. READ语句初始化

    1. 通过隐式DO循环来按照行顺序存储(参考data1.txt数据排序):
    INTEGER::i,j
    INTEGER,DIMENSION(4,3)::array
    OPEN(10,FILE='data1.txt',STATUS='OLD',ACTION='READ')
    READ(10,*) ((array(i,j),j=1,3),i=1,4)        
    
    !data1.txt中的数据
    1 2 3 1 2 3 1 2 3 1 2 3 
    
    1. 通过程序的内存存储方式来读取(列存储):
    INTEGER,DIMENSION(4,3)::array
    OPEN(10,FILE='data2.txt',STATUS='OLD',ACTION='READ')
    READ(10,*) array     
    
    !data2.txt中的数据
    1 1 1 1 2 2 2 2 3 3 3 3
    

综上所述,在初始化或操作二维数组时,尽量使用(显/隐式)DO循环,有助于提高程序的可读性,而不使用内存存储的初始化方法,该方法仅用于理解数组存储的过程。

3) 数组操作

与一维数组操作相似,两个二维数组操作的前提条件是结构一致。同样地,可使用下标进行操作,如:

array(a,:)  ! 表示数组的第a行
array(:,b)  ! 表示数组的第b列
array(a1:a2,:)  ! 表示数组的第a1行至第a2行。列同理
array(a1:a2:a3,:)  ! 表示数组的第a1行至第a2行,但增量为a3行。列同理

(2) 多维数组

Fortran支持下标多达15个的复杂数组。多维数组的声明、初始化和使用方式与二维数组相同。同样是以列顺序作为存储方法。对于多维数组array(a,b,...,n),在使用过程中,a下标总是变化最多(频繁)的,n下标总是变化最少的。


(3) 对数组使用内置函数

Fortran中有三大类内置函数:基本函数、查询函数和变换函数。

  1. 基本函数
    大部分接受标量参数的Fortran内置函数都是基本函数。通用的基本函数有
    ABSSINCOSTANEXPLOGLOG10MODSQRT等。这些基本函数同样也适用于数组参数。

    当对数组使用基本函数,则是对数组中每个元素使用基本函数,例子如:
    声明部分:

    REAL , DIMENSION(4)::array_x = [1.,2.,3.,4]
    REAL , DIMENSION(4)::array_y
    INTEGER::i
    

    执行部分:

    array_y = SIN(array_x)   ! 对整个数组进行基本函数操作
    

    等价于

    DO i = 1,4
    array_y(i) = SIN(x(i))   ! 对整个数组中的每个元素进行函数操作
    END DO
    
  2. 查询函数
    查询研究对象的属性。对于数组来说,则是查询数组的大小、结构、元素下标等基本属性。
    对于二维数组,常用的查询函数如下表:

函数 作用
SHAPE(array) 返回数组array的形状、结构
SIZE(array,dimension) dimension为1表示行,为2表示列,指定则返回数组array的行或列的个数,不指定则返回整个数组的元素总个数
LBOUND(array,dimension) dimension不指定则返回数组array的下标下边界,指定1或2则返回行或列的下标下边界
UBOUND(array,dimension) dimension不指定则返回数组array的下标上边界,指定1或2则返回行或列的下标上边界

表中查询函数的使用方法如下例:

PROGRAM array_test   ! 主程序
IMPLICIT NONE
REAL,DIMENSION(-6:4,0:4)::a=1.0  ! 数组的元素均为1.0
	
WRITE(*,100) SHAPE(a)           ! 几行几列
100 FORMAT('数组a的形状为:',2I3) 
WRITE(*,200) SIZE(a)                ! 数组元素的个数
200 FORMAT('数组a的大小为:',I3)     
WRITE(*,300) LBOUND(a)             ! 下边界
300 FORMAT('数组a的下标最小值分别为:',2I3)
WRITE(*,400) UBOUND(a)            ! 上边界
400 FORMAT('数组a的下标最大值分别为:',2I3)
	
END PROGRAM array_test

相应的结果为:

数组a的结构为: 11  5
数组a的大小为: 55
数组a的下标最小值分别为: -6  0
数组a的下标最大值分别为:  4  4
  1. 变换函数
    变换函数是有一个(或多个)数组值参数或一个数组值结果的函数。变换函数的操作对象是整个数组,输入参数和输出结果常常没有相同的结构。如上述的RESHAPE函数就属于变换函数,在二维数组初始化示例中将一个一维数组转换成二维数组。

(4) WHERE结构(逻辑条件)

由上述可知,对数组中所有元素进行操作的方法有两种:DO循环遍历操作和对数组使用函数(这仅适用于基本函数等部分情况)。

对数组中的部分元素进行操作的一种常规方法是使用DO循环和IF判断结构组合。需要对数组中的所有元素进行逐次判断。

针对Fortran中的数组,存在一种WHERE结构,能够一次完成操作,被称为掩码数组赋值(掩码可理解成为开关,符合要求则开,不符合则关)。

WHERE结构的常用格式为:

[name:] WHERE (mask_expression1)
块1
ELSEWHERE (mask_expression2) [name]
块2
ELSEWHERE [name]
块3
END WHERE [name]

注意事项:

  • mask_expression是一个逻辑数组,与被处理的数组具有同样的结构,如name>0.,表示大于0.的元素;
  • mask_expression1中为真时的数组元素执行块1,mask_expression2为真时的数组元素执行块2,mask_expression1mask_expression2均为假时的数组元素执行块3;
  • WHERE结构中可以有多个ELSEWHERE,对于数组中任何给定元素,至多只能执行语句中的一个块,块其实是一系列赋值语句;
  • WHERE结构是同时运算,更加优于对逐个元素完成运算(如DO循环),尤其对于多维数组,。

类似于IF语句,Fortran提供了一条单行WHERE语句:WHERE (mask_expression) 块


(5) FORALL结构(下标索引和逻辑条件)

WHERE结构是通过逻辑条件来对数组中的元素进行操作,FORALL结构是通过下标所以和逻辑条件来对数组中的元素进行操作。

FORALL结构的常用格式为:

[name:] FORALL (索引1 [,索引2,...,逻辑条件])
赋值1
赋值2
...
赋值n
END FORALL [name] 

注意事项:

  • 索引是指下标的三元组操作(a:b:c)a为起始值,b为结束值,c为增量值;
  • 索引必须至少有一个,逻辑条件是可选条件;
  • 任何一个FORALL结构可以拆成含有IF结构的嵌套DO结构,前者是任意次序地处理数组中的元素,后者是按照DO循环的严格顺序来处理;
  • 赋值语句是有先后执行顺序的,先执行赋值1,再执行赋值2,直至赋值n,换言之,赋值1的结果可能会对赋值2的计算有影响。

例子:

REAL.DIMENSION(10,10)::array = 1.
FORALL(i=1:10)
array(i:i) = 100.    ! 矩阵对角的元素由1.替换成100.
END FORALL

另一个例子:

REAL.DIMENSION(m,n)::array 
FORALL(i=1:m,j=1:n,array(i,j)/=0.)
array(i:j) = 1./ array(i:j)   ! 将矩阵中所有非0的元素转换成倒数
END FORALL

类似于IF语句,Fortran提供了一条单行FORALL语句:FORALL (索引1 [,索引2,...,逻辑条件]) 赋值

(6) 静态和动态数组

静态数组的大小在编译时声明,后续只能靠重新编译程序进行修改。如4行3列数组:

INTEGER,DIMENSION(4,3)::array

动态数组可分配数组)的大小在执行时声明,为了适应实际数组的大小,可允许程序调整内存需求量。

可分配数组的定义过程是:ALLOCATABLE属性声明可分配数组、ALLOCATE语句分配内存、DEALLOCATE语句释放内存。(一般版本、旧版本)

1)ALLOCATABLE属性的使用结构为:

type,ALLOCATABLE,DIMENSION(:[,:,...])::array  

注意事项:

  • ALLOCATABLE属性用于声明数组的大小是动态的,同时需要声明具体的维度;
  • 数组实际大小不清楚;
  • 括号中的代表维度,如REAL,ALLOCATABLE,DIMENSION(:,:)::a代表的是二维动态实数数组a

2)ALLOCATE语句的使用结构有两种形式,分别为:

形式①:ALLOCATE(array([i1:]i2,[j1:]j2,...),...,STAT=status,ERRMSG=err_msg)

形式②:ALLOCATE(array,SOURCE=source_array,STAT=status,ERRMSG=err_msg)

注意事项:

  • ALLOCATE语句为已声明的动态数组分配内存空间,并指定大小;
  • 形式①需要指定每个数组的每个维度宽度,形式②是将source_array的结构赋值到array中(单个);
  • STATERRMSG子句是可选的,但应尽量有,一方面可以检查分配状态,另一方面在出现分配错误时可友好中止程序,不至于莫名其妙的中断。当内存分配成功时,STAT的值为0,ERRMSG的值不会变化,当内存分配失败时,STAT的值为非0,ERRMSG的值包含错误的信息;
  • 例子:形式①如ALLOCATE(array(-5:5),STAT=status,ERRMSG=err_msg),形式②如ALLOCATE(array1,SOURCE=array2,STAT=status,ERRMSG=err_msg)
  • 在分配内存后,可以使用逻辑型内置函数ALLOCATED()检测数组的分配状态,分配成功会返回.TRUE.
  • 在过程(如子例程)中的动态数组,如果使用SAVE(语句或属性),则在该过程的第一次调用中利用ALLOCATE语句分配一次内存,随后在连续调用中的动态数组内容会被保持不变(除非进行重新计算赋值);如果没有使用SAVE,则过程返回至调用程序中时,数组内容被自动释放掉。

3)DEALLOCATE语句的使用结构为:

DEALLOCATE(array1,...,STAT=status,ERRMSG=err_msg)

注意事项:

  • 用于释放先前声明动态数组的内存,使得内存可以再次使用;
  • STATERRMSG的意义和ALLOCATE语句的定义相一致。

在2003及更高版本中,允许通过简单地赋值数据来自动分配和释放动态数组。

相应的语句为:

type,DIMENSION(:[,:,...]),ALLOCATABLE::array  

注意事项:

  • 不再需要ALLOCATEDEALLOCATE语句;
  • 将表达式赋值给动态数组时,如果结构不一致,系统会自动释放内存并重新分配一个正确的结构,如果结构一致,则不用重新分配便可重新使用;
  • 动态数组和将要赋值给它的表达式需要有相同的维度,否则会编译出错;
  • 在子例程或者函数中没有声明SAVE属性的动态数组,内存会在子例程或函数退出时自动释放,不需要执行DEALLOCATE语句进行释放。

(7) 自动数组(临时数组)

Fortran提供了在过程执行时自动创建临时数组,在过程执行结束返回至调用程序后自动释放掉该数组。

自动数组定义:①局部数组变量(非形参);②显式结构(有下标取值,但不固定,由形参传递或由模块的数据指定)。

示例:

SUBROUTINE sub1(....,m,n)   !  形参控制局部数组变量的下标
...
REAL,DIMENSION(m,n)::a     ! 局部变量a数组
a = 1.                     ! 在执行部分初始化
...

注意事项:

  • 不在声明部分初始化,但可以在执行部分初始化,如上例子;
  • 不能指定SAVE属性,因为只是临时性的;
  • 在过程A中调用另一个过程B,过程A中的自动数组可以作为调用参数传递到过程B中;
  • 当过程执行到RETURNEND语句时,自动数组会释放掉。

(8) 自动数组(临时)和可分配数组(动态)的区别

  1. 自动数组在调用过程时会自动地分配,可分配数组必须使用一定的语句进行分配和释放内存空间;
  2. 可分配数组比自动数组更加通用。前者可以用于主程序中,而后者不可以;
  3. 可分配数组比自动数组更加灵活。前者在计算过程中可以自动改变大小,在单个过程中单个可分配数组即可满足多种需要不同结构的数组的需求;后者在过程执行时即分配好固定的大小,除非重新调用过程才能改变大小。

总的来说,自动数据用于在过程中建立局部临时数组(具有过渡性);可分配数组可在主程序和过程中创建并销毁,同时能够在同一次执行过程中改变结构大小。

你可能感兴趣的:(fortran)