第五章 数组
数组类型与定义
5.1.1 定义数组
F90中变量的概念与F77的不同,它包含了两种变量,一种是标量,一种是数组。数组是科学和工程计算问题中常见的向量和矩阵的反映和概括。数组在FORTRAN程序中有着重要的意义,在批量大的情况下,如果不利用数组就失去了计算机的优越性。
a) 数组的描述
数组是类型相同的一组标量数据的有序集合,即要求这些数据都必须类型相同,并按某种确定方式排列。向量是一维数组,矩阵可看成是二维数组。类型可以是整型、实型、双精度型、逻辑型等任何一种。组成数组的每一个元素称为数组元素。
数组的维数称为秩(rank),F90中规定数组最多可以有7维。在某一维中元素的个数称为该维的长度(extent)。数组中所有元素的个数称为数组的大小(size),它等于各维的长度的乘积,数组的大小可以为0。数组的形状(shape)取决于秩和每一维的长度。
例:REAL A(10,3,2)的秩为3,大小为10×3×2=60,形状为10乘3乘2,或表示成(10,3,2)。
数组的形状可用内在函数SHAPE得到,如SHAPE(A)的结果是一维数组,其元素取值为10,3,2。
每一维的大小都由一个下界和一个上界来指定,之间以冒号分开,即下界:上界。维界表达式是整型的数学表达式,维界值可以是正、负或零,但维上界必须大于维下界的值。维长=上界-下界+1。声明数组时下界可以省略,此时维下界为默认值1。例如:REAL B(0:9,-1:1,4:5,9)。
数组可以用以下几种声明语句定义,F77中为类型说明语句、DIMENSION语句和COMMON语句,F90中还可以用类型属性说明、POINTER语句、ALLOCATABLE语句。例如下面都是合法的数组声明:
REAL A(10,2,3) ! 类型说明
DIMENSION A(10,2,3) ! DIMENSI0N语句
ALLOCATABLE B(:,:) ! ALLOCATABLE语句
POINTER C(:,:,:) ! POINTER语句
REAL,DIMENSION(2,5):: D ! 类型说明中的DIMENSION属性
REAL,ALLOCATABLE:: E(:,:,:) ! 类型说明中的ALLOCATABLE属性
REAL,POINTER:: F(:,:) ! 类型说明中的POINTER属性
无论何种情况,数组的秩总是要指定的。数组的大小和形状是否需要声明视数组的形式而定。当COMMON语句指定了数组的秩和大小后,只能定义数组的类型而不能再次重复给定维界。
b) 数组元素
在程序执行部分中,数组的三种成份可供运算或输入输出:
1、数组名 -> 代表数组中所有元素;
2、数组元素 -> 代表数组中某一个元素;
3、数组片段 -> 代表数组中若干个元素,它们可以相连或分离。
数组中单独的标量称为元素。标量的秩为0,而数组的秩至少是1。如果没有下标则指整个数组。对每一维指定确切的一个下标则定义了一个数组元素,还可以通过下标可以引用数组的一部分元素。例如A指整个数组,A(1)指数组A的第1个元素,A(3:4)指数组A的第3和第4个元素,A(1:10:2)指的是数组A的第1,3,5,7,9个元素。
数组的下标必须用逗号隔开,下标是整型常量、变量或表达式,可正、可负、也可以为0,但必须在引用的数组的维数之内。引用下标的个数要和声明的数组的维数一致。可以使用函数或数组元素作为下标:
例:REAL A(3,3),B(3,3),C(89),R
B(2,2)=4.5; R=7.O; C(INT(R)*2+1)=2.O !给元素B(2,2)和C(15)赋值
A(1,2)=B(INT(C(15)),INT(SQRT(R))) !元素A(1,2)和元素B(2,2)的值相同
c) 数组片段
数组片段是数组所有元素集合的一个子集。数组片段的元素可以是数组中任意的元素,它们不需连续或遵循某个规则。数组中的所有元素和片段的数据类型和种别都相同。如果指定数组的所有下标则得到的是数组元素(即标量),如果只指定部分下标则结果是部分数组元素的集合,即数组片段,数组片段本身也是数组。
例如,如果定义REAL A(2,3,4),则A(1,2,3)是数组元素,而A(1:2,2,2),A(1,1,4:2:-1),A(1,2:3,(/2,4/))都是数组片段。
数组片段由下标列表确定,下标列表有两种:三元下标和向量下标。
d) 三元下标
三元下标用三个值分别代表数组片段的下界,上界和步长。其一般形式为:[下界]:[上界][:步长]。如果省略下界,缺省值为数组相应维的下界;如果省略上界,缺省值为数组相应维的上界;如果省略步长,缺省值为1。如果下标都省略了则缺省片段为这个维的全长。
例:REAL A(10)
A(1:5:2)=3.0 !把元素A(1),A(3),A(5)置为3.0
A(:5:2)=3.O !把元素A(1),A(3),A(5)置为3.0,因为缺省下界为1
A(2::3)=3.O !把元素A(2),A(5),A(8)置为3.0,因为上界缺省值为10
A(7:9)=3.0 !把元素A(7),A(8),A(9)置为3.0,因为缺省步长为1
A(:)=3.0 !和A=3.0相同,将A的所有元素置为3.0
对于—个多维数组的数组片段,它的每一维都可以用三元下标来声明。如果要在一个语句或过程中引用这个数组片段,则引用下标要和声明时的下标个数一样多。注意,三元下标只能算一个下标。
例:REAL A(5,9)
A(1:4:3,6:8:2)=3.0
此例中数组A是二维数组,数组片段是形状为(2,2)的二维数组。上面括号内第一个三元组表示第一维下标变化范围,第二个三元组表示第二维下标变化范围。其元素选取法是先把第二维下标定在下界值上,而后遍历第一维下标,选中元素为A(1,6),A(4,6);再把第二维下标增一步长,再遍历第一维下标,选中元素为A(1,8),A(4,8),如此重复直至全部选完。这种选取关系相当于把第二维作外层DO循环变量,第一维作内层循环变量的变化。这四个元素按先后次序保持一定的形状,上式等价于:
数组的步长不能是0。当步长为负值时,数组子片段从上界开始递减至下界。例如声明一个数组B(10),则数组片段B(9:2:-2)是由元素B(9),B(7),B(5)和B(3)组成的数组。显然下界不能比上界大,否则产生的数组大小为0。三元下标的值可以不在数组的边界以内,例如对于数组B(10),数组片段B(3:15:6)是由B(3)和B(9)组成的数组。
e) 向量下标
三元下标以上升或下降的顺序指定数组元素,而向量下标可以以任何顺序来指定数组元素。向量下标是一个一维整数数组(即向量),它可以从整个数组中选择片段。
例:REAL A(10),B(5,5)
INTEGER I(4),J(3)
I=(/5,3,8,2/) !定义向量I
J=(/3,1,5/) !定义向量J
A(I)=3.O !设置元素A(5),A(3),A(8),A(2)的值
B(2,J)=3.O !设置元素B(2,3),B(2,1)和B(2,5)的值
例:INTEGER :: a(4)=(/0,1,2,3/),b(3)=(/1,4,3/)
则a(b)与a同类型,与b同形状,取值为(/0,3,2/)。a(b)可以不是数组片段,而是更大的数组。如上面b为(/2,3,2,3,2,3/)时,a(b)为(/1,2,1,2,1,2/)。
例:CHARACTER(1) :: symbol(0:1)=(/'F','M'/)
INTEGER :: bit(100)
若bit的元素列为0001101100111...,则symbol(bit)是用{F,M}字符构成的100字节的字符型数组FFFMMFMMFFMMM...。
向量下标的值应该在定义的边界之内,向量下标可以有多个重复的值,此数组片段称为多对一数组片段。
例:REAL A(3,3),B(4)
INTEGER K(4)
K=(/3,1,1,2/) !K矢量有重复值
A=5.0 !设置A的所有元素
B=A(3,K) !数组片段B由下列元素组成:A(3,3),A(3,1),A(3,1),A(3,2)
因为在B(4)或A(3,K)中有重复的元素A(3,1),所以它是多对一数组片段。一个多对一数组片段不能出现在赋值语句的左端。
5.1.2 数组类型
a) 显式形状(Explicit-shape)数组
这种数组指定了所有特征:固定的秩、每一维的长度和形状。其中下界是可以忽略的。它的一般形式是:([下界:]上界[,[下界:]上界]…)
例:INTEGER M(10,10,10),K(-3:6,4:13,0:9)
维界可以是常数或变量。在过程(函数和子程序)中,数组的上界和下界可以由变量或表达式指定。使用变量或表达式的数组是自动数组和可调数组。
b) 自动(Automatic)数组
自动数组是显形数组的一种,它是过程中的局域变量,自动数组必须在过程中加以声明,并且它的上下界是不定的表达式。在调用过程时,上下界通过变量或表达式求出。过程中其后的变量或表达式值的变化不会对数组的上下界产生影响。
例:SUBROUTINE EXAMPLE(N,R1,R2)
DIMENSION A(N,5),B(10*N)
……
N=IFIX(R1)+IFIX(R2)
此例中的A和B都是自动数组。子程序被调用时,数组A和B的上界通过传入的变量N来确定,而以后N的值的变化对A和B的大小不会有影响。
例:SUBROUTINE SUB1(A,B)
INTEGER A,B,LOWER
COMMON/BOUND/LOWER
……
INTEGER AUTO_ARRAY1(B)
INTEGER AUTO_ARRAY2(LOWER:B)
INTEGER AUTO_ARRAY3(20,B*A/2)
……
END SUBROUTINE
c) 可调(Adjustable)数组
可调数组也是显形数组的一种,它是过程的一个哑元,至少有一个维界不是常数,这个维界当过程被调用时才被确定。其维界表达式中的变量是哑元,或者是通过COMMON语句中传递的常量。注意多维可调数组的维界表达式必须与调用时的维界相符。
例:DIMENSION A1(10,35),A2(3,56)
……
SUM1=THE_SUM(A1,10,35)
SUM2=THE_SUM(A2,3,56)
END
FUNCTION THE_SUM(A,M,N)
DIMENSION A(M,N)
SUMX=0.0
DO J=1,N
DO I=1,M
SUMX=SUMX+A(I,J)
END DO
END DO
THE_SUM=SUMX
END FUNCTION
其中哑元M,N控制着可调数组的大小。下例说明了可调数组的维界X(-4:4,5)在调用过程时被确定后,过程内部对维界参数的赋值不会改变维界值。
例:DIMENSION ARRAY(9,5)
L=9
M=5
CALL SUB(ARRAY,L,M)
END
SUBROUTINE SUB(X,I,J)
DIMENSION X(-I/2:I/2,J)
X(I/2,J)=999
J=1
I=2
END
a) 假定形状(Assumed-shape)数组
这种数组是在过程中的哑元,它从实际传递过来的数组获得形状参数。假定形状数组的秩由冒号的个数决定。它的一般形式是:([下界]:[,[下界]:]...)。如果不指定下界,则默认值为1。上界值=过程调用时实参数组对应维的长度+下界值-1。注意它与可调数组的区别在于,可调数组属于显型数组(必须指定上界)的范围,而假定形状数组的上界是不能指定的。
例:SUBROUTINE ASSUMED(A)
REAL A(:,:,:)
此时数组A的秩为3,但每一维的长度待定。当过程被调用时A将从传递到过程的数组获得形状:
REAL X(4,7,9)
CALL ASSUMED(X)
于是A获得了数组维界(4,7,9),实际数组和假定形状数组的值必须相同。如果上面过程中数组声明了A(3:,0:,-2:),以哑元X(4,7,9)调用过程时,数组A的实际维界是A(3:6,0:6,-2:6)。
应用假定形状数组为哑元的过程时必须有显式的接口INTERFACE语句。
例:INTERFACE
SUBROUTINE SUB(M)
INTEGER M(:,1:,5:)
END SUBROUTINE
END INTERFACE
INTEGER L(20,5:25,10)
CALL SUB(L)
在此例中数组M的维界是(1:20,1:21,5:14)。
b) 假定大小(Assumed-size)数组
这种数组是在过程中的哑元,它从实际传递过来的数组获得数组大小。除了最后一维的上界以外,其它所有特征(秩,长度和维界)都必须指定。声明一个假定大小数组时,最后一个的上界用星号*表示。它的一般形式是:([显型维界,][显型维界,]...[下界:]*)。
例:SUBROUTINE ASSUME(A)
REAL A(2,2,*)
假定大小数组的秩和形状可以和实际传入的数组不同,传入的数组只确定它的大小。实际数组的元素按列传递给假定大小数组,假定大小数组也按列接收。接受的过程中假定大小数组的最后一维的长度会改变来接受所有传递进来的数组元素,于是最终给出数组的大小。如上例子中的ASSUME子程序,如果以数组X为哑元来调用的话,
REAL X(7)
CALL ASSUME(X)
则数组X的元素与数组A的对应顺序是:
X(1):A(1,1,1)
X(2):A(2,1,1)
X(3):A(1,2,1)
X(4):A(2,2,1)
X(5):A(1,1,2)
X(6):A(2,1,2)
X(7):A(1,2,2)
其中数组A的最后一维没有必要成为完整的维,所以数组A始终没有确定的形状。因为假定大小数组没有形状,所以这样的数组不能仅仅通过名称来向其它过程传递。
假定大小数组可以分解成确定的数组片段。如上例中的数组A,可分为三个片段:A(1:2,1:2,1)和A(1:2,1,2)以及A(1,2,2)。
假定大小数组的秩是完全确定的维数+1。上例中数组A的秩为3,尽管A的第三维不是完整的。
c) 延迟形状(Deferred-shape)数组
可分配数组必须以延迟形状的形式来声明。它每一维的长度只有在分配数组才被确定。声明迟形数组时,秩由冒号确定,但长度是未知的。
例:REAL,ALLOCATABLE:: A(:,:,:)
INTEGER,ALLOCATABLE,TARGET:: K(:)
可分配数组可由下列方式声明:使用ALLOCATABLE语句、DIMENSION语句、TARGET语句或在类型声明中使用ALLOCATABLE属性。如果迟形数组以DIMENSION语句或TARGET语句声明,则在其它语句中必须给出ALLOCATABLE属性。
在迟形数组的大小、形状和维界没有确定之前,其任何部分都不能被引用,可分配数组的大小、形状和维界在ALLOCATE语句执行时才被确定。[e_512_01.f90]
例:INTEGER,ALLOCATABLE:: A(:,:)
REAL,ALLOCATABLE,TARGET:: B(:,:),C(:)
ALLOCATE(A(2,3),C(5),B(SIZE(C),12))