6 构造数据

固有数据类型只是描述了问题当中出现的基本数据形式,但在实际的计算当中,计算对象往往并不只是限于那些固有数据类型,而是一些数据结构。

例如:

线性数学问题里面的计算对象很少是单纯的标量,而是诸如行列式,向量这样的由

一系列标量组成的数据结构。

甚至在计算过程中,还需要以这种数据结构的某个特定部分作为计算对象,例如行

列式或向量的单独的行与列,或对角线,部分块等

又例如:

样品的属性值可以是多方面的,例如在测量一个导体样品的交流输运性质时,需要考虑的物理量包括电流,电压,相位等,如果以每个样品的属性值作为数据对象,那么这种数据对象就至少包括3个成员:电流,电压,相位。它们的取值的类型不同,精度要求也可能不同,这3个数值同样构成一个数据结构。

因此,进一步FORTRAN 95需要以固有数据对象为基础,能够构造一定的数据结构,从而能够作到基于数据结构进行运算。

本章讨论的就是FORTRAN 95构造数据结构的几种方式:

    派生数据类型;

    数据结构的子对象;

    数据结构的成员;

    数组;

    指针。

特别的,我们首先讨论FORTRAN 95关于数据对象的一个基本分类:变量与常量。它们是数据在程序当中的2种基本行为方式,是我们理解FORTRAN 95语言范畴的关键。

6.1  数据的2种基本行为—变量与常量

首先我们给出几个抽象的基本概念,在这里只是给出大意而不作更加形式化的讨论,完全只是为了后面行文的方便。读者不用深究它们的精确定义,只需要在后面行文当中遇到这些概念时,知道回到这里稍加体会即可。

    数据对象:

计算过程当中,任何充当计算对象的数据内容,都称为数据对象,是最一般意义上

的数据个体,今后提到数据对象,我们可以理解为一个固有数据类型,一个属于某

固有数据类型的变量,一个常量,一个标量,一个数据结构,一个数据结构的成员

或子对象,表达式求值的结果,或函数引用的执行结果(即函数值)。数据对象有数

据类型(固有的或派生的),可以有数据值。可以是一个数据的集合,集合的元素

数目称为秩,每个数据对象有一个秩。它可以是一个标量或是一个数组。有名数

据对象的类型可以显式或隐式地规定。

    子对象:

子对象是某些有名对象的一部分,可由程序的其他部分引用和独立地定义。包括数据组的部分,字符串的部分(子串)和结构的部分(成员)。作为子对象的来源的数据对象称为该子对象的父对象。子对象只能用子对象描述符来引用。变量的子对象是变量。

    标量:

任何单一的数据对象,无论它是属于固有数据类型还是属于派生数据类型,就称为标量。

    数组:

一个标量的集合,如果其中每一个元素都具有相同的数据类型,相同的种别参数,那么无论它们组织成什么形式,例如行,列,块,甚至更高维度,都把这种集合称为数组。

派生数据类型:

一个标量的集合,如果其中元素的种别参数不同,或者数据类型不同,那么把这个

集合称为结构,或派生数据类型。

从程序运行这么一个动态过程的角度来看,特别是基于下面的基本计算模式:

输入数据输出数据

数据可以有2种基本行为方式:

数据对象在整个程序运行过程当中保持不变;

数据对象在整个程序运行过程当中发生变化。

保持不变的称为常量,发生变化的称为变量。

FORTRAN 95中常量与变量,标量与数组这些概念与早期的FORTRAN版本有微妙的差异。请熟悉FORTRAN77的读者注意。

    常量有两种,一种是字面常量,早期的FORTRAN版本称为常量;另一种是命名常量,早期的FORTRAN版本称为常数符号名,显然新的名称突出表达了该数据作为常量的特点,它与字面常量的最大差别在于有一个名字。这个名字可以在它的定义作用域当中任意引用。它们的共同点在于,它们的值在可执行程序执行期间是不变的。

    字面常量是标量,根据它的字面的句法构成来表明其数据类型,种别参数以及值。在一个可执行程序中,所有具有相同形式的字面常量具有相同的值。命名常量则是一个与具有PARAMETBR属性的名字相结合的常量。它的数据类型,种别参数和取值,都需要预先定义,而一旦定义之后,在其定义的作用域内都不会改变,而且这个名称也必须接受唯一解释的。

    变量的值即使用DATA语句给以初始化也能在程序运行过程当中发生变化。而FORTRAN 95的变量包括了早期FORTRAN标准里面所谓的变量和数组。具有DIMENSION属性的名字在FORTRAN 95标准里面也称为变量,属于数组。

    数组是标量的有序集合,它在FORTRAN 95标准当中有许多新的扩充,将在后面作详细讨论。

在程序运行过程当中,对数据的使用首先必须是定义,然后才能是引用,所谓定义和引用的差别在于:

    对于一个变量,当程序中确定其值时称为定义,而使用其值时称为引用,已经定义了的变量可以重定义或者回复为无定义。

例如,在程序单元中使用DATA语句使得一个变量具有初值,在程序执行过程中又可以对它赋值而得到重定义;如果该变量不具有SAVE属性的话,当该程序单元执行RETURN语句或END语句后,它就回复为无定义状态。

    对于一个常量,它的值可以被引用但不能被重定义。命名常量虽然出现的形式是一个名字,而不是一个具体的值,但它只是常量的一个便于引用的标记。

【例6-1

X = 0

N = X+1

在第一个语句当中,变量X被赋值为常量值0,这个语句的执行与X是否在此之前被定义为其他值无关,总之此时X得到一个定义,此后只要X没有被重定义,它就能用值0被程序引用。

在第二个语句当中,X被引用,并且在执行加1的运算之后,得到的值被赋值给N

在第一个语句当中的X和在第二个语句当中的N都不是被引用,而是被定义,而在第二个语句当中的X属于被引用。

变量(variable)的句法形式(R601)为;

scalar-variable-name

array- variable-name

subobject

其中子对象(subobject)的句法形式(R602)为:

array-element

array-section

structure-component

substring

变量可以是任意的数据类型,在一定的上下文当中,变量也必须取一定的数据类型。

常量父对象的子对象不可能是变量。

6.2  数据的结构----派生数据类型

五个固有数据类型已经能够作到描述,或者近似描述整数,实数,复数,逻辑值,字符串这五类常用数据对象。

考虑一下FORTRAN建立这五种相对独立的数据类型的出发点,是有助于我们进一步理解语言的数据描述这个重要任务的。

首先考虑整数。

似乎整数是一切计算的基本出发点,这里是没有省略的余地的,想像一下,没有了整数,我们还能做什么计算呢?别忘了Kronecker的名言:上帝创造了自然数,其余都是人造的

然后就是实数。

由于FORTRAN本质上只能表示有限位有理数,因此实际上也可以通过整数的除法来得到实型数据的近似表示,比方说用一个整数二元组(3771)来表示37/71这个有理数,相应的也可以用来近似地表示实数,我们可以来衡量一下这样做的利弊:

    好处就是可以不用建立实型数据类型,也就是不用引入小数点,减少了一个数据类型,也就简化了FORTRAN语言的实现;

    但同时坏处就是把麻烦留给了程序作者,在处理涉及实数的问题时,肯定会因为不能直接表达实数而增加算法的复杂性,甚至在语言本身,也有可能不得不增加一些函数之类的语言成分,用来解决由此产生的一些问题。

所以FORTRAN的解决方案就是直接引入实数的有限有理小数表示(实际上一般的程序语言都是这样做的),对于语言本身来说,无非就是增加了对小数点的语义解释,增加了对浮点数值的多种存储模式(FORTRAN使用种别参数来进行分类)的建立等,由此而可以在语言当中自然的使用有限有理小数所表示的实数。

再来分析一下复数。

本质上复数就是一个实数的二元组,而且这个二元组与复数是一一对应的,因此可以很完美的通过运用二元实数组来代替复数,这也是大多数程序语言的处理方式。但是FORTRAN的选择是建立一个独立的复型数据类型,尽管得为这个独立的数据类型定义相应的运算,但是由于FORTRAN主要就是面向科学计算的语言,因此FORTRAN能够直接写出复数,成为FORTRAN的一个优点,极大地方便了程序作者。

由此可见,建立一个数据类型的主要功能就是面向问题,以能够直接描述算法为目的。

那么我们是否有可能从实际问题当中归纳出一些数据类型,然后可以期望这些数据类型就可以满足任何问题的需要呢?

显然是不可能的。

一方面,程序语言必须能够精确描述任何需要用它来解决的问题当中出现的数据;另一方面我们不可能给每一个新问题当中出现的独特的数据结构形式规定一种相应的数据类型,唯一的解决方法,就是在FORTRAN里面约定一种语法机制,可以依据这种语法机制来描述与问题要求相适应的数据类型。反过来只要是依据这个语法机制构造出来的语言结构都能被FORTRAN编译器辨认为一种数据类型,从而知道按照什么存储模式来存储该数据,也知道应该对该种数据施加什么运算。 

这就是FORTRAN90版标准以来设置派生数据类型的出发点。

鉴于固有数据类型被认为是真实世界最基本的数据类型,任何的数据对象,或者就是属于某种固有数据类型,或者就是可以由一系列固有数据描述,因此构造派生数据类型的基本思想就是认为一个派生数据等于一系列成员的集合,这个集合构成一种数据结构,而每一个成员或者仍然属于一个派生数据类型,或者就是属于某种固有数据类型。这样对一个派生数据类型的描述,就归结到对它的成员的描述,另外再加上给出该数据类型的名称,并可选择性地描述其他属性。

这样得到的所谓派生数据类型,和五种固有数据类型不同的地方主要在于:

    每一种固有数据类型都拥有一个特定的名称,用来作为数据声明的关键词;而派生数据类型则需要程序作者自己首先定义一个名称,然后才能在后面作为关键词进行声明。

    每一种固有数据类型都已经定义好了自己的常量书写方式;而派生数据类型则依据其成员的常量形式来确定自己的常量的形式。

    每一种固有数据类型都已经定义好了自己的特定的运算;而派生数据类型则需要程序作者自己预先依据实际问题的需要来定义好相应的运算。

    每一种固有数据类型都已经定义好了自己的取值范围以及相应的多种存储模式;而派生数据类型,则完全依据其成员的取值范围与存储模式,来确定自己的取值范围和存储方式。

    派生数据类型可以在数据类型的定义里,就给出该数据类型的默认初始值,使得今后属于该类型的数据对象都一定取该初始值;而固有数据类型的定义里面不含有定义初始值的部分,要想显式地给出变量初始值,得需要使用数据类型声明语句或者DATA语句。

    固有类型的名称是全局性的,在任何环境总是可访问的,而派生类型只可在其定义的作用域内访问它们。

FORTRAN定义的数组同样是数据对象的集合,不过派生数据类型与数组的差别在于组成数组的各个成员必须是相同的数据类型,而派生数据类型的特点就在于它的成员可以是属于不同的数据类型。派生数据类型的成员可以仍然是某个派生数据类型,但数组的成员就不能是数组。

要想让数组的成员为数组只能通过间接的方式,即取数组的唯一成员为一个派生数据类型,然后该派生数据类型的成员取为数组。

对于这样构造出来的与具体问题密切相关的派生数据类型,由于其所具有的内部结构个性太强,显然我们需要定义一些与这种结构相关的特征属性,可以用来描述这种个性:

    正是由于一个已经定义好的派生数据类型具有完全个性化的内部结构,因此如果一个派生数据类型是定义在一个模块内部的话,自然的问题就是这个派生数据类型的面向模块外部的可访问性如何控制。

在默认情形下,一个在模块内部定义的派生数据类型可以被模块外部的任意程序单元访问,这个默认状态等价于使用PUBLIC语句。

反之,也可以通过使用PRIVATE语句来阻止模块外程序单元的访问。

不过,由于一个模块包含许多不同层次的数据对象,因此PRIVATE的作用对象必须是精确指定的,这里分为以下几种情况:

· 每一个对象都可以单独地通过使用属性PUBLICPRIVATE来指定其可访问性。

· 对于一个派生数据类型,可以在定义类型的语句当中设置可选的PUBLICPRIVATE描述符。

· 使用单独的针对类型名称的PRIVATE语句或在类型定义当中使用PRIVATE描述符,可以把对该类型访问控制在本模块之内。

· 在类型定义的内部使用单独的PRIVATE语句,可以只控制该派生类型的特定的组元的模块可访问性,而不影响整个类型的默认下的可访问性。

    组成一个派生类型的各个成员可以按照一定的顺序进行存储,这是通过在派生类型的定义当中运用SEQUENCE语句得到的。

一个派生类型的定义当中,成员的叙述上的顺序并不表示它们具有相应的存储上的顺序,只有在定义当中设置一个SEQUENCE语句,才能对这些成员的存储进行排序。从而可以进一步对这些成员应用COMMON语句或EQUIVALENCE语句。

    一个派生数据类型的取值范围显然就是它的所有成员的取值范围的组合,使用集合论语言就是各成员值集的乘积。FORTRAN规定了一个语法机制来给派生数据类型构造值的形式,例如用来定义命名常量,这样就可以在类型声明语句或PARAMETER语句当中使用其命名常量。这种语法机制称为派生数据类型的结构构造器。

至此我们可以看到FORTRAN语言对派生数据类型的定义,不是以某种真实世界的自然的数据集合为依据,而是提供一种描述方式,并且在语言当中约定,只有运用这种方式进行描述,就可以成立为一种数据类型。而对于通过这种方式成立的数据类型,同样包含四个方面的基本属性:

●  拥有名称,以备引用;

●  取值在一定的集合上;

●  可以施加的运算;

●  常量的写法。

下面就从这四个方面来说明派生数据类型。

6.2.1  派生数据类型的构造

给出一个派生数据类型的名称,同时意味着要给出该派生数据类型的完整定义或着说描述。一个完整的派生数据类型的句法形式(R422)为:

TYPE [[, access-specification] :: ] type-name

  [private-sequence-statement]…

component-definition-statement

  [component-definition-statement]…

END TYPE [type-name]

其中:

· 可选的访问说明(access-specification)为关键词PUBLICPRIVATE

· 类型名称(type-name)需要由程序作者自己给出,最好是有意义的英文词汇或缩写。

· 私用序列语句(private-sequence-statement)PRIVATE语句和SEQUENCE语句。

每一条成员定义语句(component-definition-statement)(R425)包含一个数据对象的类型说明(R502),其句法形式为:

type-specification[ [, component-attribute-list] :: ] component-declaration-list

其中:

· 类型说明(type-specification)表示一个固有数据类型,或者是一个已经定义过的派生数据类型,不过如果这个描述符后面带有POINTER属性,由于指针的赋值特性,那么该成员就可以表示任意可访问的派生类型,包括正在被定义的派生类型自身。

· 成员属性(component-attribute)(R426)只能是POINTERDIMENSION,即对于指针性质的成员,使用可选的属性POINTER;而对于数组成员,使用可选的属性DIMENSION,后面还可以再加上数组描述。POINTERDIMENSION这两个属性可以同时出现。

· 成员声明(component-declaration)给出成员的名称。

成员声明(R428)的句法形式为:

 

component-name[(component-array-specification)] &

[*character-length] [component-initialization]

其中:

· 成员数组说明(component-array-specification)是可选项,放置在括号当中。如果该数组具有指针属性,那么它的形状未定,否则就是显形状。在显形状的描述当中,数组的界都必须是标量整型常量表达式。如果在这里没有指出数组的界,那么一定要在后面的DIMENSION属性里说明。

· 字符长度参数(character-length)是一个可选的标量整型字面常量,它必须以星号开头。当然这个参数只能用于修饰字符型成员。

成员初始化(component-initialization)(R429)取下面2种形式之一:

=initialization-expression

=>NULL()

其中:

· 初始化表达式(initialization-expression)用来说明非指针成员的默认初始值。

· =>NULL()即把成员作为指针赋值为不带变元的固有函数NULL的结果,表示指针成员的未结合或空置这2种初始结合状态。初始化指针状态的用处在于,很多情形下,要设置一个指针,都必须首先要求该指针具有确定的结合状态。

构造了一个派生数据类型之后,要使用这个派生类型,自然需要声明属于该类型的变量,对于派生类型来说,变量的声明采用如下句法形式:

TYPE(type-name)[attribute-list::] entity-list

注意这个TYPE语句和定义派生类型的TYPE语句在句法上有差别:声明变量时,TYPE后面必须接括号里面的变量所属派生类型名称;而定义派生类型时,后接不带括号的派生类型名称。

构造了一个派生数据类型之后,经常需要引用其中的成员,引用的句法形式为:

      parent-structure % component-name

其中父结构(parent-structure)指需要引用的成员所属的派生数据类型(或数据结构)

派生数据类型构造的一般规则如下:

派生数据类型的定义当中给出的类型名称不能与任何固有数据类型的名称重合,也不能与任何本模块能够访问的其他派生数据类型的名称重合。必须保证该名称在它的有效作用域内是指称同一个类型对象。而派生数据类型内部成员的名称的作用域只局限于该派生类型内部,因此不同派生类型的成员可以使用相同的名称,即使这些不同的派生类型属于同一个作用域。同样的,不同作用域当中如果出现了相同的派生类型名称,那么它们实际上是不相干的数据对象。

● END TYPE语句后面或者省略类型名称,或者后接该类型的名称,不能出现不一致的情况。

一个作用域内,一个派生类型的名称只能定义一次。

在一个派生类型的定义当中,PRIVATE语句只能出现一次。

在一个派生类型的定义当中,SEQUENCE语句也只能出现一次。

只有当一个类型定义是放置于一个模块的规则说明部分时,才能使用访问控制符PUBLICPRIVATE

只要派生类型的定义当中出现SEQUENCE语句,就说明该派生类型的所有成员都必须具有顺序属性 (即其中也必须出现SEQUENCE),并且在定义中成员定义的次序就说明了该类型的对象的存储序列。

在一个派生类型的定义当中,必须至少包含一个成员的定义。

在成员定义当中,任何特定的成员属性都只能出现一次。

一个成员只有在具有指针属性时,才可声明为属于被定义的派生类型本身。

任何非指针的数组成员,都必须表示为显形状形式,其上下界都是整型常量

表达式。

如果成员是指定了长度的字符型数据,那么它的长度必须是整型常量描述表达

式。如果没有指定长度值,那么默认值为1

如果进行成员初始化,那么必须使用双分号间隔符。

2种初始化方式中,初始化表达式只能用于非指针变量,也可以说等价赋值符号(=)只能用于非指针变量。而固有函数NULL()则是用于指针变量的。

初始化表达式只能在派生类型的作用域当中取值。

一个派生类型内部的成员当中,可以只有部分成员是默认初始化的,即取得默认初始值,或取得默认初始结合状态,而不是一定要求所有成员都必须初始化。

一个成员如果是数组,那么它的默认初始值必须满足数组的定义,可以是一个数组构造器,也可以是一个标量,这个标量表示了该数组成员的每个元素的初始值。

下面给出不同情形下构造派生数据类型的例子:

【例6-2

TYPE SET

INTEGER N, M

END TYPE

TYPE (SET), DIMENSION (2, 2) :: a, b(3) 

在这个派生类型的定义当中,ab都是属于派生数据类型SET的数组变量。

【例6-3

TYPE CURRENT

REAL::HIGH=5.5, LOW=1.2

END TYPE CURRENT

在这个派生类型的定义当中给出了其唯一实型成员的初始值.

【例6-4

TYPE employee_name

   CHARACTER(25) last_name

   CHARACTER(15) first_name

END TYPE

 

TYPE employee_addr

CHARACTER(20) street_name

INTEGER(2) street_number

INTEGER(2) apt_number

CHARACTER(20) city

CHARACTER(2) state

INTEGER(4) zip

END TYPE

在上面2个派生类型的定义当中,一个派生数据对象是另一个派生数据对象的成员。

【例6-5 6-4中定义的数据对象还可以被第3个数据类型引用,如:

TYPE employee_data

TYPE (employee_name) :: name

TYPE (employee_addr) :: addr

INTEGER(4) telephone

INTEGER(2) date_of_birth

INTEGER(2) date_of_hire

INTEGER(2) social_security(3)

LOGICAL(2) married

INTEGER(2) dependents

END TYPE

【例6-6

TYPE ARTICLE

  CHARACTER(LEN=100)ABSTRACT

  INTEGER LINES

  CHARACTERPOINTER::TEXT()  

 END TYPE ARTICLE

在这个派生数据类型里面包含一个指针成员。

【例6-7

TYPE LINK

 INTEGER VALUE

 TYPE(LINK)POINTER::PREVIOUS=>NULL()

 TYPE(LINK)POINTER::NEXT=>NULL()

END TYPE LINK

 

TYPE (LINK),POINTRE::A_LINK

ALLOCATE(LINK)

在这个派生数据类型当中,指针成员的类型已经被定义,这种派生数据类型被广泛应用于链表与树结构。

【例6-8

TYPEPRIVATE::FILE

 INTEGER FILE_NO

 CHARACTER(LEN=30) FOLDER_NAME

 CHARACTER(LEN=10) ACCESS_LEVEL

END TYPE

这个派生数据类型定义在一个模块当中,但是对这个模块保持私用状态。

6.2.2  派生数据类型的取值,运算,以及常量表达式

一个派生数据类型的值集就是它的成员的值集的乘积,也就是它的成员的所有可能取值的所有可能的组合. 我们知道每个固有类型都有一个固定的值集,在任何环境下引用固有类型,这个值集都是一致的,而派生类型的数据值集则完全是根据它的成分的数据值集确定的。

由于派生数据类型本质上是一种数据结构,因此对派生类型对象的运算的定义,需要使用具有OPERATOR界面的函数,而赋值则使用具有ASSIGNMENT界面的子例行程序。详细的有关子例行程序及其界面的讨论参见第13章。

上面给出的派生数据类型的定义过程,实质上就是给出了一个语法机制,根据这个语法机制构造出来的语言结构肯定能够被FORTRAN编译器识别为派生数据类型对象,即或者是属于该派生数据类型的变量,或者是属于它的变量的具体取值。

因此派生数据类型的定义自动给出它的取值的结构构造器,该结构构造器生成一个数据序列,其中每一个数据对应着该类型数据对象的一个成员的取值。而该类型数据对象的所有成员的所有取值的任意组合,正是该结构构造器所能生成的所有常量。

通过这样的结构构造器生成的常量,也可以用来给定义为该派生类型的命名常量赋值。

由于派生数据类型的成员除了可以是固有数据类型之外,还可以是任意的数据结构,特别是设置为 FORTRAN的一种重要的数据结构-数组。在这种情况下,特别定义了一种用来描述派生类型的数组成员的语法机制,称为数组构造器。

当数组构造器的取值全是常量表达式时,得到数组值常量表达式,这样一个表达式可以作为一个命名常量的数组成员。

数组构造器不仅是可以用来给出一个数据结构的成员的值,也可以用来给出任意数据类型的数组值。

6.2.3  数据结构构造器

一个属于派生数据类型的常量的句法形式(R431)为:

type-name (expression-list)

其中

类型名称(type-name)为某个派生数据类型的名称。

表达式列表(expression-list)为给出派生数据类型的各个成员值的表达式序列。

结构构造器的一般规则如下:

结构构造器不能在它所代表的派生数据类型被定义之前出现,也就是说它给出的派生类型名称不能置空。

结构构造器给出的值必须与type-name所指的派生类型的成员一一对应,书写顺序也必须和派生类型定义里各成员的排列顺序保持一致。为了保证与各个成员在类型,种别参数,长度以及秩的一致,有时可能需要对值进行适当的转换。

如果派生类型包含数组成员,那么在各个成员值的表达式序列当中数组的形状,必须与类型定义当中的该数组成员的形状保持一致。

如果派生类型包含指针成员,那么在各个成员值的表达式序列当中,该指针所对应的值,必须是相应指针赋值语句当中的有效目标。

如果结构构造器的所有成员值都是常量表达式,那么该构造器实质上就构成一个派生类型的常量表达式。

【例6-9 考虑下面的派生数据类型:

TYPE EMPLOYEE

   INTEGER ID

   CHARACTER(LEN=40) NAME

END TYPE EMPLOYEE

可以得到下面的结构构造器:

  EMPLOYEE(5645, "陈辉")

【例子6-10 下面的派生数据类型包含另外的派生类型作为成员:

TYPE ITEM

 REAL COST

 CHARACTER(LEN=30) SUPPLIER

 CHARACTER(LEN=20) ITEM_NAME

END TYPE ITEM

 

TYPE PRODUCE

REAL MARKUP

TYPE(ITEM) BOOK

END TYPE PRODUCE

这时,就需要使用内嵌的结构构造器来得到成员取值:

  PRODUCE(.70, ITEM (.25, "XIN HUA", "A BOY"))

6.3  子串

从符号的意义上来看,下面两个数据对象都是属于字符串:

ASDFJASDF

12349123123

如果我们把12349123123看成是标量,那么没有理由不把ASDFJASDF也看成是标量。然后进一步由标量构造成数组,同样可以用字符串作为数组的分量,由于字符串的参数就是它的长度,因此在用字符串构成数组时,有必要限制它们的长度保持一样。

要以长度为参数构造字符串,一个自然的途径就是从一个字符串当中,按照长度截取其连续的某个部分,这就提出了子串的概念。

子串的句法形式(R609)为:

parent-string(substring-range)

其中父串(parent-string)的句法形式(R610)为以下诸种形式之一:

scalar-variable-name

array-element

scalar-structure-component

scalar-constant

子串范围(substring-range)的句法形式(R611)为:

 [starting-position][ending-position]

其中子串的始点和终点为标记父串里字符位置的下标的整型表达式,始点为子串最左边的字符在父串里所处的下标表达式,终点为子串最右边的字符在父串里所处的下标表达式,显然,始点和终点都处于1LEN之间,LEN为父串的长度。如果始点值大于终点值,那么系统认为该子串的长度为0。给出始点和终点,就能从父串截取到相应的子串。

子串的一般规则如下:

字符串属于字符型数据类型,父串与子串都是字符串。

字符串的下标是按照从左到右的顺序,从1开始,一直到LENLEN为字符串的

长度。

子串是父串里从第i个字符开始,到第j个字符结束的连续的一段字符串,Ij

都大于等于1,小于等于LEN,下标为i的字符称为子串的始点,下标为j的字符

称为子串的终点。

如果始点与终点的下标表达式的值不是整型,则必须转换为整型。

如果i没有给出,默认i1;如果j没有给出,默认jLEN

子串的长度可以是0,但不能是负值,如果给出的i大于j,则系统认为子串的长

度为0

计算子串的长度使用函数MAX,公式为:

MAX(ending-position - starting-position + 10)

如果父串的长度为0,那么它的任意子串的长度都是0

【例6-11

CHARACTER(10)signal_peptide

signal_peptide = “HLA-A*0301”

signal_peptide(1:3) = “HAI”

PRINT *, signal_peptide(7:10)

在这个例子里,signal_peptide被定义为长度为10个字符的字符型变量,在第一个赋值语句里,该变量被赋值为字符串“HLA-A*0301”,在第二个赋值语句里,该变量的下标从13的子串被引用,并且被赋值为字符串“HAI”,这样signal_peptide的值就变成了“HAI-A*0301”,接下来的打印语句的执行结果是打印下标从710的子串,为“0301”

【例6-12

CHARACTER*8 A, Asteroid

Asteroid = '2000 DG8'

A = Asteroid (1:4)

在这个例子里,首先定义了长度为82个字符型变量AAsteroid,然后给出2个赋值语句,对字符型变量Asteroid赋值为'2000 DG8',而对A赋值为Asteroid的一个子串'2000'

【例6-13

TYPE nearly_isotropic_comets

INTEGER inclination

REAL semi-major_axis, eccentricity, perihelion_distance, Absolute_Magnitude

CHARACTER*12 designation

END TYPE nearly_isotropic_comets

 

TYPE(nearly_isotropic_comets) 2000YEAR

CHARACTER*12 ASTEROID, BIG_INCLINATION(5)

由上面定义的字符串,可以得到如下合法子串:

BIG_INCLINATION(2) (1:4)  !父串为数组元素

2000YEAR % designation(6:12)!父串为结构成员

ASTEROID (1:4)!父串为标量变量

“ABCDEFGH”(N:N+1)   !父串为字符常量

如果一个数组的分量为字符串,那么由这个数组构造子串,可以有很多种合法的可能性,例如,BIG_INCLINATION(:) (1:4),表示对一个由子串组成的数组片断的引用;BIG_INCLINATION(1:4) (1:4),同样表示一个由子串组成的数组片断的引用。因此如果要从一系列长度一致的字符串构造相同下标范围的子串,可以通过对数据组的片断引用而实现。不过必须注意数组片断下标放置于子串下标之前。由于表示数组片断的句法形式和表示子串下标范围的句法形式一样,因此必须使用这个顺序安排,才能得到无歧义的表示。

【例6-14

下面2个引用含义完全不同:

BIG_INCLINATION(:) (1:4)

BIG_INCLINATION (1:4)

前者表示由5个子串构成的数组,后者表示由4个父串构成的数组。

    “ABCDEFGH”(N:N+1)得到的子串既非常量,也非变量,因为它的下标是一个变量,得到的子串称为常量子对象。参见表达式的形式。

6.4  结构成员

所谓结构就是一个集合,其元素可以是任意数据类型,也可以是标量或数组,一个结构至少包含一个元素,这样的结构本身就是一种派生数据类型,由同一类型的多个结构又可以组成一个数组。因此我们可以看到,结构的定义包含递归的成分,这样能够保证提供最大的灵活性,以便足够充分地描述实际问题当中出现的数据结构,减少因为数据结构复杂而导致算法复杂的压力。

定义一个结构之后,如果要引用其成员,可以直接使用如下形式:

parent [%component [(section-subscript-list)]] ... %component [(section-subscript-list)]

其中:

parent为父结构名称。

百分符号(%)称为成员选择符。

component为其左边邻接父结构或成员的成员

section-subscript-list表示片断下标列,如果该下标列包含下标三元组或者向量下标,那么就表示引用一个数组片断。

由于结构定义具有递归成分,因此要引用结构的成员,就会出现很多的情形,更加抽象的理解是把结构成员的引用(R614)看是一种数据引用(R612)的形式。

所谓数据引用,是把任何被引用的数据看成某个数据集合的部件,这种包含关系可以任意多层地嵌套,因此结构成员的引用也可以运用句法形式(参见附录B)递归定义如下:

part-reference [%part-reference]…

其中部件引用(part-reference)(R613)的形式定义为:

  part-name [(section-subscript-list)]

其中片断下标(section-subscript)(R618)的形式定义为以下几种情况之一:

  subscript

subscript-triplet

vector-subscript

有关数组片断的下标参见6.5.5节。

结构成员引用的一般规则如下:

结构成员的引用作为数据部件的引用,要求被引用的部件是属于多个部件之中的

一个。

作为结构成员引用的数据引用的最右边的被引用的部件,必须是一个部件名称,

如果该部件引用具有形式:

part-name (section-subscript-list)

那么被引用的就是一个数组片断,在最简单的情形下,是一个数组元素。

在上述数据引用定义里,除了最右边的部件名之外,每一个部件名称都必须是指

称一个派生类型数据对象。

数据引用时,除了最左边的部件名之外,每一个部件名称都必须是其左边邻接部

件名称所表示的派生类型的成员。

在上述数据引用定义里,任意位置的部件都可以是数组,一个数组右边邻接

的成员不能具有指针属性。

如果父结构具有INTENT, TARGET, PARAMETER属性,则其结构成员也具有

同样的属性。

在父结构还没有被声明之前,其结构成员不能被引用。

结构成员如果含有非0秩的部件,那么它的秩就是非0秩的部件的秩,否则秩为0

结构成员的类型和类型参量(如果存在的话),与最右边的部件保持一致。

【例6-15 下面定义一个包含2个成员的派生数据类型:

TYPE EMPLOYEE

 INTEGER ID

 CHARACTER(LEN=40) NAME

   END TYPE EMPLOYEE

然后声明变量CONTRACT 属于派生数据类型EMPLOYEE,并且引用该变量的一

个成员。

   TYPE(EMPLOYEE) :: CONTRACT

   CONTRACT%ID

【例6-16 下面定义一个派生类型作为另一个派生类型的成员:

TYPE DOT

REAL X, Y

END TYPE DOT

....

TYPE SCREEN

TYPE(DOT) C, D

END TYPE SCREEN

然后声明变量M属于派生类型SCREEN,并且引用该变量的几个成员:

TYPE(SCREEN) M

M%C

M%D (both of type DOT);

M%C has components

M%C%X

M%C%Y of type REAL.

【例6-17 下面定义一个包含数组成员的派生类型:

 TYPE CAR_INFO

 INTEGER YEAR

 CHARACTER(LEN=15), DIMENSION(10) :: MAKER

 CHARACTER(LEN=10) MODEL, BODY_TYPE*8

 REAL PRICE

END TYPE

...

TYPE(CAR_INFO) MY_CAR

【例6-18】下面定义派生类型以及属于该类型的2个变量:

TYPE CHARGE

INTEGER PARTS(40)

REAL LABOR

REAL MILEAGE

END TYPE CHARGE

 

TYPE(CHARGE) MONTH

TYPE(CHARGE) YEAR(12)

如下引用的数组都是合法的:

MONTH%PARTS(I)   ! An array element

MONTH%PARTS(I:K) ! An array section

YEAR(I)%PARTS! An array structure component (a whole array)

YEAR(J)%PARTS(I) ! An array element

YEAR(J)%PARTS(I:K)   ! An array section

YEAR(J:K)%PARTS(I)   ! An array section

YEAR%PARTS(I)! An array section

【例6-19 下面定义的派生类型包含一个已有定义的指针成员:

TYPE NUMBER

INTEGER NUM

 

TYPE(NUMBER), POINTER :: START_NUM => NULL( )

TYPE(NUMBER), POINTER :: NEXT_NUM => NULL( )

 

END TYPE

这样的结构可以用来构造链表,注意其中的指针为默认的非结合初始状态。

【例6-20 下面定义一个私用派生类型:

TYPE, PRIVATE :: SYMBOL

 LOGICAL TEST

  CHARACTER(LEN=50) EXPLANATION

END TYPE SYMBOL

这个派生类型属于模块私用类型,该模块可以被其他作用域访问,但该类型只能被本模块引用。

6.5  数组

数组同样是一个集合,它的元素必须是标量,其标量元素可以属于任何的固有数据类型,派生数据类型,甚至是结构,但数组最关键的一个特征是:

数组的所有标量元素必须属于同一个数据类型,并且具有同样的种别参数。

这个特征决定了数组是一种功能强大的数据结构,因为存在大量的实际问题,需要用同一个计算过程来处理大规模的同种类型的数据,因此FORTRAN 95提供了强大的内部运算和固有函数,专门用来处理整个数组,或者数组元素,或者数组片断,同时硬件的并行化发展,也为这一类通常是极大规模的运算,提供了优化的解决方案。

FORTRAN的早期版本里面,数组是作为数据的一种属性加以描述的,所使用的关键词为DIMENSION。这个规则一直被保留下来,因此任何数据对象只要是具有DIMENSION属性,就一定为数组。

6.5.1  数组的结构

数组的结构可以抽象地理解为多维离散空间的点的集合,即这些点都有相同的维度,在每一维上的坐标取值都是离散的标量。

一个具体的例子就是表格。实际上表格总是可以用二维数组加以描述,例如取上章的如下表格:

  6-1表格可以看成数组

 

   作用单元的种类

 语句 

主程序

模块

数据块

外部子程序

模块子程序

内部子程序

接口块

USE语句

Y

Y

Y

Y

Y

Y

Y

ENTRY语句

N

N

N

Y

Y

N

N

FORMAT语句

Y

N

N

Y

Y

Y

N

其他声明

Y

Y

Y

Y

Y

Y

Y

DATA语句

Y

Y

Y

Y

Y

Y

N

导出类型定义

Y

Y

Y

Y

Y

Y

Y

接口块

Y

Y

N

Y

Y

Y

Y

语句函数

Y

N

N

Y

Y

Y

N

CONTAINS

Y

Y

N

Y

Y

N

N

可执行语句

Y

N

N

Y

Y

Y

N

 

如果该表格的行,即作用单元的种类看成是一个维度a;把该表格的列,即语句的种类,看成是另一个维度b,那么该表格的每一个元素都可以用符号E作用单元的种类,语句的种类,或者Eab,来表示,其中a可以取7个值:

主程序,模块,数据块,外部子程序,模块子程序,内部子程序,接口块。

b可以取10个值:

USE语句,ENTRY语句,FORMAT语句,其他声明,DATA语句,导出类型定义,接口块,语句函数,CONTAINS,可执行语句。

而每个元素Eab取值为符号YN

这样我们就把一个表格完全的用一个数组加以描述了。

从上面的例子可以看到,数组的结构是由维度,和每个维度的取值数目决定的。其中:

一个数组的维度的数目称为该数组的秩;

每个维度的坐标数目称为数组在该维度的宽度;

那么一个数组的秩和每个维度的宽度就决定了该数组的形状;

一个数组的所有维度的宽度的乘积,称为该数组的尺度,也就是该数组的元素的数目。

数组的秩至少可以达到7,这是FORTRAN标准所要求的任何编译器都必需达到的数组处理规模,当然实际的编译器所能处理的数组的秩都要大于7。同时FORTRAN标准对于宽度的大小没有限制。

数组的形状可以用类似于向量的形式表示,即括号当中的一串标量数值,用逗号相隔,每个数值表示相应的维度的宽度。例如:

REAL A(22510)

表示以实型数值为元素的数组A,含有3维,每个维度的宽度分别是22510

在描述每个维度的宽度时,可以使用下标范围的形式,也就同时说明了该维度的元素的下标表示方法,例如:

REAL A(20:2410)

这个数组可以用上面的语句来描述,但是这里的描述方式更加具体地说明了数组的第2个维度的下标标示方式,即用从024的下标了标示25个该维度的坐标。

显然数组A的尺度为,即数组A包含500个元素。同样形状的数组A还可以用别的形式加以说明。

【例6-21

DIMENSION A(20:2410)

REALDIMENSION(20:2410):: A

COMMON A(20:2410)

TARGET A(20:2410)

由于机器表达数据的有限性,数组在每个维度上的坐标都必定有上下界,当在数组描述时使用了下标,那么最小下标值就是数组在该维度的下界,最大下标值就是数组在该维度的上界。

必需注意的是这时数组的下标范围可以取任意的整数值范围,而如果没有在数组说明当中说明下标范围,只是给出其宽度n,那么数组在该维度的默认下标范围是1n,即从1n的自然数。

因此与上面同样形状的数组A还可以表示为:

REAL A(2-5:1910)

REAL A(25:2910)

关于形状的描述里面的上下界的表示,参见第8章的有关小节。

6.5.2  全数组

由于数组是一个数据结构,因此对于数组的引用,可以是引用整个数组,也可以是引用数组的某个元素,某些元素,或数组的片断,具体的分别在于,如果是引用了数组的名称,无论是一个常量数组的名称,还是一个变量数组的名称,只要在引用该数组名称时,不包含该数组的下标列,或数组片断的下标列,那么就是把该数组作为一个整体加以引用,即作为一个全数组。

一个全数组如果具有INTENTTARGETPARAMETER这些属性,那么它的任意元素和片断都具有相应的属性;

一个全数组如果具有POINTER属性,注意,它的任意元素和片断都不具有相应的POINTER属性。

6.5.3  数组元素

数组元素的引用很简单,直接用下标给出该元素在每一个维度上的坐标即可。

【例6-22 声明1维数组X如下:

INTEGERDIMENSION(25)::X

那么对X的元素的引用例子为:

X(3)X(12)X(24)

声明3维数组A如下:

REALDIMENSION(22510):: A

那么对A的元素的引用例子为:

A(1125)A(2228)A(11710)

这里的(1125)等称为下标列。

6.5.4  数组片断

数组最灵活的功能体现在能够以数组的任意部分为对象作运算,把一个数组的任意指定部分作为一个新的数组,称为数组片断,而其所属的数组称为该数组片断的父数组。

数组片断的引用,首先是一个父数组变量名称,然后是下标列,不过这里的下标列里面,必需至少包含一个下标是用下标三元组,或者是下标向量来表示的,否则这个引用就成了对数组元素的引用。

下标三元组和下标向量的定义见下节。

【例6-23 下面是引用了一个数组片断:

INTEGERDIMENSION(25)::X

   …

X(1016)=78

上面定义了1维整型数组X,其宽度为25,后面引用了X的一个片断,由X的第10个坐标到第16个坐标这7个元素组成,在这个引用当中,X所指定的这7个元素都被赋值为78

注意这里的坐标引用(1016)为一个省略了第3项的下标三元组。

6.5.5  数组元素和数组片断的句法形式

数组元素的引用实质上就是数据引用,因此数组元素的一般句法形式就是R612

数组片断的一般句法形式(R616)为一个数据引用,后接一个可选的包含在括号当中的子串范围。子串范围的句法形式见6.3节的R611

数据引用当中的组分名称可以后接一个可选的片断下标列,片断下标的句法形式(R618)可以是如下3种之一:

subscript

subscript-triplet

vector-subscript

其中下标三元组(subscript-triplet)的句法形式(R619)是:

[subscript][subscript][stride]

下标向量(vector-subscript)(R621)则就是一般的秩为1的整型数组表达式。

这里的下标和步长(stride)都必须是整型标量表达式。

一般规则如下:

如果被引用数据是一个数组元素,那么这个数据引用的每个部件引用都必需是秩为

0,而最后的部件引用必需包含一个下标列。

如果被引用数据是一个数组片断,那么必须有一个部件引用为非0秩,并且或者是

最后的部件引用非0秩的片断下标列,或者是另外一个部件引用具有非0秩。

如果一个数组片断以后接子串范围的数据引用的形式出现,那么最右边的部件名称

必须是字符型。

在数组的每一个维度上,片断的下标都必须给出。

【例6-24 下面都是合法的数组元素与数组片断的引用:

X(23)

X(1:N:2M)

Y(: , : , :)(1:3)

SCALAR_A % ARRAY_D(L)

SCALAR_A % ARRAY_D(1:L)

SCALAR_A % ARRAY_E(1:N)%SCALAR_D

ARRAY_F(1:N:2) % ARRAY_G(I,J) % STRING(K)(:)

 

TYPE REPAIR_BILL

REAL PARTS (30)

REAL LABOR

END TYPE REPAIR_BILL

 

TYPE VEHICLE

CHARACTER(LEN=35)OWNER

INTEGER MILEAGE

TYPE(REPAIR_BILL)COST

END TYPE VEHICLE

 

TYPE(VEHICLE)BLACK_BURK  YELLOW_XIALI

 

PRINT*BLACK_BURK % COST % PARTS

PRINT*YELLOW_XIALI % COST % LABOR

PRINT*BLACK_BURK % OWNER

   

TYPE(REPAIR_BILL)FIRST

TYPE(REPAIR_BILL)FOR2000(10)

【例6-25 下面都是合法的标量父结构:

FIRST % LABOR

FIRST % PARTS(I)

FIRST % PARTS

FIRST % PARTS(IJ)

FOR2000(K)% LABOR

FOR2000(K)% PARTS(I)

FOR2000(K)% PARTS

FOR2000(K)% PARTS(IJ)

【例6-26 下面都是合法的向量父结构:

FOR2000 % LABOR

FOR2000 % PARTS(I)

FOR2000(KL)% LABOR

FOR2000(KL) % PARTS(I)

【例6-27 下面都是非法的向量父结构:

FOR2000(KL) % PARTS  !非法

FOR2000(KL) % PARTS(IJ)   !非法

FOR2000 % PARTS   !非法

FOR2000 % PARTS(IJ)!非法

1. 下标

在对数组元素作引用时,每一个下标都必须处于该元素所属维度的下标上下界之中。

如果下标出现在数组片断的引用当中,那就意味着该被引用的片断相对于其父数组缺少了相应的维度,因此片断的秩也就相应地要减少1,因此在引用数组片断时,如果父数组的某个维度只是以其上下界的某个坐标出现在其片断引用当中,显然该维度实际上在其片断里就被压缩为1点了,也就是减少了一个维度。

2. 下标三元组

所谓下标三元组是一种表示从在某个维度上连续取值的数组里,有规律地按照固定间隔取数组片断的表达形式。

下标三元组的一般形式(R619)见上。

其中第一个数值表示取片断的相应维度上的下界;第二个数值表示取片断的相应维度上的上界;第三个数值表示在从上界到下界,按照固定间隔取坐标时的步长。

下标三元组的一般规则如下:

如果下界被省略了,那么所取默认值为父数组所声明的下界;

如果上界被省略了,那么所取默认值为父数组所声明的上界;

如果步长被省略了,那么所取默认值为1

如果上下界和步长都被省略了,而只剩下冒号(),那么就是取整个维度范围;

如果步长为正整数,那么该片断的下标为一个数列,取值为从下界开始,以步长为

间隔,递增直至不大于上界的最大下标值;这时如果下界大于上界,那么该片断的下

标数列为空集;

如果一个片断的下标数列为空集,那么该数组片断的尺度为0

步长不能取0

如果步长为负整数,那么该片断的下标为一个数列,取值为从下界开始,以步长为

间隔,递减直至不小于上界的最小下标值;这时如果下界小于上界,那么该片断的下

标数列为空集;

只要按照三元组所取的数组元素都包含在父数组的某个维度范围里面,那么该三元

组所给出的范围可以超出父数组所声明的相应维度范围。

【例6-28 如果数组A声明如下:

  A(789)

A的一个片断为:

  A(47534)

那么该片断的秩为2,比其父数组A的秩小1,因为该片断引用当中的第2个维度出现了一个单独的下标,故维度减小了1;而片断的形状为(42),尺度为8,它的8个元素为:

  A(453)

  A(454)

  A(553)

  A(554)

  A(653)

  A(654)

  A(753)

  A(754)

【例6-29 给出数组A(100),而它的一个片断为A(4528-5),那么该片断的秩为1,形状为(4),尺度为4,它的所有元素为:

A(45)

A(40)

A(35)

A(30)

而如果取其片断为A(4528),这时默认步长为1,显然其尺度为0

如果取其片断为A(2511035),尽管第2个下标超出了父数组A的下标范围,但是该片断的元素却没有超出父数组的元素范围,因此这种片断取法是合法的,它的所有元素为:

A(25)

A(60)

A(95)

3. 下标向量 

鉴于依靠下标三元组所截取的数组片断是具有规则形式的数组片断,当我们需要其他形式的数组片断时,三元组就无法胜任相应的描述数组的任务了,因此FORTRAN给出了一种具有最大的灵活性的截取数组片断的方式,即下标向量。

所谓下标向量,就是由一些下标值构成的一个向量,这些下标值取自父数组在某个维度上的下标范围,由与这些下标值相应的坐标就构成了父数组的一个数组片断。

【例6-30

INTEGER I(10)

REAL A(100)

   …

I=(/ 2335114657657889901/)

A(I)=3.34

这段例程里首先定义了2个秩为1的数组变量I(10)A(100),也即向量,然后在后面用赋值语句给出I的一个具体赋值,接着以I为数组下标变量,给出了数组A的部分元素的一个具体赋值,实际上,这里I就是下标向量,而A(I)则是由下标向量I所给出的父数组A(100)的一个数组片断。

注意其中根据向量I的赋值,A的片断截取是任意顺序的,这就体现了利用下标向量来截取数组片断的特点。

不过虽然由下标向量来截取数组片断具有极大的灵活性,但是根据下标向量而得到的数组片断具有如下限制:

1.不能作为外部文件;

2.不能作为指针目标;

3.不能作为哑元INTENT(OUT)INTENT(INPUT)的实元。

下标向量的构成允许出现重复的元素,例如在上面的例子里面,下标向量I可以赋值为:

I=(/ 2335234657657889901/)

这样A(23)就会被访问2次,即A(I(1))A(I(3)),这样得到的数组片断仍然是合法的,被称为多对一数组片断,这样的数组片断不能出现在如下场合:

    赋值语句当中等号的左边;

    READ语句的输入项。

6.5.6  下标,下标三元组和下标向量的混合使用

上面说明了如何分别使用下标,下标三元组和下标向量这三种方式来截取数组片断,实际上,这三种方式可以根据具体问题的要求来混合使用,下面是1个例子。

【例6-31 给出父数组A如下:

A(201020)

可以构造A的一个数组片断如下:

A(4227 9 10153)

该片断的所有元素如下:

A(4910)

A(11910)

A(18910)

A(4913)

A(11913)

A(18913)

然后我们还可以给出下标数组(向量)

INTEGER DIMENSION(4)::I=(/3739/)

运用这个下标向量可以构造数组片断:

A(10208 I 14)

该数组片断的所有元素为:

A(10314)

A(18314)

A(10714)

A(18714)

A(10314)

A(18314)

A(10914)

A(18914)

可以看到其中包含了重复的元素,即多对一数组片断。

6.5.7  数组元素序

由于计算机在实质上只能按照一维的串行顺序来处理数据,因此对于高于1维的数组,就产生一个如何表达其中数据的问题,这就需要约定一个统一的把多维数组转化为一个便于计算机处理的数据序列的方法,按照这个统一约定得到的数列就表示了数组元素的顺序,即数组元素序。

由于一个多维数组的各个维度是可以用序列的方式表示出来的,因此一个自然的表达数组元素序的方法就是,规定数组维度的顺序即各个维度被依次遍历的顺序,

【例6-32 取如下2维数组:

  A(23)

按照它的形状的表示方法,它的第1维有2个下标,第2维有3个下标,那么这个数组的元素的排列顺序就是:

A(11)A(21)A(12)A(22)A(13)A(23)

即先遍历第1维的2个下标而保持第2维的下标不变,然后再遍历第2维的下标,直观地看,就是依照数组元素顺序排列下来后,第1维的下标的变化最快,而最后1维的下标变化最慢,其他维度的下标的变化依次类推。

对于按照数组元素序排列的数组元素数列,可以给以这个数列本身的下标标记,这个下标称为下标顺序值。

按照上面约定的从多维数组到串行序列的转换规则,可以给出一般的多维数组的元素的下标顺序值的计算公式。

当然,对于一个数组片断,它的元素的下标顺序值不能指定为其所在父数组里的下标顺序值,而只能是把该片断本身看成一个数组之后,重新给出的下标顺序值。

例如对于数组A(10),取其片断A(293),该片断元素包括A(2)A(5)A(8),对于1维数组A(10)来说,这些元素的下标顺序值就是它们的下标,即258。而对于数组A(293)来说,它们的下标顺序值却分别是123

6.5.6  数组构造器

所谓数组构造器就是给数组元素赋值时的句法形式,即数组元素的写法。由于语言的表述在本质上只能是一个字符串,因此数组元素的取值的写法只能是针对1维数组而言,因此数组构造器在形式上得到的是一个秩为1的数组的元素具体取值。

作为一个秩为1的数组的元素,自然的形式就是一个元素组成的序列,但是为了在句法上标志其构成一个数组,约定使用一对括号与斜线把所有元素值和其上下文区分开,因此一个数组构造器的句法形式(R432)为:

(/ ac-value-list /)

数组构造器元素取值列(ac-value-list)中的元素取值作为数据对象,可以是表达式(R723),或者是隐性do变量取值(R434),所谓隐性do变量取值是直接以变量的形式来表示do结构含义的变量的具体取值。

【例6-33

 (SQRT(REAL(K))K=1013)

这里REAL(K)为变量形式,(SQRT(REAL(K))K=1020)则具有do结构的含义,经过K的具体取值,得到一个数组:

 (/ 3.1623.3173.4643.606 /)

它的一般句法为:

 (ac-value-listac-do-variable=scalar-integer-expression&

  scalar-integer-expression [scalar-integer-expression])

其中数组构造器值可能有三种形式:

    标量表达式。

【例6-34

/2.46.42.42.59.8/)

    数组表达式。

【例6-35

 (/X(110I)X(3030I-2)/)

    隐式do变量。

【例6-36

(/(SQRT(REAL(I))I=010)/)

数组构造器的一般规则为:

●  数组构造器里的每一个元素取值都必须是相同的数据类型,相同的种别参数,

相同的长度参数。

数组构造器的数据类型与种别参数就是它的表达式取值的数据类型与种别参数。

如果数组构造器不含有表达式或者其隐性do变量无法实际取值,那么得到一个

秩为0,尺度为0的数组。

数组构造器do变量必须是标量整型命名变量,该变量具有数组构造器do变量的

作用域。

如果一个数组构造器do变量包含在另一个数组构造器do变量里面,它们不能是

同一个变量。