从本章开始,我们将赋予语言实质性的语义,也就是规定上章所描述的语言的每一个细节所具有的涵义。这种涵义与其说是我们对于一种语言细节的定义,不如说是算法的要求,要求语言具有足够多的细节,用来表达在算法当中有可能出现的精细情节。
对于任何的问题,站在计算机的角度来看,总是可以把它抽象为如下图所示的结构:
输入数据输出数据
因此要准备通过计算来解决一个问题,首先要作到的是把该问题所涉及到的数据整理好,也就是列出所有的数据,然后根据数据的数学属性进行分类,这个分类的过程就是对数据施加足够的标记的过程,将来把这些数据输入到计算机,计算机将能够依据这些标记,辨识出数据所应该具有的数学属性,从而施加相应的合法数学运算。
所以作为向计算机描述计算问题的FORTRAN语言,它首先要作到的是约定如何给数据施加足够详细的标记。
对这个标记过程的第一个要求是保证准确性,也就是说这个语言的标记系统必须正确地反映真实世界的问题里面,数据所具有的数学属性,因此这个标记系统必然是与数据的数学分类结构保持一致的。
从数学的观点来看,世界上的所有数据,总是可以被表示为整数,实数,复数等等基本的数据种类,因此本章的内容就是讨论:
● FORTRAN语言如何把数据归结为一些基本数据类型;
● 然后为了足够详尽地描述每一个数据类型的属性,FORTRAN是如何施加相应的标记的;
● FORTRAN语言对于这些标记(语法形式)所约定的语义是什么。
然后我们就可以知道,要想用FORTRAN来描述一个问题的算法,并进而以问题算法的FORTRAN语言版本为媒介,通过计算机来得到计算结果,第一个步骤,就是准备好数据的FORTRAN描述。
在上一章里,据称计算机能够使用语言,而且是非常类似于人类的语言,至少从形式上看很象,这难免会令某些人(特别是看过KUBRICK的影片《2001: A Space Odyssey》的观众们)感到恐惧:)
别怕!且先不讨论FORTRAN作为语言是否具有与人类语言等价的表达能力,至少从自然语言的语义学的角度来看,FORTRAN说出来的话其实是绝对空洞的,因为FORTRAN语言的全部语义基础就只是数据,而数据对机器而言,只是意味着经过编码的符号。
一台计算机其实是由以下6个部分组成:
● 数据------也就是基本数据元素以及数据结构;
● 基本操作------也就是一个对上述数据进行操作的基本操作集;
● 顺序控制------也就是一个控制针对数据的基本操作执行的时间顺序的机制;
● 数据存取------也就是一个如何给操作提供数据的机制;
● 存储管理------也就是一个数据存储分配机制;
● 操作环境------也就是一个支持程序和外部环境进行数据通讯的机制。
因此一台计算机
● 在程序的使用者看来,就是给它输入数据,它再给你加工过的结果数据;
● 在程序的编制者看来,就是把对数据的处理过程表示为计算机有限的一系列基本操作(指令)的集合,使得计算机能够处理相应的数据;
因此,计算机的一切可以说都是围绕着数据----如何表达数据,如何处理数据。而计算机语言所要具备的两个部分的功能,首先就是完备的描述数据的性质,然后就是描述数据的处理过程。
那么,什么是数据呢?
● 数据就是符号化了的信息!
对于计算机来说,任何信息都只有表示为符号,才能被认可;反过来说,计算机只能输入符号,而不会也不能理解符号的含义,它的能力只是体现在按照既定规则来处理符号。
然后,就是给出数据的表示,即如何用符号来明确而无歧义地表达数据。
要使得符号具备数据的含义,需要经过这么几个步骤:
● 处理符号的第一步:给符号分类,并给出描述符号性质的方法。
这个分类是人作为设计者给符号规定语义的第一步,因为对于人来说,数据不能只是符号,而是具有来自真实世界的语义,设计者正是根据符号的这种语义,制定相应的处理符号的规则,而计算机要想能够正确地处理符号,基本的前提,就是每当引入一个数据,都得由人向机器声明这个数据是什么类型,这个数据具备什么性质,而且假设计算机已经被引入处理该种数据类型的规则。
● 处理符号的第二步:区分常量与变量。
这一对范畴反映了最基本的人类抽象能力,也正是人类思维的基本模式。要想让机器模拟这种能力,最简单的做法就是:任何时候都必须首先声明,哪些符号表示常量,哪些符号表示变量,而变量相应的取值范围必须规定好,也就是说必须描述其取值为具有何种属性的常量值的集合;或至少已经被机器默认。
● 处理符号的第三步:给每一类数据规定相应的合法运算。
对于一种数据可以执行什么样的运算,来自于语言设计者对数据语义的规定,只有当运算被表示为相应的机器指令或指令集合,这时在表面看来,机器才开始真正“理解”了数据的“涵义”。
因此可以说,数据的定义构成了计算机的“灵魂”。
按照上面讨论的步骤,说明一个数据类型包括四个方面:
● 命名的语法
● 取值的范围与属性说明
● 该数据类型的常量的表示方法
● 定义合法的运算
因此相应的一个数据类型的四个要素就是:
(1) 名字;
(2) 值的集合;
(3) 表示值(相应的常量)的方法;
(4) 操作值的运算的集合。
对于这四个要素,FORTRAN一方面要约定它们的语法形式,从而可以构成符号描述的唯一性标记,保证相应的描述语句能够被FORTRAN编译系统无歧义的辨识,另一方面就是要给出这些语法形式所对应的含义。
真实世界的数据显然是多种多样的,几匹马,轴承的内径,圆周长与直径的比值,电子的波函数,非各向同性电介质的电极化率,10个被试每日的最高血压等等,这些数据都具有非常不同的形式与性质,如果我们每针对一种数据形式,都把它定义为某一种新的数据类型,则肯定是烦不胜举,因此合理的途径是找到一种统一的数据描述方式,而对于科学计算问题来说,自然的数据分类方式是数学对数值数据的分类,再加上非数值型数据,会是非常适合于科学计算的数据表达方式。
当然,如果是以描述其他类型的问题为目的,如事务处理,符号演算等,则选用另外的数据分类方式会更有效。
至少从数学的观点来看,我们常常需要处理的数据,都可以表示为一些基本数据类型的组合,例如我们知道向量实际上就是一个数组,数组的每个元素为标量,因此应用数组这种结构,就可以自然地表示向量,另外复数尽管也可表示为一个二元数组,但是这种二元数组的乘法不同于二维向量,因此为了避免这种歧义, FORTRAN把复数当成一个基本数据类型。而几种基本标量里面,整数和实数都同样必须构成基本的数据类型。由此可以建立FORTRAN的对数据的类似描述。
由于真实世界问题的要求的不同,对数据的描述也有程度不等的情况,最基本的情况就是直接说明数据的类型以及其他属性,又由于数据表示的实现具有一个重要的参数,即存储空间,所以当问题要求的数据,不能满足于默认的存储空间的时候,这时,就需要进一步给出数据的种别参数,这是更加详细的数据描述。如果在真实世界问题当中出现的的数据对象,干脆不符合已有的固有数据类型以及数组的定义,这时就还需要根据用户的要求构造一个依赖于问题的特定的数据结构,这就是数据描述时会遇到的第三种情况。
对于这三种情况,FORTRAN的解决方式如下:
第一种情况:
确定数据的类型以及相应的可能具有的属性。
首先,FORTRAN所能辨识的数据类型首先分为两大类:
● 固有数据类型
● 派生数据类型
根据语义上的基本差别,数据首先具有一些基本的类型,这些基本类型一般是和构成真实世界里的信息的那些基本元素相对应,比方说数字,字符等。然后其他情况下遇到的数据都可以由这些基本数据类型组合得到。不过一种语言具体的规定哪些基本的数据类型,往往受到该种语言主要应用的场合的影响,由于FORTRAN主要用来进行科学计算,因此它所定义的基本数据类型,正是与我们在科学计算问题当中遇到的数据类型相契合的。
● 所谓固有类型,是FORTRAN语言所定义的最基本的数据类型,每一种固有类型是和该种数据类型相应的各种运算一起隐式定义的,也就是说一旦声明引入某种固有数据类型,则系统总是默认为对它进行相应的运算是合法的,并且总是可访问的。
这样就做到了每种数据类型都和它相应的运算捆绑在一起,使得问题的描述非常自然。
● 固有类型包括五种:
整型(INTEGER)、实型(REAL)、复型(COMPLEX)、逻辑型(LOGICAL)和
字符型(CHARACTER)。
这个分类完全是遵循数据的数学分类,即整型指整数,实型指实数,复型指复数,逻辑型指逻辑值,字符型则是语言的基本元素。这样就可以把基本的数学语言一一对应的直接翻译为FORTRAN语言。
● 所谓派生类型是由用户定义的,非隐式定义的类型,只要用一个类型定义来声明其成员是何种固有类型,或者是何种其它已经定义过的派生类型,就能够被FORTRAN认可为一种数据类型。
由于派生数据类型正是由固有数据类型充当成员而构成的,因此在结构关系上,可以把固有数据类型看成原子,而把派生数据类型看成分子。由于语言的根本目的就是为描述算法服务的,因此从这个角度出发,派生数据类型本质上体现了非常重要的数据抽象与数组合的思想,由于我们需要运用语言来描述的问题是开放性,我们很难划定需要FORTRAN来描述的问题的范围,因此通过构造派生数据类型,使得我们可以很自然而简洁地建立新的数据类型。这是FORTRAN在FORTRAN77标准之后的一个重大进步。派生类型数据最重要的用途就是扩充了数组这种重要的数据结构,由于数组在科学计算领域,是一种极端重要的数据结构类型,FORTRAN除了能够直接描述数组,同时还能描述更为广泛的派生数据类型,也就可以直接对一个数据集合的各个成员同时施加运算,拥有了这种自然的数据类型,就避免了象FORTRAN的早期版本那样,需要通过特别设计的算法来实现这种运算。
所谓固有数据类型的固有,对于FORTRAN来说,就是为每一种固有数据类型规定了它的存储模式。
在FORTRAN77及其之前的标准里,整型,实型,逻辑型数据都是使用了一个数值存储单元,而复型和双精度数据则使用了两个数值存储单元,字符型数据使用一个字符存储单元。由于存储模式是非常底层的语言实现结构,因此FORTRAN后续的标准要想保持兼容,只有继承这个约定。
因此在FORTRAN90与95当中,默认的整型,实型,逻辑型数据都是使用了一个数值存储单元,而默认的复型和双精度数据则使用了两个数值存储单元,默认的字符型数据使用一个字符存储单元。而作为语言的一个发展,在FORTRAN90之后的标准里,开始允许在一个程序单元内,由用户定义特定的不依赖于固有数据存储模式的数据类型,这就是第6章的派生数据类型。
建立一种数据类型,最大的好处就是可以把相关的运算和数据捆绑在一起,对于一个特定问题当中的数据对象,是否应该被明确地看成数据类型,属于语言设计的权宜,因为建立一种数据类型所能带来的好处可以用算法来补偿,而FORTRAN77之后的版本的选择是增加派生数据类型,这样使得我们可以在进行科学计算时,有更为自然的描述方式。
数据类型的全部分类总结如下:
固有数据类型
数值型数据
整型
实型
复型
非数值型数据
逻辑型
字符型
派生数据类型
就数据的属性而言,类型当然是最重要的属性了。在指定类型之后,紧接着的就是根据实际情况,看需要描述的数据是否还具有其他需要说明的属性。
对于数组来说,具有一个基本的属性,就是数组的大小,相应的就是如何指定数组的存储空间的大小的问题。
由于FORTRAN具备可分配数组与指针的功能,因此在程序开头并不一定需要指定数组的大小(维度),在程序执行过程当中,数组的大小会作为输入或计算结果被读入,这个指标可以针对具体的问题的要求,以及运行的状况而定。在FORTRAN还不具备这种动态功能的时候,就需要在数组声明里指定数组的维度,而在事先又很难准确预料程序运行过程当中对数组储存空间的要求,因此如果指定的数组过大,就会大量地浪费当时非常宝贵的内存空间,而如果指定的数据组过小,则肯定会在程序运行过程当中导致错误。
所以为了避免这些问题,现在一般不会在数据声明的时候精确指定数组大小,而是把数组处理成一个动态对象,从而有效地回避了这个问题。
数据的一个重要属性,就是它的可访问性。在FORTRAN语言里,模块提供了对数据的访问控制。任何数据对象只要想把自己局限在模块内使用,模块就能够提供足够的保护,使得外部程序无法访问该数据对象。模块的这个功能使得FORTRAN成为一种安全可靠的语言。
是否打开数据的其他许多具体的属性,取决于具体的数据应用环境,因此要讨论数据的每一种属性,在这里不太现实,我们只有等到下面具体地说明每一种声明语句时再具体说明,因为属性指定总是在出现在声明语句当中。
第二种情况:
指定固有数据类型的种别参数。
对于计算机来说,在确定数据的类型,从而可以引导到相应的运算之后,进一步就需要为数据在内存指定存储位置和存储空间,实际上对于冯纽曼型计算机来说,这个步骤是非常关键的,因为冯纽曼型计算机的要点,就是硬件之外的一切,都必须表示为数据,都必须存储在内存当中,然后在程序的运行当中,随时与CPU进行通讯,因此在程序的开头就明确数据在内存当中的位置与每个数据所占有空间的大小,是保证程序运行非常基本的要求。
● 用来指定程序当中需要使用的每一种固有数据类型所要求占据的内存空间大小的属性由种别参数表示。给这个变量(参数)指定一个数值,就可以说明数据所需要的存储空间的大小,也就是程序允许的数值数据的位数和字符串的字符数目。
● KIND(种类种别参数)分别说明整数类型的十进指数范围,实数类型和复数类型的十进制精度和指数范围,字符类型和逻辑类型的表示方法。
● LEN(长度种别参数)对字符类型规定了字符的个数。
【例5-1】
REAL(KIND=3)::ABC,X,LONG
CHARACTER(LEN=40,KIND=GREECE)::NAME
具体的种别参数的约定是与语言的具体实现相关的,因此具体的取值还是得参考编译器的文档。
● 如果没有声明数据的种别,那么程序就会采用默认的参数,由于FORTRAN的早期版本没有引入种别参数,因此对于有不同精度要求的实型变量,直接采用了两种不同的数据类型,这就是REAL和DOUBLE PRECISION,从FORTRAN90以来的版本里,通过引入种别参数,对种别参数的不同取值,就足够表达不同的精度,而同时为了保证和早期版本的兼容,单独的数据类型声明DOUBLE PRECISION还是被保存下来了,这样一来,就产生了一个有一定任意性的后果,即新的语言标准对不同精度的实型数据,可以通过使用同一个数据类型的不同的种别参数值来表示,而同时用DOUBLE PRECISION作为数据声明也是有效的,这样就保证了源码向前的兼容性,却不具备向后的兼容性。
● 对于在指定种别参数的数值时,一般是以字为单位,这样对于字长不同的机器而言,相互之间就会出现程序移植的困难,下面分情况予以说明:
● 实型----由于DOUBLE PRECISION是属于老式标准的遗留物,因此使用DOUBLE PRECISION作为数据声明的程序就不具备良好的可移植性,因为所谓双精度是针对具体的机器的字长而言的,对于32位的机器,双精度就是64位,而对于64位机器,双精度就意味着128位,这样在不同字长的机器环境里,双精度就具有不同的位数,使得程序无法在不同字长的平台之间进行直接的移植。因此在这种情况下,最好还是统一使用REAL的种别参数来表达算法所要求的实数精度。可以说种别参数一劳永逸地解决了实数精度的可移植性问题。
● 复型----由于所谓复型本质上就是由两个实数表达的,因此按道理复型同样应该能够具有表达多种精度的能力,而实际上早期的版本在这方面是有欠缺的,不过随着FORTRAN90引入种别参数,就可以在COMPLEX的声明语句里通过运用种别参数来实现多种精度的表达,对于任何FORTRAN的实现,至少能表达两种精度,而一般来说是多于两种的。
● 字符型----对于字符,一般的机器都是用单字节8bits来表示一个字符,这样就可以总共表示28=256个不同的字符,这对于任何以字母写出来的语言都是足够的了,不过对于汉语,日语这样一些语言就不够用了,一般得需要双字节,即16bits,这样就可以表达216个字符。因此字符型数据同样需要附加种别参数,以便除了使用默认的基本字符之外,还可以使用辅助字符集里的字符,从而实现程序的本地化。不过某个具体的编译器是否支持双字节字符,必须参考相应的手册。因为FORTRAN 95标准也没有强制要求FORTRAN的任何实现都必须支持双字节字符。
● 逻辑型----由于一切逻辑型数据都只有两个值,因此如何确定逻辑型数据的存储空间应该是非常好办的,不过不幸的是,FORTRAN的早期版本规定逻辑型数据使用和实型数据一样大小的机器存储单位,这样当机器的字长很大时,就会非常的浪费机器的存储空间。因此到了FORTRAN90和FORTRAN 95,除了作为默认的情形,和旧的语言标准保持兼容之外,还可以通过指定种别参数,使得逻辑型数据的存储空间大小只有一个字,甚至一个bit。当然具体的使用方法需要参考相应编译器的说明。
● 整型----显然在程序应用当中会出现几乎任何大小的整型数据,因此无法在语言标准里面统一的规定整型数据的存储空间大小,这就同样需要依靠种别参数来指定应该给具体问题当中的整型数据确定多大的存储空间。具体地指定方式属于编译器设计者的选择,需要参考相应编译器的语言说明。
第三种情况:
派生数据类型。
数据的本义就是对真实世界里的事物的描述。这种描述可以是简单的,如一个标量,也可以是复杂的,如一个张量,对于更复杂的对象,在自然语言里有一种自然的描述方法,就是使用一系列的词汇,每个词汇都是对象在某个方面的属性的度量;在计算机语言里,可以采用类似的解决方案,即把对象的每一个需要描述的性质用一个适当的基本数据类型来表示,这样用一组基本数据类型就可以描述该对象。而这一组数据可以看成是一个新的数据类型,表示了一个变量。
这样构造出来的数据类型称为派生数据类型,和固有数据类型一样,在声明派生数据类型时,需要给出名称,描述它的每一个元素的固有数据类型以及相应属性和种别参数(如果非默认的话),当然也需要适当地定义其运算。
既然这种派生数据类型是由一组数组成,就会出现两种情况:
● 这组数据都是属于一个数据类型
这样构成的派生数据类型就是数组,显然对于数组的元素的描述就可以统一进行。具体的用法会在后面专门说明。
● 这组数据的各个元素属于不同的数据类型
这样构成的派生数据类型称为结构,这时就需要对每个数据元素进行分别的说明, 即每一个元素的数据类型,可能有的属性,种别参数等等。
上面对派生数据类型的描述实际上是递归式的,即一个派生数据类型的元素同样可以是另一种派生数据类型,而没有限定必须是固有数据类型。
【例5-2】 下面是一个典型的派生数据类型。
TYPE SAMPLE
REAL CURRENT
COMPLEX (KIND = QUAD) PHASE
CHARACTER (LEN = 50) SOURCE
END TYPE SAMPLE
TYPE (SAMPLE) SI401,SI402,SI403,SI404
在上面的例子里,首先定义了一个名称为SAMPLE的数据类型,每一个SAMPLE类型的数据由三个分量组成,它们的名称分别为CURRENT,PHASE,SOURCE,分别属于实型,复型和字符型,其中复型和字符型还分别说明了种别参数和字符长度属性,然后给出了程序当中需要使用的四个属于该种数据类型的变量:SI401,SI402,SI403,SI404。
上面例子当中派生数据类型的定义,以TYPE开始,以END TYPE结束。
对于计算机来说,数据分类的第一个反应就是针对不同类型的数据约定不同的存储模式。
由于存储模式的规定涉及到编译环境的设置,因此存储模式的约定是与系统环境相关的,鉴于Compaq Visual Fortran的广泛应用,本节特别针对Compaq Visual Fortran系统而言的说明了数据的各种存储模式。
下表5-1列出了Compaq Visual Fortran所有的固有数据类型的存储空间要求,和相应的能够在这个空间里表达的数据规模。
表5-1 固有数据类型的存储模式:
数据类型 |
单位存储空间 |
能表示的数据规模 |
BYTE |
1 byte (8 bits) |
BYTE表示等价于INTEGER(1)的带符号的整型数据类型。 |
INTEGER |
参见INTEGER(2), INTEGER(4), 以及 INTEGER(8). |
带符号的整型数据, 包括INTEGER(2), INTEGER(4), or INTEGER(8)。数据规模由编译器选项/integer_size:nn 控制。默认的规模控制选项为/integer_size:32 (等价于INTEGER(4))。 |
INTEGER(1) |
1 byte (8 bits) |
从-128到127带符号的整数。 |
INTEGER(2) |
2 bytes (16 bits) |
从-32,768到32,767带符号的整数。 |
INTEGER(4) |
4 bytes (32 bits) |
从-2,147,483,648到2,147,483,647带符号的整型数据。 |
INTEGER(8) |
8 bytes (64 bits) |
从-9,223,372,036,854,775,808到9,223,372,036,854,775,807带符号的整型数据。 |
REAL(4) |
4 bytes (32 bits) |
从1.17549435E-38到 3.40282347E38的按照IEEE S_floating格式的单精度实型浮点值。在1.17549429E-38和1.40129846E-45之间的值是非常态的。 |
REAL(8) |
8 bytes (64 bits) |
从2.2250738585072013D-308到1.7976931348623158D308的按照IEEE T_floating格式的双精度实型浮点值。在2.2250738585072008D-308和4.94065645841246544D-324之间的值是非常态的。 |
COMPLEX(4) |
8 bytes (64 bits) |
由一对从1.17549435E-38到 3.40282347E38的按照IEEE S_floating格式的单精度实型浮点值组成的单精度复型浮点值。在1.17549429E-38和1.40129846E-45之间的值是非常态的。 |
COMPLEX(8) |
16 bytes (128 bits) |
由一对从2.2250738585072013D-308到1.7976931348623158D308的按照IEEE T_floating格式的双精度实型浮点值组成的双精度复型浮点值。在2.2250738585072008D-308和4.94065645841246544D-324之间的值是非常态的。 |
LOGICAL |
参见LOGICAL(2), LOGICAL(4), 以及LOGICAL(8). |
逻辑型值, 包括LOGICAL(2), LOGICAL(4),以及 LOGICAL(8). 数据规模由编译器选项/integer_size:nn 控制。默认的规模控制选项为/integer_size:32 (等价于LOGICAL(4))。 |
LOGICAL(1) |
1 byte (8 bits) |
逻辑型值.TRUE. 或.FALSE. |
LOGICAL(2) |
2 bytes (16 bits) |
逻辑型值.TRUE. 或.FALSE. |
LOGICAL(4) |
4 bytes (32 bits) |
逻辑型值.TRUE. 或.FALSE. |
LOGICAL(8) |
8 bytes (64 bits) |
逻辑型值.TRUE. 或.FALSE. |
CHARACTER |
每个字符1 byte (8 bits) |
根据约定的字符编码表示的字符数据,通过字符数据的声明形式:CHARACTER(LEN=n)或 CHARACTER*n,其中n 表示byte数,来表示数据规模。 |
HOLLERITH |
每个Hollerith 字符1 byte (8 bits) |
Hollerith 常量。 |
表中的INTEGER(4)等价于INTEGER(KIND=4)以及INTEGER*4.
一个数据如何才是被完备描述了,以及FORTRAN所要求的描述一个数据的要素是哪些,是一个问题的两面。这个问题对于程序的作者是很重要的,因为FORTRAN现在允许用户自己定义合乎自己需要的派生数据类型,这就要求我们知道一个派生数据类型的定义是否完备。
FORTRAN的数据类型必须包含如下四个部分:
● 数据类型的名称
● 数据取值的集合
● 可以施加于数据的值的运算
● 该数据类型的常量的表示形式
要能够说明数据的类型所属,首先每种数据类型本身得有个名称,才能在描述数据对象的时候,说某个数据对象属于某个数据类型。
固有数据类型就只有5种,它们的名称:INTEGER,REAL,COMPLEX,LOGICAL,CHARACTER是语言标准的规定。
但派生数据类型则完全是程序作者自定义的,因此必须由作者使用TYPE来给出其构造的派生数据类型的名称,也就是说只要一个数据或一个变量的取值符合TYPE与END TYPE之间的定义,就被该程序单元识别为属于该数据类型,就可以应用相应的运算。
如果一个程序单元里出现的数据不能被识别为该程序单元的数据声明里的诸种类型,那么FORTRAN还会尝试运用一种方式来试图确定它的数据类型,就是根据数据名称的第一个字符来进行判别,这种方式属于FORTRAN的古老传统,因为早期FORTRAN所处理的数据类型比较单纯,顾可以如此简化处理,FORTRAN90与FORTRAN 95都继承了这点。
对于每种数据类型,存在一个允许的具体取值的集合。而属于该数据类型的变量的取值范围必定是在这个集合内。
表面看起来数据类型的取值集合都是明确的数学意义,但是由于本质上计算机的任何具体取值,都必须是有限的,因此数据类型表面的所谓数学涵义并不是很符合实际的。固然整型必定是取整数值,但只能取有限的整数值,而且这个值还有上限,即一个整型数据能够取多大的整数不仅在机器的硬件方面有制约,在语言的具体实现上也进行了约束。
同样,对于实型来说,更不可能就是和实数集合等价,实型数据的具体取值同样只能取可有限表示的实数,即有限小数。至于某些软件(如MATHEMATICA)声称可以精确的引用无理常数,例如欧拉常数,实际上是使用了一个收敛级数来表达无理常数,只有当用户指定有理表示的精度后,计算机才对级数做相应的截断,给出相应精度的有理表示,而并不是说该常数的无限位表示完全存储在计算机里面。
● 逻辑型数据能够取得的值的个数是完全确定的,即仅有真和假两个值(即两个元素)。由此可见所有逻辑型变量都是某种判断,而对该判断的取值只能或真或假,这里实际上就规定了FORTRAN语言只能用来表述满足排中律的数学。
● 对于整型和实型来说,既然只可能取有限值,那么剩下的问题就是如何给某个具体取值分配存储空间了,由于程序单元是根据数据声明当中对数据取值的规划来确定如何为数值分配存储空间的, 因此对于具有极大处理能力的现代计算机而言,最好针对数值占用空间的大小进行分级,以做到在保证数值表达需求的前提下,尽量避免存储空间的浪费。FORTRAN为了给数值占用空间的大小分级,引入了种别参数(K1ND),使得在数据声明的时候,就可以一致地规定该类数据在表达时,允许占用空间的大小。
例如整型除了默认表示之外,还可以标志以种别参数“SHORT”,这个参数意味着在整型的默认取值范围了划出了一个子集,只要是属于这个子集的数据,允许系统给它分配较为小的,但更为合算的存储空间。
对于实型来所,则完全可以根据算法的需要,在开始的数据声明里,就给程序单元里可能出现的数据划出三流九等,使得程序对存储空间的占用更为合理。当然FORTRAN语言标准只是规定了实型必须至少在默认精度种别之外,还需要有一个双精度种别,而在FORTRAN的各种编译实现里,还可以规定更多的精度种别。
● 对于字符型数据来说,它的存储空间完全和字符串长度成正比,因此只要直接规定字符串的字符个数,就可以一致地得到其存储空间分配标准。
● 至于复型和派生类型,则完全以其他数据类型作为成员,自身没有什么特别的规定,因此也就没有独特的针对这两种数据类型的种别参数。
显然,FORTRAN通过运用种别参数来明确地规定数据的表示,使得Fortran的标准化程度得到了进一步提高,从而提高了程序的可移植性。
允许施加于数据的运算同样可以分为两类,即与固有数据类型相应的固有运算,还有自定义运算。由于在FORTRAN里面,运算的主要语法功能是构成表达式,因此详细的关于运算的讨论,参见有关表达式的章节。
固有运算就是固有数据类型在FORTRAN里面指定了表示符从而可以直接引用的那些固有运算,根据运算所能施加的算元据的不同,一共分为四类:
1. 算术运算
2. 串联运算
3. 关系运算
4. 逻辑运算
简述如下:
● 算术运算
针对三种数值型数据,可以直接引用7种固有的算术运算:
● 2种一元运算:
求反运算,其运算符为-;
求同运算,其运算符为+。
这两种一元运算可以施加于任意数值型数据和种别参数的组合,其运算结果的数据类型与种别参数和算元的数据类型和种别参数保持一致。
● 5种二元运算是:
加法运算,其运算符为+;
减法运算,其运算符为-;
乘法运算,其运算符为*;
除法运算,其运算符为/;
乘幂运算,其运算符为**。
这5种运算的两个算元可以是数值型数据的任意数据类型与任意种别参数的任意组合。
如果参与运算的两个算元不是同一个类型或种别参数不同,那么FORTRAN如何决定结果的数据类型或种别参数呢?基本的原则就是向需要存储空间大的操作数看齐,以免损失算元的信息。具体地说,就是:
● 若两个算元是相同类型和相同种别参数,则运算结果的类型与种别参数就是算元的类型与种别参数。
● 若两个算元都是整型但种别参数不同,则运算结果的种别参数是取十进制幂范围大的那个算元的种别参数;若范围一样大,则由系统决定。
● 当一个算元是整型、另一算元是实型或复型,则运算结果的种别参数就取那个实型或复型的算元的种别参数。
● 若两个算元属于不同种别参数的实型或复型数据,则运算结果的种别参数取十进制精度高的那个算元的种别参数;若精度一样,则由系统决定取舍。
规定了运算结果的属性,具体的值就是通常的算术运算的结果,即
● 加法为两个算元之和;
● 减法为两个算元之差;
● 乘法为两个算元之积;
● 除法为两个算元之商,如果两个算元都是整型数据,它们相除时称为整除,其结果商就是首先进行算术上的除法运算,得到的商去掉小数部分,取得的整数值即为整除的结果。这是为了满足上面关于保持类型一致性的规则。
例如:99/100的值为0;(-99)/100的值为0;58/3的值为19;(-58)/3的值为-19。
● 乘幂为以第一个算元为底,第二个算元为指数的乘幂值。
● 串联运算
针对相同种别参数的字符型数据定义了串联运算,其运算符是//。
串联运算的结果为保持种别参数不变的字符型数据。运算结果的值为第一算元的字符值,在右边紧接第二个算元的字符值。
例如:ABC//RTY的值为ABCRTY
● 关系运算
关系运算是分别针对整型、实型、复型和字符型数据来定义的二元运算。
关系运算的结果为逻辑型数据,即只能取.TRUE.和.FALSE.两个值之一。
FORTRAN 95定义了六种固有关系运算,这六种固有关系运算根据其可以施加的操作数的不同,又可以分为两类:
可以施加于除复型之外的数值类型,种别参数以及字符型的第一类:
● 大于,其运算符为.GT.,或>;
● 大于等于,其运算符为.GE.,或>=;
● 小于,其运算符为.LT.,或<;
● 小于等于,其运算符为.LE.,或<=;
可以施加于所有数值型与字符型的第二类:
● 等于,其运算符为.EQ.,或==;
● 不等于,其运算符为.NE.,或/=。
对于数值型数据来说,关系运算具有通常的涵义,并且两个算元可以是任意的数值型类型与任意种别参数的组合。当然只有复型不能比较大小,而只能比较是否相等。
对于字符数据来说,关系运算具有独特的涵义。
首先要求两个算元具有相同的种别类型参数,但是可以具有任意的长度。其关系运算的执行可以理解为执行下列几个步骤:
(1)首先使两个作为算元的字符串的字符长度变为一致,如果相对来说有个字符的长度较短,就在右边以空格字符填充,直到两个算元长度相同为止。
(2)然后对两个算元按字符位置从左边第一个字符开始逐个进行比较判别,直到足够判别关系是否成立为止。
(3)而字符的比较是按字符在字符集中排列序列的位置的先后来进行的:
● 若字符1在字符2之前,则认为满足小于关系,小于等于关系和不等于关系;
● 若字符1在字符2之后,则认为满足大于关系,大于等于关系和不等于关系;
● 如果位置相同,即为同一个字符,则认为满足等于关系。
● 所有空串都是相等的。
● 等于关系和不等于关系的运算结果与字符集序列无关,而其它四种关系的运算结果是依赖于字符集排列序列的。由于ASCII的排列序列对于任何系统都是一致的,所以一般而言可移植性是能得到保证的。
● 如果参与运算的默认字符数据值全是字母或全是数字,则按语言的规定,其顺序是严格确定的;
● 如果参与运算的默认字符数据值参杂了字母与数字,则把其中的数字看成字符,而排序则依赖于系统的具体规定。所以在使用时要注意这点。
● 如果参与运算的字符数据值包含了非默认的字符型数据,则同样依赖于系统的规定。
● 逻辑运算
针对任意种别参数的逻辑型数据定义五种逻辑运算。
我们知道数值型数据和字符型数据进行关系运算后的结果是逻辑型数据,此外还 可以根据算法的需要自定义逻辑型数据,逻辑运算就是施加于逻辑型数据,而得到逻辑型数据值的运算。
根据算元的数目,逻辑运算包含两种,其中一元运算为:
● 非运算,运算符为.NOT.;
非运算的运算结果定义如下表5-2:
表5-2非运算的运算结果
.NOT.的算元 |
.TRUE. |
.FALSE. |
.NOT.的运算值 |
.FALSE. |
.TRUE. |
二元运算包括:
· 与运算,运算符为.AND.;
· 或运算,运算符为.OR.;
· 逻辑等价运算,运算符为.EQV.;
· 逻辑不等价运算,运算符为.NEQV.;
各运算的结果定义如下列各表:
表5-3 与运算的运算
A.AND.B |
A |
B |
|
|
.TRUE. |
.FALSE. |
|||
A |
.TRUE. |
.TRUE. |
.FALSE. |
|
B |
.FALSE. |
.FALSE. |
.FALSE. |
|
表5-4 或运算的运算
A.OR.B |
A |
B |
|
.TRUE. |
.FALSE. |
||
A |
.TRUE. |
.TRUE. |
.TRUE. |
B |
.FALSE. |
.TRUE. |
.FALSE. |
表5-5 逻辑等价运算的运算
A.EQV.B |
A |
B |
||
.TRUE. |
.FALSE. |
|
||
A |
.TRUE. |
.TRUE. |
.FALSE. |
|
B |
.FALSE. |
.FALSE. |
.TRUE. |
表5-6 逻辑不等价运算的运算
A.NEQV.B |
A |
B |
|
.TRUE. |
.FALSE. |
||
A |
.TRUE. |
.FALSE. |
.TRUE. |
B |
.FALSE. |
.TRUE. |
.FALSE. |
逻辑运算的结果的种别参数的约定:
● 当两个算元的种别参数相同时,则结果的种别参数与算元的相同;
● 当两个算元的种别参数不同时,则结果的种别参数依赖于系统的约定。
由于上面列出的固有运算,并不能满足我们在构造表达式时对运算的全部需求。显然,要使得语言具有开放性,就不可能期望通过指定有限的对象来概括任意需求,因此必然需要制定一个构造规则,以便允许程序作者自定义运算。所谓自定义运算就是需要程序作者根据算法的需要自己来定义的运算。
从语法的角度来讲,一个运算的定义包括三个部分:
● 符号的表示;
所谓符号的表示就是给出运算的名称,命名规则为一个字符串的左右分别加一个小数点(句点)。
【例5-3】
.REMAINDER.
.REVERSE.
.INTEGRAL.
固有运算的表示符号除了通常的数学表示符号之外,同时还有一套等价的字符串加左右句点的表示方法,这就和自定义运算的符号表示统一起来了。这样做的好处就是可以用符号串直接作为文字来表示运算的涵义,(例如上面的三个名称就可以用来表示求余,反号,求积分这三种运算),从而便于程序的写作和阅读。这是一个值得遵循的良好的写作风格。
● 运用固有运算的组合给出的自定义运算的定义; 自定义运算的定义是通过函数用OPERATOR来完成的,具体的说明见有关过程的章节。
● 自定义运算的算元集合的描述。 数学上定义一个函数,必定要指出函数的定义域,同样一种自定义的运算也需要指定能够施加于其上的算元的范围,这里包括如下几种情况:
· 定义在某个固有数据类型的真子集上;
如果对一个固有运算也做这样的限制,那么就把这个固有运算看成自定义运算了。
· 定义在不止一个固有数据类型上,例如数值型数据和字符型数据的某种组合上;
可以针对某个固有运算做这样的扩展,同样视之为自定义运算。
· 定义在派生数据类型上;
这样得首先定义该派生数据类型。
· 定义在上述任意情形的组合而成的集合上。
数据在程序当中的行为,除了以指定数据类型的变量形式出现之外,还有就是以常量形式出现,也就是给出某个数据类型的具体取值的形式。
因此对于数据类型的说明,还包括给出该数据类型的常量的书写语法。
【例5-4】 下面给出每一种数据类型的说明常量的例子:
例子 数据类型 例子的取值 |
345 INTEGER 345 713.2或7.132E2 REAL 713.2 (2.77,5.38) COMPLEX 2.77+5.38i .TRUE. LOGICAL TRUE “SPACE_A” CHARACTER SPACE_A SAMPLE(1.582,(3.2,5.5),”CHENG”) 派生类型 SAMPLE(1.582,3.2+5.5i,”CHENG”) |
可以看出,对于数值型数据,直接给出常量数据,就可以了,而对于字符型数据则需要写在定界符里面,对于派生类型则需要遵循派生数据类型的说明语法。
当然一个数据类型里的常量同样具有种别参数的属性,只要给出的常量不是属于默认的种别,就需要给常量加上种别参数,而种别参数有两种情况:
● 一种是采用整数,由于不同的编译器对于这些整数的具体解释有可能是不同的,因此会妨碍程序的可移植性;
● 一种是采用命名常量来作为种别参数,那么只要这些命名常量一直被使用,就能保证对它的解释的一致性。
【例5-5】
类型 例子 |
INTEGER 2_SHORT REAL 3.14159267895632_QUAD COMPLEX (3.14159_HIGH, 56.2) LOGICAL .TRUE._BYTE CHARACTER CHINESE_”例子” |
例子里SHORT,QUAD,HIGH,BYTE,CHINESE都是命名常量。
对于整型,实型,复型和逻辑型来说,种别参数写在数据的右边,以下划线隔开,而字符型则是写在数据的左边,同样以下划线隔开。
对于计算机来说,数据的意义无非就是要知道在存储空间为一个特定的数据划出多大的空间来装载它,然后才谈得上给每一个数据编制地址,从而随时可以对数据进行读入读出操作。
确定数据占用空间大小的自然方式就是统一地给一类数据指定固定的存储模式,这就是FORTRAN早期的做法,即整型,实型,逻辑型统一地用一个数值存储单位来存储,而双精度实型与复型则统一采用两个数值存储单位来存储,字符型数据则统一采用一个字符存储单位来存储。由于数值存储与字符存储具有不一样的情况,因此这两种存储单元的字节数大小不一样。
不过语言的进步,毋宁说是算法的进步,要求语言能够提供更加灵活的存储模式的可选择性,这就是FORTRAN90引进的种别参数,这样就扩充了固有数据类型的存储模式。同时为了使得程序能够与旧的标准兼容,一般采取在默认的情况下采取旧的存储模式,而需要扩充时,则额外加上种别参数。
数据的存储模式是通过对数据进行声明来指定的。详尽的声明语句的使用参见数据的声明,不过下面我们给出描述各个数据类型的四个基本属性的词法与句法,以备寻检。
何谓FORTRAN里的整数?
数学上的整数用整型数据来表示。而所谓整数,具体表示出来,在数学上一般的表示形式如下:
。
其中:
i 是一个任意的整数。
s 表示正负符号(可以取+1或-1)。
l 是一个正整数,表示i的位数,即表示i需要多少个数字。
r 是一个大于1的正整数,表示i的进制的基数,即逢r进一位的意思。
wk 是一个小于r的非负整数,表示了i的每一位的值。
例如一个形如-41的整数,也可以表示为二进制形式-0101001,因为我们有:
要完全的描述整数i,显然s,l,r,wk(k=1,2,…l)这些数值都是必须提供的。
例如给出十进制整数-41,实际上也就是提供了
s=-1;l=2;r=10;w1=1;w2=4
这一套完整的信息。
不过如果我们的目的是描述一个取整数值的变量n的数据类型,显然s和wk(k=1,2,…l)都无须给出,而整型数据的分类都是围绕l和r来进行的。
【例5-6】 下面的带种别参数的整型数据的声明语句:
INTEGER(4) i
实际上表示的是如下形式的整数:
也就是取l=31和r=2。
下面我们就给出说明整型数据的四个基本属性的方式与相关功能函数
整型的名称就是INTEGER。也可以说就是声明整型数据类型的语句的关键词。
声明一个数据对象属于整型数据的基本语句句法为:
INTEGER [ ( [ KIND = ] kind-parameter) ] [ [ , attribute-list] :: ] entity-list
【例5-7】 以下这些声明语句主要是要说明数据项:
INTEGER X
INTEGER DIMENSION(:), POINTER :: days, hours
INTEGER(SHORT)RED_BALL
INTEGER(2) POINTER :: k, limit
INTEGER(1) DIMENSION(10) :: min
【例5-8】 以下这些声明语句主要是要说明数据的属性:
INTEGER days, hours
INTEGER(2) k, limit
INTEGER(1) min
DIMENSION days(:), hours(:), min (10)
POINTER days, hours, k, limit
整型数据也可用于指出某个变量为整型也可以构成一个条件语句。
【例5-9】
INTEGER I, X
READ (*,*) I
IF (I) THEN
X = 1
END IF
取值为整数值的数据对象被定义为整型数据对象。
值得注意的是整型数据的取值范围,无论如何都只能是整数集合的一个真子集,因为计算机所能表示出来的整数的大小是受到一个有限数值的限制的。至于某一个具体的编译器能够表示的最大的数值是多少,并不是统一的,需要具体的依据编译器的约定,当然只有在我们需要考虑有可能取非常大的数值时,才会注意到系统的这方面的限制。
更具体地考虑一下,当我们要定义一个整型变量的时候,我们不止是需要考虑它是否可能取非常大的数值,更重要的是要在满足算法的数据精度要求与节约机器的内存空间之间取得某种折中。因为尽管语言标准只要求编译器提供整数的一种存储标准,但是现在一般的编译器都能提供多种存储模式,针对算法的具体情况,程序作者就可以选择不同的数据存储模式,来获得高效的程序。
FORTRAN语言可以很方便地描述数据对象的具体的存储模式,那就是种别参数,实际上对于FORTRAN 来说,每一个数据总是认为它不仅属于一个数据类型,进一步还属于该数据类型的某个种别,在种别参数没有表达出来的时候,就会赋予默认的种别参数值。
由于FORTRAN标准只是规范了固有数据类型的定义,对于每一个固有数据类型所定义的各个种别的具体存储模式,则一般由具体的编译系统来约定,因为给具有不同字长的数据规定适当的存储空间,得依据系统不同具体情况来确定。
FORTRAN提供了三个固有函数,可以用于查询具体的某个系统对整型数据的存储模式的设置:
· KIND
· RANGE
· SELECTED_INT_KIND
● 固有函数KIND
这个函数能够给出任意属于固有数据类型的数据对象的种别参数
句法:
result = KIND (x)
输入x,可以是任意属于固有数据类型的数据对象,如变量或常量。
输出为一个属于默认整型数据的标量,就是x的种别参数值。
【例5-10】
KIND (0)为默认整型的种别参数值;
KIND (12)同样为默认整型的种别参数值;
● 固有查询函数RANGE
这个函数能够给出数据用十进制表示时的幂次范围。
一般句法为:
result = RANGE (x)
输入x是数值型数据。
输出为一个属于默认整型数据的标量,表示数据用十进制表示时的幂次范围。
由于机器的存储模式是以二进制的位数为单位的,因此这个函数的作用就在于把二进
制的幂次范围转换为十进制的幂次范围。
对于一个整型变量,输出值为INT(LOG10( HUGE(x) ))。
对于一个实型或复型变量,输出值为INT(MIN (LOG10( HUGE(x) ),
-LOG10( TINY(x) ))).
【例5-11】
如果变量X属于REAL(4),那么RANGE (X)的值为37. (因为HUGE(X) = (1 - 2-24) x 2128
TINY(X) = 2-126,而;
)。
● 固有转换函数SELECTED_INT_KIND
这个函数能够根据数据取值的十进制幂次范围,给出其种别参数值。
一般句法为:
result = SELECTED_INT_KIND (l)
输入十进制最大幂次l。
输出满足-10l < n < 10l的变量n的种别参数值。
如果系统不支持相应的整型种别,就会返回数值-1。
如果该数据同时满足不止一个种别参数值,则取具有最小十进制幂次范围的种别参数
值。
由于很多情况下,我们只知道数据取值的十进制幂次范围,因此这个函数就提供了通
过十进制幂次范围来指定种别参数的方法。
【例5-12】
INTEGER (SELECTED_INT_KIND (6))X !等价于INTEGER(4)X,而X
!的取值最大可以达到106,而最小
!可以达到10-6。
INTEGER (SELECTED_INT_KIND (5)) N,M !声明了变量N和M的取
!值最大可以达到105,而最小可以达
!到10-5。
i = SELECTED_INT_KIND(8) ! 返回4
i = SELECTED_INT_KIND(3) ! 返回 2
i = SELECTED_INT_KIND(10) ! 返回-1, 因为系统不提供如此高
!的精度。
施加于整型数据的运算包括:
● 一元固有算术运算:求反运算-;求同运算+。
● 二元固有算术运算:加法运算+;减法运算-;乘法运算*;除法运算/;乘幂运算**。
● 二元固有关系运算:大于.GT.或>;大于等于.GE.或>=;小于.LT.或<;小于等于.LE.
或<=;等于.EQ.或==;不等于.NE.或/=。
固有算术运算的结果仍然为整型数据,而固有关系运算的结果为默认种别类型的逻辑型数据。
所谓常量,可以理解为取了具体的定值的数据对象,因此整型常量从形式上讲,就是一串数字,可能在前面(左端)加上正负号,也可能在后面(右端)加上下划线,然后跟一个种别参数。
整型字面常量的一般形式为(R403):
[s]n[n...][ _k]
其中:
s表示正负号;如果取负号(-),则这个负号是不可缺的,如果取正号(+),则是可选
的。因此不带任何符号的数字串被默认为正数。
n表示数字(0到9),从左端开始第一个非0数字开始,它左边的任何0都会被忽略。
在默认情形下,这些数字都被认为是十进制表示形式的数值。
k是一个可选的种别参数,必须用下划线( _ )和表示数据的数字串区分开。
种别参数的一般语法形式为:
digit-string
scalar-integer-constant-name
即数字串,或者是取整型标量值的命名常量。
【例5-13】
1表示INTEGER(1);
2表示INTEGER(2);
4表示INTEGER(4);
8表示INTEGER(8);
LONG
SHORT
其中1,2,4,8同样属于默认种别的整型数字,而LONG和SHORT都是命名常量,它们的具体取值完全依赖于编译系统。
在默认情形下,整型常量总是被解释为十进制表示的数值,除了这种默认的十进制表达形式之外,还可以在DATA语句当中初始化表示为其他系统许可的进制形式。
在FORTRAN语言标准里面,规定了十进制之外的三种进制形式:
● 二进制常量形式(R408):
B ’ digit [ digit ]…’
B “ digit [ digit ]…”
其中的数字只能是0或1.
即二进制常量表示为以字母B开头,后跟用一对撇号或引号括起来的数字串,而且每
个数字不是0就是l。
● 八进制常量形式(R409):
O ’ digit [ digit ]…’
O “ digit [ digit ]…”
其中的数字只能是0到7.
八进制常量表示为以字母O开头,后跟用一对撇号或引号括起来的数字串,而且每个
数字是0到7之间的一个数字。
● 十六进制常量形式(R410):
Z ’ digit [ digit ]…’
Z “ digit [ digit ]…”
其中的数字只能是0到9,和A到F这五个字母,用来表示10到15.
十六进制常量表示为以字母z开头,后服用一对撇号或引号括起来的数字或字母的串,
而且每个数字是数字0到9或字母A到F之一。
这里定义的二进制、八进制和十六进制的字面常量形式只能用于DATA语句中。
如果要在CVF系统里面使用非十进制来表示数值,除了属于FORTRAN标准的二进制,八进制,十六进制三种额外的进制形式之外,还可以使用如下的语法形式表示更多的进制形式:
[s] [[base] #] nnn...
其中:
base是从2到36的任意整数。这表明CVF可以使用从2进制一直到36进制来表
示整数。
而如果base省略了,但是给出了符号#,那么表示后面的整型数据被看成16进制,
如果base 和#都被省略了,那么后面的整型数据就被看成默认的10进制。
为什么取能够表示的最大进制是36呢,因为一般从英文26字母里面来取得
符号来表示0到9之外的数字表示符号,一般的约定就是按照字典顺序,例
如如果使用11进制,那么A表示10,如果使用36进制,那么A表示10,B
表示11,C表示12等等一直到Z表示35。
这种表示方法里面,字母的大小写是不加区别的。
【例5-14】 下面的7个变量所赋予的整型数值都是表示十进制的3,994,575:
I = 2#1111001111001111001111
m = 7#45644664
J = +8#17171717
K = #3CF3CF
n = +17#2DE110
L = 3994575
index = 36#2DM8F
【例5-15】 这些都是正确的整型常量;
0
-112
+43212
62_2
1992110235764803_8
31_SHORT
9999999999999999999_LONG
【例5-16】 这些都是错误的整型常量:
9999999999999999999 !对于默认的种别参数来说,这个数太大了。
3.14 !不允许出现小数点。
32,767 !不允许出现逗号。
33_3 !3不是一个有定义的种别参数。
【例5-17】 下面都是非十进制形式的整型常量:
FORTRAN赋值 十进制值 十六进制值 |
LOGICAL(1)X INTEGER(1)X |
X = -128 -128 Z'80' |
X = 127 127 Z'7F' |
X = 255 -1 Z'FF' |
X = 255 255 Z'FF' |
X = -32768 -32768 Z'8000' |
X = 32767 32767 Z'7FFF' |
X = 65535 -1 Z'FFFF' |
数字串后的下划线和种别参数是可选项。如果省略,就被认为是默认整型,这时系统默认的种别参数值就是固有查询函数KIND(0)的结果。
SHORT是命名常量,且具有常量值,这个值必须是非负的,而且与一种表示方法相对应。
非字面常量的数值数据都有一个名字,对于名字要说明其类型,具体说明的方法参见第6章。
何谓FORTRAN里的实数?
计算机显然也不能表示所有的实数,因为计算机只能表示有限形式的数值,并且表达一个数值所使用的数字数目也是受到限制的。
对于计算机来说,只能用下面的形式来表示实数:
。
其中:
● x 是一个实数。
● s 表示正负符号(可以取+1或-1)。
● r 是一个大于1的正整数,表示x的进制的基数,即逢r进一位的意思。
● l 是一个大于1的正整数,表示x的位数,即表示x需要多少个浮点数字来表示。
显然这依赖于系统对r的选择。
● wk是一个小于r的非负整数,表示了x的每一位的值,而且w1不能是0。
● e 为幂次范围emin 到emax 的一个整数。
● 如果x = 0,那么wk 和e定义为0。
从实型数据的数学表达式可以看出,如果要对实型数据的存储模式进行分类的话,在默认取十进制的情况下,就一定是围绕l和e来进行的,例如单精度实型(REAL(4))的定义如下:
x=0,或者
● 一个实型数据值最多可以用多少个浮点数字来表示,依赖于系统的约定,下表5-7
是CVF在不同环境里的相应约定。
表5-7实型数据值的浮点数字
IEEE S_floating |
24 |
Compaq (以前是DIGITAL) VAX F_floating 1 |
24 |
IEEE T_floating |
53 |
Compaq VAX D_floating 1 |
53 2 |
Compaq VAX G_floating 1 |
53 |
1仅限于VMS
|
|
● 幂次范围emin 到emax同样依赖于系统的约定,下表5-8是CVF在不同环境里的相
应约定:
表5-8幂次范围的浮点数字
|
emin |
emax |
IEEE S_floating |
-125 |
128 |
Compaq VAX F_floating 1 |
-127 |
127 |
IEEE T_floating |
-1021 |
1024 |
Compaq VAX D_floating 1 |
-127 |
127 |
Compaq VAX G_floating 1 |
-1023 |
1023 |
1VMS only |
|
|
实型数据的名称是REAL,不过实型数据还有一个种别,拥有单独的名称,即DOUBLE PRECISION,这属于历史的遗留,是在FORTRAN没有出现种别参数的时候所增加的双精度实型数据的名称,当然使用种别参数是完全可以代替使用这个名称。
实型数据的声明的语法形式如下:
REAL [ ( [KIND=] n) ] [ ,attribute-list::] entity-list
DOUBLE PRECISION [ ,attribute-list::] entity-list
其中:
n取值为种别参数4, 8, 或16。
种别参数16只出现在OpenVMS, Tru64 UNIX, 和Linux环境当中。
● 种别参数是可选的,如果使用了种别参数,那么给出的实型数据对象就属于相应
的种别;如果没有使用种别参数,那么给出的实型数据对象就属于默认实型。
● DOUBLE PRECISION实际上就是REAL(8)。如果使用DOUBLE PRECISION就不能再使用种别参数。
● 如果要改变实型的默认指定。可以使用编译选项/real_size:size。
给出实型数据对象的名称的例子如下。
【例5-18】
这些声明语句主要说明数据项。
REAL (KIND = high), OPTIONAL :: testval
REAL, SAVE :: a(10), b(20,30)
这些声明语句主要说明数据属性。
REAL (KIND = high) testval
REAL a(10), b(20,30)
OPTIONAL testval
SAVE a, b
鉴于FORTRAN的所谓实型只是有限位有理数的集合,因此同样有必要依据精度对实型数据进行分类,以避免给任意的实型数据安排同一个存储模式,从而有可能导致非常大的内存空间的浪费。
FORTRAN标准规定了实型至少要有两种精度形式,一种是默认实型,一种是在默认实型的基础上精度加倍,得到双精度实型。
在FORTRAN90以后,为了与早期的语言版本相容,保留了说明默认双精度实型的关键词DOUBLE PRECISION,不过可以使用REAL加双精度种别参数(REAL(8))的形式,或直接使用指数符D加指数的形式代替。
和整型类似,FORTRAN设置了四个固有函数,可以用来查询具体的某个系统对实型数据在精度方面的约定。这四个固有函数为:
· KIND
· PRECISION
· RANGE
· SELECTED_REAL_KIND
● 固有函数KIND
这个函数用于查询变量的种别参数。由于实型数据的双精度型被认为是一种单独的实
型数据种别,因此针对它的种别参数查询函数使用KIND(0.0D0),而对于默认实型则
使用KIND(0.0)。
句法:
result = KIND (x)
输入x可以是任何属于固有数据类型的数据对象。
函数结果为一个属于默认整型的标量,表示x的种别参数。
【例5-19】 KIND (0.0)为默认实型的种别参数。
● 固有函数PRECISION
这个函数给出实数的有限十进制小数表示或近似表示的十进制精度。
函数输出为一个正整数p,表示数据取值至少含有p个有效数。
句法:
result = PRECISION (x)
输入x必须是实型或复型,可以取值为标量或数组。
函数结果为一个属于默认整型的数值,等于INT((DIGITS(x) - 1) * LOG10(RADIX(x)))。
如果 RADIX(x)等于10的某个整数次幂,则结果加1。
如果X是一个REAL(4)值,那么PRECISION(X)等于6。因为:
INT ((24-1) * LOG10 (2.)) = INT (6.92...)。
● 固有函数RANGE
参见应用于整型数据的该函数的说明。
● 固有函数SELECTED_REAL_KIND
这个函数能够根据数据的十进制精度和十进制幂次范围,给出其种别参数值。
使用这个函数,就可以在只了解数据的十进制表示的精度与幂次范围要求的情况下,
在数据说明当中指定其种别参数。
一般形式:
result = SELECTED_REAL_KIND ([p] [,r])
其中:
p作为可选输入,属于默认整型的标量;
r作为可选输入,属于默认整型的标量。
这两个可选量必须至少出现一个。
输入表示十进制精度的p或(和)十进制最大幂次r。
输出具有十进制精度的p或(和)满足-10r < n < 10r的变量n的种别参数值。
显然其中的p就是函数PRECISION返回的结果,r就是函数RANGE返回的结果。
如果系统不支持相应的实型种别,就会出现如下结果:
● 如果系统不支持相应精度,则得到-1;
● 如果系统不支持相应幂次范围,则得到-2;
● 如果系统两者都不支持,则得到-3。
如果该数据同时满足不止一个种别参数值,则取具有最小精度的种别参数值。
【例5-20】
REAL (SELECTED_REAL_KIND (5))X !表示X具有至少5位有效数,并且
!没有指定幂次范围。
REAL (SELECTED_REAL_KIND (6, 50))X !表示X具有至少6位有效数,
!并且指定幂次范围为1050到10-50。
REAL (SELECTED_REAL_KIND (6, 70)) !等价于REAL (8)
i = SELECTED_REAL_KIND(r=200) ! returns 8
i = SELECTED_REAL_KIND(13) ! returns 8
施加于实型数据的运算包括:
● 一元固有算术运算:求反运算-;求同运算+。
● 二元固有算术运算:加法运算+;减法运算-;乘法运算*;除法运算/;乘幂运算**。
● 二元固有关系运算:大于.GT.或>;大于等于.GE.或>=;小于.LT.或<;小于等于.LE.
或<=;等于.EQ.或==;不等于.NE.或/=。
对实型数据施加固有算术运算的结果总是实型数据,即使二元运算的某个运算元为整型,结果仍然被视为实型。
固有关系运算的结果为属于默认种别类型的逻辑型数据。
FORTRAN可以写出有限位的整数,也可以写出有限位的小数,然后可以再加上正负号,到此为止,实数里面的非有限小数就只能用有限小数来近似了。
带符号的实型字面常量的写法(R412)有以下几种形式:
不含指数部分的实常量:
[s]n[n...][ _k]
含指数部分的实常量:
[s]n[n...]E[s]nn...[_k]
[s]n[n...]D[s]nn...
[s]n[n...]Q[s]nn...
413
其中:
s表示正负号,如果是负数必须有(-),如果是正数,(+)可选。
n表示从0到9的数字(默认实型的情况下)。如果不含指数部分,则其中必须包含
小数点。在整数与小数部分的数字串遵循R402,指数部分的数字串遵循R416和
R401。
k是种别参数:REAL(4)是4, REAL(8)是8,种别参数前面必须有下划线( _ )。
E,D表示指数符(R415)。后接指数表示10的幂次,例如1.0E6 表示1.0 * 10**6。
● FORTRAN只能表示实数的有限小数部分,并且按十进制科学记数法解释,即有
效数乘以10的指数幂。书写的有效数字个数可以多于系统约定的用来近似表示该
常量值的位数。
● 在一个实型常量里同时出现种别参数与指数时,使用E。
● 指数符使用D,表示常量为双精度实型数据(REAL(8)),由于这种精度的实型是单
独定义的,因此不能再带有种别参数。它的种别参数值是固有查询函数
KIND(0.0D0)的结果,而且约定其表示方法的十进制精度要大于默认实型的精度。
● 带种别参数的实型常量受到其种别参数的约定。
● 既不带指数,也没有种别参数的实型常量为默认的单精度常量,即REAL(4),此
时种别参数值是固有查询函数KIND(0.0)的结果。
● 应用种别参数来规定所谓的有效数的个数时,数字串里第一个非0数字左边所有
的0全被忽略。例如00.0000562389里面,5之前所有的0都不计入有效数。
● 如果实型常量不含指数部分,则在数字串部分必须包含小数点,如果有效数为不
带小数点的数字串,即整型字面常量时,必须出现指数符及指数,否则就变为整
型数据了;如果含指数部分,则小数点不是必须的,而指数部分的数字串里不能
出现小数点。
● 在默认情况下,指数符E指单精度实型(REAL(4)),而如果带有种别参数,则依
种别参数,例如-7.E2_8为双精度实型常量,也可以写为-7.D2。
● 如果实型常量的数字串当中出现指数符,那么指数数字串不能省略,但可以是0。
● 种别参数可以使用HIGH,LOW,QUAD等命名常量,其具体取值由系统约定。
单精度实型(REAL(4))的定义如下:
x=0,或者
【例5-21】 下面都是合法的实型常量:
-12.78
+3.73E2
-13.6E_4
10.93E7_QUAD
.96D2
3.14159_4
-.00127
+5.0E3
2E-3_4
123456789D+5
123456789E+5_8
+2.7843D00
2E200_8
123456789Q4000
1.23Q-400
【例5-22】 下面都是不合法的实型常量:
1,234,567. ! 数字串当中不能含有逗号
325E-47 !对于REAL来说,这个数太小;但是属于合法的DOUBLE PRECISION
!常量。
-47.E47 !对于REAL来说,这个数太大;但是属于合法的DOUBLE PRECISION常
!量。
625._6 !6不是合法的实型种别参数。
100 !在不含指数部分的情况下,没有小数点,故只能是合法的整型常量。
$25.00 !不允许含特许字符。
-.25D0_2 !2不是合法的实型种别参数。
+2.7182812846182 !没写指数符D,不过在忽略7个有效数之外的数字之后,可
!以视为合法的单精度实型常量。
1234567890D45 !这个数对于D_floating格式来说太大;但属于合法的G_floating和
!T_floating格式。
123456789.D400 !这个数对于双精度格式来说太大。
123456789.D-400 !这个数对于双精度格式来说太小。
1.Q5000 !这个数对于三倍精度格式来说太大。
1.Q-5000 !这个数对于三倍精度格式来说太小。
两个实型数据就构成一个复型数据,因为在数学上,一个复数本质上就是一个实数二元组。
和实型值集合只是实数的一个子集一样,复型值集合只是数学上复数的近似表示的一个子集。复型的值运用实值的有序对来表示,第一个实值称为实部,第二个实值称为虚部。
复型数据的取值的写法如下:
(c,c)
其中逗号与括号不可或缺,而c可以取:
● 整型常量或REAL(4)常量,得到COMPLEX(4)常量;
● 整型常量,REAL(4)常量,或DOUBLE PRECISION (REAL(8))常量,得到COMPLEX(8)常量;
正是由于复型本质上由实型组合成,因此复型数据的存储模式依据实型而定。
复型的名称为COMPLEX,说明复型数据类型的句法为:
COMPLEX [([KIND = ] kind-parameter)] [ ,attribute-list::] entity-list
其中:
n为种别参数4 or 8,同于实型。
● 由于复型数据种别参数是应用于实部和虚部两个实型数据的,如果出现种别参
数,该种别参数指定实部和虚部两个实型数据的种别;如果不带种别参数,则称
此复型数据为默认复型,即实部和虚部都属于默认实型。
【例5-23】
COMPLEX ch
COMPLEX (KIND=4),PRIVATE :: zz, yy !等价于COMPLEX*8 zz, yy
COMPLEX(8) ax, by! 等价于COMPLEX*16 ax, by
COMPLEX (kind(4)) y(10)
complex (kind=8) x, z(10)
复型数据对象的取值的种别完全由实型而来,根据两个实型分量的组合,可以分为如下情形:
● 两个实型分量都是默认实型,则相应复型也称为默认复型;
● 两个实型分量都是双精度实型,则相应复型也称为双精度复型;
● 给复型对象指定一个种别参数,意味着同时给它的两个实型分量指定了同样的种别参数;
● 两个分量都是整型对象,则都转换成默认实型,因而认为是默认复型的;
● 其中一个分量是整型对象,另一个分量是实型对象,则将整型的分量转换成与另一实
型分量相同种别的实型对象,其种别参数就是实型分量的种别参数。
● 如果两个实型分量的种别参数不同,则具有精度高的分量的种别参数就是复型的种别
参数,并且把另一分量转换为这个精度高的种别形式。
这里显然符合FORTRAN对存储模式一致性处理的一般原则,即当需要统一存储具有不同存储模式的几种数据时,就一律采用需要存储空间最大的那种模式。
应用于复型的固有查询函数与实型相同,其中有关KIND,RANGE,PRECISION的说明参见实型的相应说明。
SELECTED_REAL_KIND也可以直接用于复型对象,用法与功能都与用于实型对象一致。
【例5-24】
COMPLEX (SELECTED_REAL_KIND(5,50))CY !表示复型变量CY至少具有
!5位有效数,同时取值范围
!可达10-50到1050。
施加于复型数据的运算包括:
● 一元固有算术运算:求反运算-;求同运算+。
● 二元固有算术运算:加法运算+;减法运算-;乘法运算*;除法运算/;乘幂运算**。
● 二元固有关系运算:大于.GT.或>;大于等于.GE.或>=;小于.LT.或<;小于等于.LE.
或<=;等于.EQ.或==;不等于.NE.或/=。
这里的算术运算就是定义在复数集合上算术运算。
对复型数据施加固有算术运算的结果总是复型数据,即使二元运算的某个运算元为整型,或实型,结果仍然被视为复型。
固有关系运算的结果为属于默认种别类型的逻辑型数据。
复型字面常量的句法(R417)为:
(real-part,imaginary-part)
其中实部和虚部可以是带号整型字面常量(R403)与带号实型字面常量(R412)的任意组合。
复型数据对象的种别如何确定,参见上面的6.4.5.2节
【例5-25】 下面是合法的复型常量:
(1,3) !默认复型
(7.0,-7.0) !默认复型
(0,3.6E5) !默认复型
(0.5_4,+8.6E29) !如果假定种别参数值4比8所表示的实数精度低,则该复型数据具有
!种别参数值8
【例5-26】 下面是不合法的复型常量:
(1.23,) !漏写了作为虚部的整型或单精度实型常量值。
(1.0, 2H12) !不能用Hollerith常量
(,1.23Q0) !漏写了实部
(1.7039E0,-1.7039D0) ! 2个常量都不属于REAL(16),但属于合法的双精度常量。
用来作为任何判断的结果的变量或常量,就是逻辑型数据对象。之所以把它单独归结为一种数据类型,是因为它的取值只有是和否两个,在存储方面可以非常简单,为了充分显示这个特点,有必要单独作为一种数据的类型。
逻辑型数据对象的名称为LOGICAL,它的声明语句的句法为:
LOGICAL [([KIND = ] kind-parameter n)] [ ,attribute-list::] entity-list
其中n可取种别参数1, 2, 4, 或8。
种别参数是可选项,如果没有使用种别参数,则为默认逻辑型。
【例5-27】 下面的声明语句主要说明逻辑型对象。
LOGICAL, ALLOCATABLE :: flag1, flag2
LOGICAL (KIND = byte), SAVE :: doit, don’t
【例5-28】 下面的声明语句主要说明对象的属性。
LOGICAL flag1, flag2
LOGICAL (KIND = byte) doit, dont
ALLOCATABLE flag1, flag2
SAVE doit, dont
逻辑型数据取值只有两个可能,即表示真的(.TRUE.)和表示假的(.FALSE.)的两个值,它们后面可以跟可选的下划线与种别参数。
一个系统至少要提供一种表示方法作为默认逻辑型,在默认的情形下,一个逻辑型值需要和默认实型值一样大的存储空间,它的表示方法就是省略可选的下划线和种别参数,此时种别参数值可以通过固有查询函数KIND(.FALSE.)获得。
大多数系统还提供其他存储模式,例如一个逻辑型值采用一个bit或一个byte来存储,具体的种别参数的约定依赖于具体的系统。
至于查询数值型数据的种别参数的固有函数SELECTED_INT_KIND和SELECTED_REAL_KIND对于逻辑型没有相应的函数。
施加于逻辑型对象的运算包括:
● 一元固有运算:非(.NOT.)
● 二元固有运算:与运算(.AND.);或运算(.OR.);逻辑等值运算(.EQV.);逻辑不
等值运算(.NEQV.)。
对逻辑型值施加上面的运算,仍然得到逻辑型值。
逻辑型常量只有两个,它们也可以附加系统约定的种别参数,句法为:
.TRUE.[_k]
.FALSE.[_k]
其中k为可选的种别参数: LOGICAL(1)是1;LOGICAL(2)是2;LOGICAL(4)是4;LOGICAL(8)是8。注意种别参数前面一定要有下划线( _ )。这里的种别参数是与实型数据的种别参数保持一致的。
【例5-29】
.TRUE._BIT
.FALSE._LONG !这里的LONG必须是已说明的有名常量,并且具有非负的整常量值。
!其存储模式必须是已经说明的.
字符型数据对象在系统认可的字符集当中取值,其唯一形式就是由有限个属于系统字符集当中的字符构成字符串。
字符型数据的名词为CHARACTER,字符型对象的基本声明句法为:
CHARACTER [([LEN = ] length-parameter &
[ , [KIND = ] kind-parameter])] [ ,attribute-list::] entity-list
可以看到其中各种可选项的组合情况比较多,为清晰起见,分别写出如下:
CHARACTER
CHARACTER([KIND=]n)
CHARACTER([LEN=]len)
CHARACTER([LEN=]len [, [KIND=]n])
CHARACTER(KIND=n [, LEN=len])
CHARACTER*len[,]
其中n取种别参数值1;
len是字符长度值,不是种别参数值,一般取正整数值,也可能取星号或特定表达式。
参见表达式
有关CHARACTER声明语句,还参见数据声明
【例5-30】 下面是不同形式的字符型数据的声明语句。
CHARACTER (70)PROJECT
CHARACTER (LEN=30, KIND=GERMAN)TRANSFORMATION
CHARACTER (LEN=25,KIND=GREEK),DIMENSION(11)::Z1
字符型能取的值的集合就是所有有限字符串的集合。
所谓一个有限字符串就是一个字符按照文字方式排列的有限序列,如果从左到右对每一个字符进行编号,得到1,2,3.……,n,那么n就是该字符串的长度,即该字符串所有字符(包括重复字符)的数目。
决定一个字符串变量的值的存储空间的因素有两个:
● 字符串的长度;
● 字符的存储单位的大小。
因此这两个因素都是在描述字符型对象时,系统需要知道的参数。
字符长度作为一种种别参数,它的取值分为如下情形:
● 可以等于0,这时称长度等于零的字符串为空串;
● 在没有指定LEN值的时候,默认长度值为1;
● 根据字符数目,取大于0的整数。当然这个整数存在上界,由系统给出约定。
系统至少需提供一种定义字符型数据值的集合的表示方法,一般是会有几种方法,每种方法用不同的种别参数来区分。
种别参数的具体取值依赖于具体的系统的约定。
字符型数据还有一个需要仔细加以约定的属性是字符集的排序问题。
由于字符型数据的运算除了串联之外,还有一类关系运算,需要依据对字符集的字符顺序的规定。一般原则如下:
● 首先出于可移植性的要求,最好把字符集限制在FORTRAN的基本字符集。
● 空格优先于所有字母与数字;
● 字母按照英文字母表排序;
● 数字按照0,1,2,…,9的顺序排序;
● 字母与数字不能间杂;
● 除了空格,对其他特殊字符与下划线的位置都没有约束;
● 语言标准不要求系统支持ASCII编码,但要求提供固有函数,ACHAR和IACHAR,
用来在系统编码和ASCII编码之间进行转换。
● 固有函数LGT,LGE,LLE,LLT可用来基于ASCII顺序进行排序比较运算。
可以施加于字符型数据的运算包括:
● 串联运算:运算符是//,针对具有相同种别参数的字符型数据而定义。其运算结果仍
然为一个保持种别参数不变的字符型数据。
● 关系运算:
● 大于,其运算符为.GT.,或>;
● 大于等于,其运算符为.GE.,或>=;
● 小于,其运算符为.LT.,或<;
● 小于等于,其运算符为.LE.,或<=;
● 等于,其运算符为.EQ.,或==;
● 不等于,其运算符为.NE.,或/=。
关系运算的结果为逻辑型数据,即只能取.TRUE.和.FALSE.两个值之一。
字符字面常量用可选的种别参数,然后是下划线后跟用撇号或引号为界的,由可表示字符组成的字符串来表示。字符型字面常量的构成句法(R420)如下:
[k_]'[ch...]'
[k_]"[ch...]"
其中:
k为可选种别参数,默认情形为1,后接下划线( _ )。
注意字符型数据的种别参数置于常量前面(左边)。
ch 为ASCII字符,或者说系统可表示字符。
前一个撇号或引号称为前定界符,后一个称为尾定界符,它们统称为定界符。
● 字符型常量的值限制在定界符之间,而定界符本身不属于字符串。
● 定界符之间的任意字符,包括空格和Tab键都属于字符串,不过对于控制符得参
考系统的约定。
● 字符串当中可以包含定界符本身,不过就只能看成字符,不能看成是字符型常量
的定界符。当用引号作定界符时,则用一个撇号来表示该字符,当用撇号作定界
符时,则用两个连续的撇号来表示,且其间不夹有空格字符,此时两个连续的撇
号计作一个字符。对字符字面值中的引号也类似地处理
● 长度为0的字符常量,也就是空串,由两个连续的同样的定界符表示,中间不夹
空格。
● 如果前定界符前不出现种别参数和下划线,则该字符常量为默认字符型。
● 对于可表示字符的约定与源程序形式有关。在固定源码形式中,它依赖于系统字
符集,它是除一部分(甚至全部)控制字符外的所有字符;在自由源码形式中,它可以
包含系统的附加图形字符。
【例5-31】 下面是合法的字符常量。
"WHAT KIND TYPE? "
'TODAY''S DATE IS: '
"The average is: "
''
“”
‘I’’m a student’
“I’m a student”
CHINESE_”汉字的种别参数CHINESE作为命名常量已经被定义”
【例5-32】 下面是不合法的字符常量:
'HEADINGS !缺少尾定界符
'Map Number:" !定界符的首尾不配套