派生数据类型
数据结构是指若干个数据的连接方式,一个复杂的数据往往是由若干个不同类型数据形成的结构。派生类型是指用户利用FORTRAN系统内部类型,如数值型、逻辑型、字符型等自行设计出一个新的数据类型,它们实际上是由内部类型数据形成的某种结构。本章主要目的是学会按复杂数据的客观结构形态,由程序员定义出一种派生类型,再结合上将在后面叙述的模块后,可将该类型必需的操作写成内部子程序,连同派生类型一起写在模块中,供程序各单元共同使用,成为数值计算特别是信息管理的有力工具。
4.3.1 数据结构
实际生活中的数据,不像数学上的那么理想,只是一个整数、一个实数或一串字符等,实用的数据往往是由许多单一数据彼此联系而形成一种结构。例如数组就是由许多数按前后次序排列的一种数据结构,也即数据是一种数据结构,数组名就结构名。但是数组这种结构有局限性,它一定要求结构内所有成员都是同一种内部类型:或者都是整型,或者都是逻辑型,等等。对于成员有的是整型、有的是字符的数据结构,就不能用数组结构表示,必须由程序员自行定义派生类型。
本节不讨论数据结构的严密定义,而是通过实例树立数据结构的概念,设计出能反映复杂数据结构的新类型并加以操作。如输入一个班30个学生的姓名及成绩,由于姓名是字符型,成绩是整型,而数据成员只能是一种类型,因此要把姓名作一数组,成绩另作一数组,排名次时要分别作相应调整,非常不便。如果能设计出一种新的派生类型,该类型既包含字符型成员,又有整型成员,再以新类型数据组成一个数组(它们都是同一个派生类型,可以组成数组)就可以在输人输出时只写一个数组名就可既传递人名又传递成绩,非常方便。
一个数据结构由若干数据组成,每个组成部分称为该结构的成员。表示结构中成员的一股形式:结构名%成员名。即在所属结构名后写一个百分号(%)而后写出成员本身名称。这样的成员可以像访问变量一样被访问,包括赋值、打印、引用等。
学生(结构型)
STUDENT
学号(整型)
NUMBER
班级(结构型)
CLASS
姓名(字符型)
NAME
成绩(结构型)
SCORES
地址(字符型)
ADDRESS
系(字符型)
DEPARTMENT
专业(字符型)
MAJOR
数学(整型)
MATH
物理(整型)
PHYSICS
英语(整型)
ENGLISH
设一个学生记录为一个结构,内由学号、班级、姓名、宿舍、成绩等有关信息组成,它们分别为不同类型。有些成员下还可再由若干数据组成,如图71所示。边注的英文名是程序中所取的该成员的名称。在程序中,如要访问整个学生结构,只要在被访问处写上结构名,即写上STUDENT。访问其中成员,如成员是一个简单数据,例如学生姓名,则在程序中被访问处写上STUDENT%NAME即可。访问成员中结构成员下的简单数据,则用两个%表示。如访问学生所在系,则在被访问处写STUDENT%CLASS%DEPARTMENT。第2个百分号表示DEPARTMENT是CLASS的成员。如果DEPARTMENT下面还有成员,要访问这些成员时后面还需加上‘%成员名’。
4.3.2 派生类型
为了便于组织数据,F90允许定义和使用派生数据类型。任何复杂的数据结构,经分析后都可分解为较简单的成员,可用自定义的派生类型反映它。派生数据类型有一个类型名称,它不能和任何内部数据类型或已定义的派生数据类型重名。定义一种派生数据类型后就可以用它来定义变量、命名常量或其它派生类型了。
a) 派生类型定义
定义派生类型时必须使用TYPE块。TYPE块应写在程序的说明部分中,通常写在说明的前部,其一般形式是:
TYPE[,访问属性说明::] 派生类型名
成员1类型说明
……
成员n类型说明
END TYPE [派生类型名]
其中,TYPE是关键字,表示派生类型定义开始。访问属性说明关键字是PUBLIC或PRIVATE,默认值是PUBLIC,即访问方式是共用的。PRIVATE表示该类型是专用的,这个关键字只有当TYPE块写在模块说明部分中时,才允许使用。如果不是在模块内定义的派生类型,不可使用PRTVATE。派生类型名是任意取的,一旦定义完成,该类型名就成为一个新的类型,就像整型、实型、逻辑型等一样,按一种类型使用。例如可以把各种变量、各种数组说明为这种新的类型,而后按新类型特有法则操作。通常,类型的取名与物理对象的名称一致。例如上述学生结构定义成派生类型可以取派生类型名是STUDENT。
TYPE语句下面是结构中各成员的类型说明语句,被说明的类型一般就是FORTRAN内部类型(整型、实型等),也允许用一个层次较低的派生类型作为某成员的类型。派生类型的说明语句的一般形式是:TYPE(派生类型名)::变量名。TYPE块中只有类型说明语句,不允许有可执行的动作语句。派生类型中字符型成员不可取*为长度,必须是确定长度。系统不一定会按定义时的顺序储存各个成员,除非在定义中包含有SEQUENCE语句。
例如:对上面的学生记录作派生定义,由于学生结构中有两个成员CLASS与SCORES本身又是次一层的结构,因此要先对CLASS与SCORES这两个结构作出派生类型定义。CLASS下有两个成员:DEPARTMENT(字符型)、MAJOR(字符型)。SCORES下有三个成员:MATH(整型)、PHYSICS(整型)、ENGLISH(整型)。整个学生结构定义派生类型名为STUDENT_TYPE。
TYPE CLASS_TYPE
CHARACTER(LEN=50) :: DEPARTMENT, MAJOR
END TYPE CLASS_TYPE
TYPE SCORES_TYPE
INTEGER(1) :: MATH, PHYSICS, ENGLISH
END TYPE SCORES_TYPE
TYPE STUDENT_TYPE
SEQUENCE
INTEGER(4) :: NUMBER
TYPE(CLASS_TYPE) :: CLASS
CHARACTER(LEN=10) :: NAME, ADDRESS
TYPE(SCORES_TYPE) :: SCORES
END TYPE STUDENT_TYPE
TYPE(STUDENT_TYPE),DIMENSION(40):: STUDENT
注意在上面三个定义派生类型TYPE语句中,派生类型名取成‘名_TYPE’形式,这是为了加强可读性。特别是当它的成员名与类型名一致时,可以这样区分。当给定属性DIMENSION(40)后,STUDENT即为有40个学生的派生型数组,每个学生按顺序有学号、班级(系、专业)、姓名、地址、成绩(数学、物理、英语)等成员。
b) 缺省初始化
F95中,允许定义派生数据类型成员时给予初始值,这时就有了缺省初始化,但没有必要为每一个成员指定初始值。它的显式初始化会覆盖缺省初始化。例如:
TYPE REPORT
CHARACTER (LEN=20) REPORT_NAME
INTEGER DAY
CHARACTER (LEN=3) MONTH
INTEGER :: YEAR = 1999 ! 年份这个成员有缺省初始值
END TYPE REPORT
而下面语句中,显式的初始化覆盖了NOV_REPORT中的YEAR成员。
TYPE(REPORT),PARAMETER :: NOV_REPORT=REPORT("Sales",15,"NOV",2001)
又如,下面用DATA语句进行显式初始化:
TYPE EMPLOYEE
INTEGER ID
CHARACTER(LEN=40) NAME
END TYPE EMPLOYEE
TYPE(EMPLOYEE) MAN_NAME,CON_NAME
DATA MAN_NAME/EMPLOYEE(417,'Henry Adams')/
DATA CON_NAME%ID,CON_NAME%NAME /891,"David James"/
c) 结构构造函数
F90有一种与结构有关的函数,称为结构构造函数,它用来给某结构类型中一个具体的变量赋值。定义了一个派生类型,实际上也同时建立了一个结构构造函数,这个函数名就是派生类型名,函数的自变量就是派生类型内各成员。只要派生类型一建立,就可直接调用这个结构构造函数。如果把实在的各成员值作为实元,与结构构造函数中自变量作哑实结合,其函数值就是被定义成派生类型的一个变量的值,可以直接赋给派生类型中的某一变量名。例如,对下面的派生类型:
TYPE FRIEND_TYPE
CHARACTER(LEN=20) :: NAME
INTEGER :: AGE
END TYPE FRIEND_TYPE
系统中就自动生成一个函数名叫FRIEND_TYPE的结构函数,该函数有两个哑元自变量:NAME、AGE。其完整的函数形式为:FRIEND_TYPE(NAME,AGE)。假设变量名MY_FRIEND被说明为FRIEND_TYPE类型,即程序中有说明语句:
TYPE(FRIEND_TYPE) :: MY_FRIEND, MY_BOY_FRIEND, MY_GIRL_FRIEND
于是可把‘Kong Ming’作为NAME的实元,25作为AGE的实元,在程序执行部分中调用结构构造函数,并把函数值赋给变量WHO,赋值语句为:MY_FRIEND=FRIEND_TYPE(‘Kong Ming’,25)。此时具有FRIEND_TYPE类型的变量MY_FRIEND就有了NAME成员值和AGE成员值。
调用结构构造函数作哑实结合时,只要保持类型、个数、位置一致,可以有多种形式的实元与哑元结合。譬如实元可以又是另一低层次结构的结构构造函数,或另一个结构的成员等。如回到前面较复杂的学生结构例中,设已定义派生类型 STUDENT_TYPE,并说明了变量名ZHANG_FEI是STUDENT_TYPE类型的,而后我们在执行部分中对ZHANG_FEI的成员CLASS通过如下赋值语句赋值:
ZHANG_FEI%CLASS=CLASS_TYPE(‘22th Department’,‘Applied Physics’)
这里引用CLASS_TYPE的结构构造函数,说明变量 ZHANG_FEI的班级是22系应用物理专业,也即ZHANG_FEI%CLASS已经有定义。因此接着可以把ZHANG_FEI%CLASS作为结构构造函数STUDENT_TYPE的实元,把整个函数值赋给变量ZHANG_FEI:
ZHANG_FEI=STUDENT_TYPE(0022123,ZHANG_FEI%CLASS,&
‘ZHANG_FEI’,‘He Fei’,SCORES_TYPE(82,73,90))
使用结构构造函数可以非常方便地给具有这种结构的变量赋值,如果若干变量形成一个数组,就可打印出一份详细的档案,并可按照需要排序及检索。
d) 应用
例:利用成员特征作排序检索。如果上面40个学生姓名和成绩已经读入,要查找名叫张飞的成绩并打印输出,并列出数学成绩在85分以上的名单。将前面的派生类型定义简化后为:
PROGRAM STUDENT_IO
TYPE SCORES_TYPE
INTEGER(1) :: MATH, PHYSICS, ENGLISH
END TYPE SCORES_TYPE
TYPE STUDENT_TYPE
CHARACTER(LEN=10) :: NAME
TYPE(SCORES_TYPE) :: SCORES
END TYPE STUDENT_TYPE
TYPE(STUDENT_TYPE),DIMENSION(40):: STUDENT
READ‘(A,3I3)’, STUDENT
DO I=1,40
IF(STUDENT(I)%NAME=‘Zhang Fei’) PRINT *,STUDENT(I)%SCORES
END DO
DO I=1,40
IF(STUDENT(I)%SCORES%MATH>=85) PRINT *,STUDENT(I)%NAME
END DO
END PROGRAM STUDENT_IO
例:建立一个计算机帐户的数据库,根据用户号码查找其帐上余额并显示。[e_432_01.f90]
例:建立一个学生的数据库,包含有学生姓名和考分的记录。从键盘上输入这些记录,并将它们放入一直接存取的文件中,要求可以显示、增加和修改记录。[e_432_02.f90]
【作业】
[4.1] 对任意读入的100个英文字母(可以从书中任选一段),计算字母e出现的频率,打印输出。
[4.1] 设机内已设有口令“physics”,编一程序,能提示用户键入口令字(Password),再把用户键入的口令与physics作比较,若符合则继续,否则提示“口令错误,重新键入”
[4.1] 利用派生类型编一数据结构程序,描述本班同学个人拥有的微机的硬件指标,包括:(购买者、购买价、购买日期)、(CPU时钟频率、CPU芯片厂家)、(硬盘容量、内存大小)、(显示器尺寸、显示器类型)、是否DVD光驱、微机品牌。按时钟频率及购买价排序并打印。