目录
第5章 I/O操作
5.1 格式描述符
以下表格中:
d
表示小数点右边数字的个数;e
表示指数的位数;k
表示比例因子(十进制小数点移动的位数);m
表示要显示的最小数据位数;r
表示重复次数;w
表示字符域宽。
指数计数法:尾数在0.1~1.0之间。
科学计数法:尾数在1.0~10.0之间。
工程计数法:尾数在1.0~1000.0之间,指数通常是3的倍数。
实型数I/O描述符 | 格式 | 含义 |
---|---|---|
D描述符 | [r]Dw.d | 双精度实型数的指数计数法表示 |
E描述符 | [r]Ew.d[Ee] | 实型数的指数计数法表示 |
EN描述符 | [r]ENw.d[Ee] | 实型数的工程计数法表示 |
ES描述符 | [r]ESw.d[Ee] | 实型数的科学计数法表示 |
F描述符 | [r]Fw.d | 实型数的十进制表示 |
整型数I/O描述符 | 格式 | 含义 |
---|---|---|
I描述符 | [r]Iw[.m] | 整型数的十进制表示 |
特殊进制数I/O描述符 | 格式 | 含义 |
---|---|---|
B描述符 | [r]Bw[.m] | 实型数/整型数的二进制表示 |
O描述符 | [r]Ow[.m] | 实型数/整型数的八进制表示 |
Z描述符 | [r]Zw[.m] | 实型数/整型数的十六进制表示 |
逻辑型数据I/O描述符 | 格式 | 含义 |
---|---|---|
L描述符 | [r]Lw | 逻辑型数据表示 |
字符型数据I/O描述符 | 格式 | 含义 |
---|---|---|
A描述符 | [r]A[w] | 字符型数据表示 |
通用I/O描述符 | 格式 | 含义 |
---|---|---|
G描述符 | [r]Gw.d[Ee] | 任何类型都适用的通用编辑描述符 |
GO描述符 | [r]GO | 任何类型都适用的可调节宽度的通用编辑描述符 |
舍入I/O描述符 | 格式 | 含义 |
---|---|---|
RU描述符 | RU | 为在此描述符之后的所有描述符对应的数据的数值向上取整 |
RD描述符 | RD | 为在此描述符之后的所有描述符对应的数据的数值向下取整 |
RZ描述符 | RZ | 为在此描述符之后的所有描述符对应的数据的数值向0取整 |
RN描述符 | RN | 为在此描述符之后的所有描述符对应的数据的数值四舍五入取整 |
RC描述符 | RC | 为在此描述符之后的所有描述符对应的数据的数值采用兼容舍入原则 |
RP描述符 | RP | 为在此描述符之后的所有描述符对应的数据的数值采用处理器默认的舍入原则 |
小数指示I/O描述符 | 格式 | 含义 |
---|---|---|
DC描述符 | DC | 在此描述符之后的所有描述符对应的浮点数都使用“, ”作为小数部分和整数部分的分隔符 |
DP描述符 | DP | 在此描述符之后的所有描述符对应的浮点数都使用“. ”作为小数部分和整数部分的分隔符 |
定位I/O描述符 | 格式 | 含义 |
---|---|---|
X描述符 | nX | 水平距离:空n格 |
/描述符 | / | 垂直距离:向下移动一行 |
T描述符 | Tc | TAB:移动到当前行的第c-1列 |
TL描述符 | TLn | TAB:向当前行左边移动n列 |
TR描述符 | TRn | TAB:向当前行右边移动n列 |
最后来看一个程序,这个程序示例了“
:
”符和“*
”符的用法:
PROGRAM main
IMPLICIT NONE
INTEGER(KIND = 4) :: i
INTEGER(KIND = 4),DIMENSION(8) :: x = [1,2,3,4,5,6,7,8]
WRITE(*,"(3('x(',I0,') = ',I0,4X))") (i,x(i),i=1,8)
!x(1)□=□1□□□□x(2)□=□2□□□□x(3)□=□3□□□□
!x(4)□=□4□□□□x(5)□=□5□□□□x(6)□=□6□□□□
!x(7)□=□7□□□□x(8)□=□8□□□□x(
WRITE(*,"(3('x(',I0,') = ',I0,:,4X))") (i,x(i),i=1,8)
!x(1)□=□1□□□□x(2)□=□2□□□□x(3)□=□3□□□□
!x(4)□=□4□□□□x(5)□=□5□□□□x(6)□=□6□□□□
!x(7)□=□7□□□□x(8)□=□8
WRITE(*,"(*(3('x(',I0,') = ',I0,4X),/))") (i,x(i),i=1,8)
!x(1)□=□1□□□□x(2)□=□2□□□□x(3)□=□3□□□□
!x(4)□=□4□□□□x(5)□=□5□□□□x(6)□=□6□□□□
!x(7)□=□7□□□□x(8)□=□8□□□□x(
WRITE(*,"(*(3('x(',I0,') = ',I0,:,4X),/))") (i,x(i),i=1,8)
!x(1)□=□1□□□□x(2)□=□2□□□□x(3)□=□3□□□□
!x(4)□=□4□□□□x(5)□=□5□□□□x(6)□=□6□□□□
!x(7)□=□7□□□□x(8)□=□8
END PROGRAM main
5.2 OPEN语句
在读写文件之前,必须通过
OPEN
语句将I/O单元与磁盘文件显式连接起来:
OPEN(open_list) !打开文件(将文件连接到I/O单元)
open_list | 输入or输出 | 含义 | 取值 |
---|---|---|---|
[UNIT = ]int_expr | 输入 | 指明文件所要连接的I/O单元 ( 1 ) ^{(1)} (1) | 整数 |
FILE = char_expr | 输入 | 指明要打开的文件名 ( 2 ) ^{(2)} (2) | 字符串 |
STATUS = char_expr | 输入 | 指明要打开的文件的状态 ( 3 ) ^{(3)} (3) | "OLD" 、"NEW" 、"REPLACE" 、"SCRATCH" 、"UNKNOWN" (默认) |
NEWUNIT = int_var | 输出 | 自动选择与当前打开的I/O单元不冲突的I/O单元,返回使用的单元编号 | 整数 |
IOSTAT = int_var | 输出 | 操作结束后I/O的状态 ( 4 ) ^{(4)} (4) | 整数 |
IOMSG = char_var | 输出 | 描述在操作期间所发生错误的字符串 | 字符串 |
ACCESS = char_expr | 输入 | 指明文件的访问方式 ( 5 ) ^{(5)} (5) | "SEQUENTIAL" (默认)、"DIRECT" 、"STREAM" |
ASYNCHRONOUS = char_var | 输入 | 指明是否使用异步I/O | "YES" 、"NO" (默认) |
DECIMAL = char_expr | 输入 | 指明实数的整数和小数部分用哪种符号分隔开 | "COMMA" (默认)、"POINT" |
ENCODING = char_expr | 输入 | 指明读写文件时,字符数据的编码类型 ( 6 ) ^{(6)} (6) | "UTF-8" (Unicode)、"DEFAULT" (默认) |
ROUND = char_expr | 输入 | 指明在格式化I/O操作中所使用的舍入类型 | "UP" 、"DOWN" 、"ZERO" 、"NEAREST" 、"COMPATIBLE" 、"PROCESSOR DEFINED" (默认) |
SIGN = char_expr | 输入 | 指明输出行中正数的前面是否显示正号 | "PLUS" 、"SUPPERSS" 、"PROCESSOR DEFINED" (默认) |
FORM = char_expr | 输入 | 指明文件的格式状态 ( 7 ) ^{(7)} (7) | "FORMATED" 、"UNFORMATED" |
ACTION = char_expr | 输入 | 指明文件的读写权限 | "READ" 、"WRITE" 、"READWRITE" (默认) |
RECL = int_expr | 输入 | 指明直接访问文件中每条记录的长度 ( 8 ) ^{(8)} (8) | 整数 |
POSITION = char_expr | 输入 | 指明文件打开后文件指针的位置 ( 9 ) ^{(9)} (9) | "REWIND" 、"APPEND" 、"ASIS" (默认) |
DELIM = char_expr | 输入 | 指明表式输出和名称列表输出语句中用于分隔字符串的字符 ( 10 ) ^{(10)} (10) | "QUOTE" 、"APOSTROPHE" 、"NONE" (默认) |
PAD = char_expr | 输入 | 指明格式化输入时最前面的不足字段是否以空格填充 | "YES" (默认)、"NO" |
BLANK = char_expr | 输入 | 指明在数字域中空格所代表的意义 ( 11 ) ^{(11)} (11) | "NULL" (默认)、"ZERO" |
ERR = int_expr | 输入 | 指明文件打开失败时,转向的语句标号 ( 12 ) ^{(12)} (12) | 整数 |
(1)在所有
OPEN
语句中,必须出现UNIT=
子句或者NEWUNIT=
子句,如果UNIT=
子句作为OPEN
语句的第一个子句出现,那么“UNIT=
”是可省的。
(2)FILE=
子句不允许用于临时文件,因为不会创建永久文件,给临时文件指定文件名是错误的。
(3)如果文件状态为"OLD"
,表示该文件必须已经存在于系统中,否则执行OPEN
语句会产生错误;如果文件状态为"NEW"
,表示系统中一定不存在该文件,否则执行OPEN
语句会产生错误;如果文件状态为"REPLACE"
,那么如果文件已经存在,程序会删除它创建一个新的文件,如果文件不存在,程序会根据给出的名字创建一个新的文件;如果文件状态为"SCRATCH"
,那么会在计算机上创建一个临时文件(在程序运行时可以用来临时存放数据,当临时文件被关闭或者程序结束,该文件就会自动从系统中删除),然后将它和I/O单元关联起来;如果文件状态为"UNKNOWN"
,那么程序的处理可能会根据处理器的不同而不同,在程序中应该避免使用这个状态,因为这样会降低程序的可移植性。
(4)如果文件打开成功,int_var=0;否则int_var为一个与处理器有关的正数,并且该正数代表着发生错误的类型。
(5)有三类文件的访问方式,分别是顺序式、直接式、流式。顺序访问指的是打开文件后,对其中记录的读写是按照从头到尾的顺序逐一进行;直接访问在任何时候都可以从文件中的一条记录直接跳到另一条记录,而不需要经过读取这两条记录之间的任何记录;流式访问类似于C语言中的文件I/O,读写文件都是以“文件访问单元”(通常是字节)进行的,这种模式和顺序访问方式的不同在于,顺序访问是面向记录的,每条记录后面都会自动插入一个记录结束符(新行),相反,流式访问只是按照指定的字节来读写文件,而不对每行的结束做任何额外的处理。
(6)如果取值是"UTF-8"
,那么每个字符以2字节的Unicode编码存取;如果取值是"DEFAULT"
,那么字符编码依赖于处理器,实际上这意味着它是1字节的ASCII码字符编码。
(7)有两种文件格式:"FORMATTED"
和"UNFORMATTED"
。格式化文件中的数据是由可识别的字符和数字等组成的,这些文件之所以叫做格式化文件是因为在读写数据时,使用格式描述符将数据转化为计算机可用的形式,当向格式化文件写入数据时,存储在计算机中的比特序列就被翻译成了一系列人所能识别的字符,然后这些字符被写到文件中;未格式化文件所包含的数据是计算机内存中数据的精确拷贝,当向未格式化文件写入数据时,实际上是将计算机内存中的精确比特序列拷贝到文件,未格式化文件要比相应的格式化文件小很多,但是由于未格式化文件中的信息是按照比特序列进行编码的,所以对人来说不容易识别和检查,此外,一些特殊值的比特序列可能因为计算机系统类型的不同而不同,所以未格式化文件不能轻易地从一台机器移植到另一台机器。如果文件使用的是顺序访问,那么默认的文件格式是"FORMATTED"
;如果文件使用直接访问方式,那么默认的文件格式是"UNFORMATTED"
。
(8)对于格式化直接访问文件来说,该子句包含每条记录的字符的长度;对于未格式化直接访问文件来说,该子句包含以处理器相关的度量单位计算的每条记录的长度。
(9)如果取值是"REWIND"
,那么文件指针指向文件的第一条记录;如果取值是"APPEND"
,那么文件指针指向文件的最后一条记录之后、文件结束符之前;如果取值是"ASIS"
,那么文件指针的位置不定,与处理器相关。
(10)如果取值是"QUOTE"
,那么字符串之间以逗号分隔,字符串中实际含有的引号会自动重复一次;如果取值是"APOSTROPHE"
,那么字符串之间以省略号分隔,字符串中实际含有的省略号会自动重复一次;如果取值是"NONE"
,那么字符串之间没有分隔符。
(11)在现代Fortran程序中不再需要这条子句。
(12)在现代Fortran程序中不在需要这条子句,用IOSTAT=
子句代替它。
5.3 CLOSE语句
一旦不再需要文件,就应该用
CLOSE
语句将其和I/O单元的连接断开:
CLOSE(close_list) !关闭文件(将文件与I/O单元的连接断开)
close_list | 输入or输出 | 含义 | 取值 |
---|---|---|---|
[UNIT = ]int_expr | 输入 | 指明要关闭的I/O单元 | 整数 |
STATUS = char_expr | 输入 | 指明文件关闭后,文件的去留 ( 1 ) ^{(1)} (1) | "KEEP" (默认)、"DELETE" |
IOSTAT = int_var | 输出 | 操作结束后I/O的状态 ( 2 ) ^{(2)} (2) | 整数 |
IOMSG = char_var | 输出 | 描述在操作期间所发生错误的字符串 | 字符串 |
ERR = int_expr | 输入 | 指明文件关闭失败时,转向的语句标号 ( 3 ) ^{(3)} (3) | 整数 |
(1)临时文件在关闭后都会被删除,因此把临时文件的状态指定为
"KEEP"
是非法的。
(2)如果文件关闭成功,int_var=0;否则int_var为一个与处理器有关的正数,并且该正数代表着发生错误的类型。
(3)在现代Fortran程序中不在需要这条子句,用IOSTAT=
子句代替它。
5.4 INQUIRE语句
INQUIRE(inquire_list) !检查文件的属性
如果文件还没有打开,那么必须通过文件名来识别文件;如果文件已经打开了,那么可以通过文件名或I/O单元来识别文件。但是不能两者同时使用。
inquire_list | 输入or输出 | 含义 | 取值 |
---|---|---|---|
[UNIT = ]int_expr | 输入 | 指明要检查的文件I/O单元 | 整数 |
FILE = char_expr | 输入 | 指明要检查的文件名 | 字符串 |
IOSTAT = int_var | 输出 | I/O状态(成功返回0,否则返回与处理器相关的正数) | 整数 |
IOMSG = char_var | 输出 | I/O错误信息 | 字符串 |
EXIST = log_var | 输出 | 文件是否存在 | .TRUE. 、.FALSE. |
OPENED = log_var | 输出 | 文件是否打开 | .TRUE. 、.FALSE. |
NUMBER = int_var | 输出 | 获取文件代码 | 整数 |
NAMED = log_var | 输出 | 文件是否有名字(临时文件没有名字) | .TRUE. 、.FALSE. |
NAME = char_var | 输出 | 文件名 | 字符串 |
ACCESS = char_var | 输出 | 文件的访问方式 | "SEQUENTIAL" 、"DIRECT" 、"STREAM" |
SEQUENTIAL = char_var | 输出 | 是否能按顺序访问打开 | "YES" 、"NO" 、"UNKNOWN" |
DIRECT= char_var | 输出 | 是否能按直接访问打开 | "YES" 、"NO" 、"UNKNOWN" |
STREAM= char_var | 输出 | 是否能按流访问打开 | "YES" 、"NO" 、"UNKNOWN" |
FORM = char_var | 输出 | 文件的格式状态 | "FORMATED" 、"UNFORMATED" |
FORMATTED = char_var | 输出 | 是否能连接格式化I/O | "YES" 、"NO" 、"UNKNOWN" |
UNFORMATTED = char_var | 输出 | 是否能连接未格式化I/O | "YES" 、"NO" 、"UNKNOWN" |
RECL = int_var | 输出 | 直接访问文件中记录的长度 | 整数 |
NEXTREC = int_var | 输出 | 直接访问文件中最后从文件中读出或写入的记录个数 | 整数 |
BLANK = char_var | 输出 | 在数字域中空格所代表的意义 | "NULL" 、"ZERO" |
POSITION = char_var | 输出 | 文件打开时文件指针的位置 | "REWIND" 、"APPEND" 、"ASIS" 、"UNDEFINED" |
ACTION = char_var | 输出 | 文件的读写权限 | "READ" 、"WRITE" 、"READWRITE" 、"UNDEFINED" |
READ = char_var | 输出 | 是否为只读文件 | "YES" 、"NO" 、"UNKNOWN" |
WRITE = char_var | 输出 | 是否为只写文件 | "YES" 、"NO" 、"UNKNOWN" |
READWRITE = char_var | 输出 | 是否为读写文件 | "YES" 、"NO" 、"UNKNOWN" |
DELIM = char_var | 输出 | 表式输出和名称列表输出语句中用于分隔字符串的字符 | "QUOTE" 、"APOSTROPHE" 、"NONE" 、"UNKNOWN" |
PAD = char_var | 输出 | 格式化输入时最前面的不足字段是否以空格填充 | "YES" 、"NO" |
ASYNCHRONOUS = char_var | 输出 | 是否使用异步I/O | "YES" 、"NO" |
ENCODING = char_var | 输出 | 读写文件时,字符数据的编码类型 | "UTF-8" 、"UNDEFINED" 、"UNKNOWN" |
ID = int_expr | 输入 | 即将进行异步数据传输的ID号 | 整数 |
PENDING = log_var | 输出 | 返回指定ID=子句异步I/O操作的状态 | .TRUE. 、.FALSE. |
POS = int_var | 输出 | 返回下一步要读写的文件位置 | 整数 |
ROUND = char_var | 输出 | 格式化I/O操作中所使用的舍入类型 | "UP" 、"DOWN" 、"ZERO" 、"NEAREST" 、"COMPATIBLE" 、"PROCESSOR DEFINED" |
SIGN = char_var | 输出 | 输出行中正数的前面是否显示正号 | "PLUS" 、"SUPPERSS" 、"PROCESSOR DEFINED" |
ERR = int_expr | 输入 | 指明语句失败,转向的语句标号(在现代Fortran程序中不在需要这条子句,用IOSTAT=子句代替它) | 整数 |
5.5 READ语句
READ(control_list) io_list !读取文件(通过I/O单元)
control_list | 输入or输出 | 含义 | 取值 |
---|---|---|---|
[UNIT = ]int_expr | 输入 | 指明读取的I/O单元 | 整数 |
[UNIT = ]* | 输入 | 指明从标准输入设备读取数据 | * |
[FMT = ]int_expr | 输入 | 指明当读取格式化数据时采用的格式的语句标号 | 整数 |
[FMT = ]char_expr | 输入 | 指明当读取格式化数据时采用的格式 | 字符串 |
[FMT = ]* | 输入 | 指明以表式I/O读取数据 | * |
IOSTAT = int_var | 输出 | 操作后的I/O状态 ( 1 ) ^{(1)} (1) | 整数 |
IOMSG = char_var | 输出 | I/O错误信息 | 字符串 |
REC = int_expr | 输入 | 指明在直接访问文件时要读取的记录个数 | 整数 |
NML = name_list | 输入 | 指明要读取的I/O实体的名称列表 | 名称列表 |
ADVANCE = char_expr | 输入 | 指明顺序文件是否进行高级或非高级I/O ( 2 ) ^{(2)} (2) | "YES" (默认)、"NO" |
SIZE = int_var | 输出 | 在非高级I/O中已经从输入缓冲区读取的字符个数 | 整数 |
EOR = int_expr | 输入 | 指明在非高级I/O操作时,到达结束记录时,转向的语句标号 ( 3 ) ^{(3)} (3) | 整数 |
ASYNCHRONOUS = char_expr | 输入 | 指明是否使用异步I/O | "YES" 、"NO" (默认) |
DECIMAL = char_expr | 输入 | 指明实数的整数和小数部分用哪种符号分隔开 | "COMMA" (默认)、"POINT" |
DELIM = char_expr | 输入 | 指明用于分隔字符串的字符 | "QUOTE" 、"APOSTROPHE" 、"NONE" (默认) |
ID = int_var | 输出 | 返回一个和异步I/O传输相关的唯一的ID ( 4 ) ^{(4)} (4) | 整数 |
POS = int_expr | 输入 | 指明按照流访问方式打开的文件的读取位置 ( 5 ) ^{(5)} (5) | 整数 |
ROUND = char_expr | 输入 | 指明在格式化I/O操作中所使用的舍入类型 | "UP" 、"DOWN" 、"ZERO" 、"NEAREST" 、"COMPATIBLE" 、"PROCESSOR DEFINED" (默认) |
SIGN = char_expr | 输入 | 指明正数的前面是否显示正号 | "PLUS" 、"SUPPERSS" 、"PROCESSOR DEFINED" (默认) |
END = int_expr | 输入 | 指明到达文件末尾时,转向的语句标号 ( 6 ) ^{(6)} (6) | 整数 |
ERR = int_expr | 输入 | 指明文件读取发生错误时,转向的语句标号 ( 7 ) ^{(7)} (7) | 整数 |
(1)如果读取成功,int_var=0;如果遇到文件结束条件,int_var=-1;如果在非高级I/O中遇到文件结束条件,int_var=-2;如果读取失败,int_var为一个表示错误类型的正数。
(2)该子句指明在READ结束时是否放弃当前缓冲区的内容。
(3)在现代Fortran程序中不在需要这条子句,用IOSTAT=
子句代替它。
(4)如果指定了异步数据传输,那么只能使用ID=
子句。
(5)当文件是按照流访问方式打开时,只能使用POS=
子句。
(6)在现代Fortran程序中不在需要这条子句,用IOSTAT=
子句代替它。
(7)在现代Fortran程序中不在需要这条子句,用IOSTAT=
子句代替它。
表控输入(List-directed Input):变量列表中的变量类型决定输入数据需要的格式。
格式化输入(Formated Input):格式描述符决定输入变量的参数类型。
表控输入的优点是易于使用,因为不需要为它专门编写FORMAT语句,这样,即使用户可能在任何一列键入输入数据,READ语句仍能正确解析输入值。此外,表控输入还支持空值,如果输入数据行包含两个连续的“
,
”,那么这个位置对应的变量的值将保持不变;如果输入数据行以“/
”作为结束符,那么结束符后面对应的所有变量的值将保持不变:
PROGRAM main
IMPLICIT NONE
INTEGER(KIND = 4) :: a,b,c
a = 1;b = 2;c = 3
WRITE(*,"('Please enter a, b, c: ')")
READ(*,*) a,b,c !输入:10 ,, 30
WRITE(*,*) a,b,c !10 2 30
a = 1;b = 2;c = 3
WRITE(*,"('Please enter a, b, c: ')")
READ(*,*) a,b,c !输入:10 /
WRITE(*,*) a,b,c !10 2 3
END PROGRAM main
5.6 WRITE语句
WRITE(control_list) io_list !向文件写入数据(通过I/O单元)
WRITE
语句中可用的子句除了END=
、SIZE=
、EOR=
子句外,其它和READ
语句中的相同,这里不再赘述。
表控输出(List-directed Onput):输出列表中的值的类型决定输出数据的格式。
格式化输出(Formated Onput):格式描述符决定输出数据的格式。
输出语句除了
WRITE
外,还有一个
PRINT fmt,io_list !向标准输出设备写入数据
WRITE
的区别是前者没有设置输出设备的能力,只能针对标准设备输出。今后输出统一用更灵活的WRITE
语句。
5.7 文件定位语句
REWIND(control_list) !将顺序文件中的文件指针移动到开头处
BACKSPACE(control_list) !将顺序文件中的文件指针向后移动一个记录
ENDFILE(control_list) !将顺序文件中的文件指针移动到结束处
control_list | 输入or输出 | 含义 | 取值 |
---|---|---|---|
[UNIT = ]int_expr | 输入 | 指明要操作的I/O单元 ( 1 ) ^{(1)} (1) | 整数 |
IOSTAT = int_var | 输出 | 操作后的I/O状态 ( 2 ) ^{(2)} (2) | 整数 |
IOMSG = char_var | 输出 | I/O错误信息 | 字符串 |
ERR = int_expr | 输入 | 指明文件读取发生错误时,转向的语句标号 ( 3 ) ^{(3)} (3) | 整数 |
(1)为了和早期Fortran版本兼容,仅包含I/O单元编号的文件定位语句可以不使用括号,例如:
REWIND 10
、BACKSPACE 10
、ENDFILE 10
。
(2)操作成功,int_var=0;否则int_var为一个与处理器有关的正数,并且该正数代表着发生错误的类型。
(3)在现代Fortran程序中不在需要这条子句,用IOSTAT=
子句代替它。
5.8 I/O名称列表
I/O名称列表是一个读入或输出固定变量名和数值列表的好方法:
NAMELIST /nl_group_name/ var1[,var2,...] !必须写在执行语句之前
名称列表的I/O语句除了用
UML=
子句代替FMT=
子句外看上去和格式化I/O语句类似,当执行面向名称列表的WRITE
语句时,名称列表中的所有变量名都会和其值一起按照特定的顺序输出:
PROGRAM main
IMPLICIT NONE
INTEGER(KIND = 4) :: a = 1,b = 2
CHARACTER(LEN = 10) :: str = "wibibaboo"
INTEGER(KIND = 4),DIMENSION(3) :: vec = [1,2,3]
NAMELIST /my_list/ a,b,str,vec
OPEN(UNIT = 10,FILE = "data.nml",DELIM = "APOSTROPHE")
WRITE(UNIT = 10,NML = my_list)
!data.nml文件包含的数据信息:
!&MY_LIST
!A = 1,
!B = 2,
!STR = 'wibibaboo ',
!VEC = 1, 2, 3
!/
CLOSE(UNIT = 10)
END PROGRAM main
当执行面向名称列表的
READ
语句时,程序会搜索带有&nl_group_name
的输入文件,它表示名称列表的开始,然后读取名称列表中的所有数值,直到碰到“/
”才终止:
PROGRAM main
IMPLICIT NONE
INTEGER(KIND = 4) :: a = 1,b = 2
CHARACTER(LEN = 10) :: str = "wibibaboo"
INTEGER(KIND = 4),DIMENSION(3) :: vec = [1,2,3]
NAMELIST /my_list/ a,b,str,vec
OPEN(UNIT = 10,FILE = "data.nml",DELIM = "APOSTROPHE")
!data.nml文件包含的数据信息:
!&MY_LIST
!b = 20,
!b = 200,
!str = 'WIBIBABOO ',
!vec(1) = 10,
!vec(2) = 20
!/
READ(UNIT = 10,NML = my_list)
WRITE(UNIT = *,NML = my_list,DELIM = "APOSTROPHE")
!&MY_LIST
!A = 1,
!B = 200,
!STR = 'WIBIBABOO ',
!VEC = 10, 20, 3
!/
CLOSE(UNIT = 10)
END PROGRAM main
形参和动态创建的变量不能出现在名称列表中,这包括无上下界值的数组的形参、长度不定的字符变量、自动变量以及后面要讲的指针。
5.9 三种文件访问方式
顺序访问文件在读写时,不能任意跳跃到文件的某个位置读写数据,只能从头开始一步步向下进行,改变文件读写位置时,只能一步步地进退,或是直接移回文件开头或结尾,下面来看几个例子:
!向文件中写入数据
PROGRAM main
IMPLICIT NONE
TYPE :: student
CHARACTER(LEN = 10) :: name
INTEGER(KIND = 4) :: age
END TYPE student
!I/O单元号通常定义成常量,在编写大型程序时,有助于修改以及分清文件
!I/O单元号在整个程序中是共享的,不同的函数可以使用同样的I/O单元号来操作文件
INTEGER(KIND = 4),PARAMETER :: file_id = 10
CHARACTER(LEN = 20) :: file_name = "e:\data.dat"
INTEGER(KIND = 4) :: i
TYPE(student),DIMENSION(4) :: students
students = [student("Jerry",18),student("Tom",19),student("Rose",20),student("Mike",21)]
OPEN(UNIT = file_id,FILE = file_name,STATUS = "NEW")
DO i = 1,4
WRITE(UNIT = file_id,FMT = "(A8,I3)") students(i)%name,students(i)%age
END DO
CLOSE(UNIT = file_id)
!data.dat文件包含的数据信息:
!Jerry 18
!Tom 19
!Rose 20
!Mike 21
END PROGRAM main
!读取文件并将其中的数据输出在屏幕上
PROGRAM main
IMPLICIT NONE
INTEGER(KIND = 4),PARAMETER :: file_id = 10
CHARACTER(LEN = 79) :: file_name
CHARACTER(LEN = 79) :: buffer
!这里之所以限制读取字符串的长度为79是因为在标准的DOS及Windows的cmd窗口下,
!一行只能显示80个字符,所以读太多字符在cmd窗口就显示不出来了,
!但是如果刚好输出80个字符,有的编译器所编译出来的程序会发生断行的现象,所以最好是79
INTEGER(KIND = 4) :: read_stat = 0
LOGICAL(KIND = 4) :: is_exist
WRITE(*,*) "Please enter file name: "
READ(*,"(A79)") file_name
INQUIRE(FILE = file_name,EXIST = is_exist)
IF(.NOT.is_exist) THEN
WRITE(*,"('File ""',A,'"" doesn''t exist! ')") TRIM(file_name)
STOP
END IF
OPEN(UNIT = file_id,FILE = file_name,ACCESS = "SEQUENTIAL",STATUS = 'OLD')
DO WHILE(.TRUE.)
READ(UNIT = file_id,FMT = "(A79)",IOSTAT = read_stat) buffer
IF(read_stat /= 0) EXIT
WRITE(*,"(A79)") buffer
END DO
CLOSE(UNIT = file_id)
END PROGRAM main
直接访问文件中的记录可以按照任意顺序来访问,这对于需要以任意顺序读取的信息比如数据库文件的读取非常有用。操作直接访问文件的关键是文件中的每条记录的长度必须相等,因为当记录等长的时候,那么就可以精确计算磁盘文件中第i条记录的位置,这样就可以直接读取含有该条记录的磁盘扇区,而不需要读取该记录前的其他所有扇区。
直接访问文件可以通过在
OPEN
语句中指定ACCESS = "DIRECTED"
的方法来打开,每个记录的长度使用RECL=
子句来指明,对于格式化文件来说,必须指定FORM = "FORMATTED"
,因为直接访问文件的默认格式是"UNFORMATTED"
。
PROGRAM main
IMPLICIT NONE
INTEGER(KIND = 4) :: i
INTEGER(KIND = 4) :: irec
CHARACTER(LEN = 20) :: buffer
OPEN(UNIT = 10,FILE = "data.dat",ACCESS = "DIRECT",FORM = "FORMATTED",STATUS = "REPLACE",RECL = 20)
DO i = 1,5 !插入5条数据
WRITE(UNIT = 10,FMT = "('This is record ',I3,' .')",REC = i) i
END DO
!data.dat文件包含的数据信息:
!This□is□record□□□1□.This□is□record□□□2□.This□is□record□□□3□.This□is□record□□□4□.This□is□record□□□5□.
WRITE(*,"('Which record would you like to see? Please enter: ')")
READ(*,"(I3)") irec
READ(UNIT = 10,FMT = "(A)",REC = irec) buffer
WRITE(*,"('The record is: ',/,A)") buffer
CLOSE(UNIT = 10)
END PROGRAM main
例:为了比较格式化和未格式化的直接访问文件的操作以及文件的大小,创建两个各自包含50000条记录的文件,每个文件的每条记录都有四个双精度实型数据。
PROGRAM main
IMPLICIT NONE
INTEGER(KIND = 4),PARAMETER :: SGL = SELECTED_REAL_KIND(P=6)
INTEGER(KIND = 4),PARAMETER :: DBL = SELECTED_REAL_KIND(P=14)
INTEGER(KIND = 4),PARAMETER :: MAX_RECORDS = 50000
INTEGER(KIND = 4) :: i,j,irec
INTEGER(KIND = 4) :: length_fmt = 84 !格式化文件中每条记录的长度
INTEGER(KIND = 4) :: length_unf !未格式化文件中每条记录的长度
REAL(KIND = SGL) :: time_fmt !格式化文件所用的时间
REAL(KIND = SGL) :: time_unf !未格式化文件所用的时间
REAL(KIND = SGL) :: rnum !随机数
REAL(KIND = DBL),DIMENSION(4) :: records !每条记录中包含四个双精度实数
INQUIRE(IOLENGTH = length_unf) records !返回包含在输出列表中实体的未格式化记录的长度
WRITE(*,"('The formatted record length is ',I)") length_fmt !84
WRITE(*,"('The unformatted record length is ',I)") length_unf !8
OPEN(UNIT = 10,FILE = "data.fmt",ACCESS = "DIRECT",FORM = "FORMATTED",STATUS = "REPLACE",RECL = length_fmt) !data.fmt大小为4102KB
OPEN(UNIT = 20,FILE = "data.unf",ACCESS = "DIRECT",FORM = "UNFORMATTED",STATUS = "REPLACE",RECL = length_unf) !data.unf大小为1563KB
!向文件填充数据
DO i = 1,MAX_RECORDS
DO j = 1,4
CALL random_seed()
CALL random_number(rnum)
records(j) = 30.0_DBL * rnum
END DO
WRITE(UNIT = 10,FMT = "(4ES21.14)",REC = i) records !21×4=84
WRITE(UNIT = 20,REC = i) records
END DO
CLOSE(UNIT = 10)
CLOSE(UNIT = 20)
END PROGRAM main
流访问文件按照字节读写文件,每次读写一个字节,并且不处理其中的特殊字符,比如回车、换行等。使用一系列的
WRITE
语句可以将数据写入文件,当想要结束一行时,需要将“新行符”(类似于C语言中的\n)写入文件,Fortran有一个内置函数new_line
,可以实现这一功能:
PROGRAM main
IMPLICIT NONE
OPEN(UNIT = 10,FILE = "data.dat",ACCESS = "STREAM",FORM = "FORMATTED",STATUS = "REPLACE")
WRITE(UNIT = 10,FMT = "('Text on first line.',$)")
WRITE(UNIT = 10,FMT = "(A)") new_line(" ")
WRITE(UNIT = 10,FMT = "('Text on second line.',$)")
WRITE(UNIT = 10,FMT = "(A)") new_line(" ")
!data.dat文件包含的数据信息:
!Text on first line.
!
!Text on second line.
CLOSE(UNIT = 10)
END PROGRAM main
5.10 内部文件
Fortran提供了一种机制,可以将数字数据转换为字符数据或者将字符数据转换成数字数据,这就是内部文件(Internal File)。在内部文件中,
READ
和WRITE
操作发生在内部字符缓冲区中,而不是磁盘文件中。
例:在使用
READ
命令从键盘输入数据时,如果用户输入错误的数据,可能会导致程序死机(例如如果需要输入整数但却输入英文字母时)。有一种处理方法就是让程序暂时把数据当成字符串读入,然后检查字符串中是否含有不合理的字符,如果字符串中都是0~9的数字,就把字符串解释为整数,不然就让用户重新输入。
PROGRAM main
CHARACTER(LEN = 10) :: temp_str
INTEGER(KIND = 4) :: i,int_data
LOGICAL(KIND = 4) :: flag = .TRUE.
DO WHILE(flag)
flag = .FALSE.
WRITE(*,*) "Please enter an integer: "
READ(*,"(A10)") temp_str
DO i = 1,LEN_TRIM(temp_str)
IF(LLT(temp_str(i:i),"0") .OR. LGT(temp_str(i:i),"9")) THEN
WRITE(*,"('Error input, please reenter!')")
flag = .TRUE.
EXIT
END IF
END DO
END DO
READ(temp_str,"(I)") int_data !将temp_str读取为整数
WRITE(*,*) int_data
END PROGRAM main
例:输出格式可以事先放在字符串中,程序运行时,动态改变字符串的内容就可以达到动态改变输出格式的目的。
PROGRAM main
IMPLICIT NONE
INTEGER(KIND = 4) :: a = 1,b = 2
CHARACTER(LEN = 30) :: fmt_str = "(I??,'+',I??,'=',I??)"
WRITE(fmt_str(3:4),"(I2.2)") INT(LOG10(REAL(a))+1)
WRITE(fmt_str(11:12),"(I2.2)") INT(LOG10(REAL(b))+1)
WRITE(fmt_str(19:20),"(I2.2)") INT(LOG10(REAL(a+b))+1)
WRITE(*,fmt_str) a,b,a+b !1+2=3
END PROGRAM main
5.11 异步I/O
在普通的Fortran I/O操作中,如果程序使用
WRITE
或READ
语句向文件中写入数据或从文件中读取数据,那么程序执行到WRITE
或READ
语句处就会暂停执行,直到数据全部写完或读完,这就是同步I/O,因为I/O操作和程序的执行是同步进行的。
相反,异步I/O操作和程序的执行是并行的,如果执行异步
WRITE
语句,那么待写入文件的数据会先拷贝到一些内部缓冲区,当写过程启动后,控制马上返回到调用程序,这种情况下,调用程序可以继续全速执行,而写操作也会同时进行。如果执行的是异步READ
语句,那么在启动读过程后,控制立即返回到调用程序,此时被读的变量是未定义的,它们可能是旧值,也可能是新值,也可能正在更新,所以在读操作完成前,这些变量不能使用。
使用异步
READ
的程序如何知道何时完成了读操作呢?第一种方法,当启动I/O操作时,使用ID=
子句为该操作获取一个ID,然后使用INQUIRE
语句查询操作的状态;第二种方法,用WAIT
指令让编译器等待读操作完成,再交出控制权。
Fortran编译器被允许但不必须实现异步I/O,在很多系统上设计的Fortran编译器支持多种CPU,其中I/O操作可以在不同的CPU上独立计算,大型并行计算机总是支持异步I/O操作。
如果执行异步
WRITE
,那么程序不需要采取其他专门的操作;如果执行异步READ
,那么程序必须等待读取操作执行完毕后才能使用其中的变量:
REAL(KIND = 4),DIMENSION(5000,5000) :: data1
REAL(KIND = 4),DIMENSION(5000,5000) :: data2
...
!异步WRITE
OPEN(UNIT = 10,FILE = "data1.dat",ASYNCHRONOUS = "YES",STATUS = "NEW",ACTION = "WRITE",IOSTAT = open_status)
WRITE(UNIT = 10,FMT = "(10F10.6)",ASYNCHRONOUS = "YES",IOSTAT = write_status)
(Continue processing with "data1" abled to use...)
!异步READ
OPEN(UNIT = 20,FILE = "data2.dat",ASYNCHRONOUS = "YES",STATUS = "OLD",ACTION = "READ",IOSTAT = open_status)
READ(UNIT = 20,FMT = "(10F10.6)",ASYNCHRONOUS = "YES",IOSTAT = read_status)
(Continue processing with "data2" unabled to use...)
WAIT(UNIT = 20)
(Continue processing with "data2" abled to use...)
第6章 指针
在前面的章节中,已经创建并使用了五种Fortran自带的数据类型以及派生数据类型的变量,这些变量有两个共同的特征:第一,都存储某一形式的数据;第二,这些变量在程序中的个数和类型在程序执行之前就已经声明了,并且在整个程序执行过程中保持不变。Fortran包含另一种类型的变量,该变量不包含数据,而是包含另一变量在内存中的地址,因为这种类型的变量指向另外的变量,所以被称为指针(Pointer)。指针主要用于变量或数组必须在程序执行过程中动态创建和销毁的情况。
6.1 指针和目标变量
在变量声明语句中包含
POINTER
属性将变量声明为指针类型,它可以指向包括派生数据类型在内的某个特定的数据类型:
DATA_TYPE,POINTER :: scalr_ptr_name
DATA_TYPE,DIMENSION(:),POINTER :: vec_ptr_name
DATA_TYPE,DIMENSION(:,:),POINTER :: matrx_ptr_name
在变量声明语句中包含
TARGET
属性将变量声明为目标变量,该变量才可以被同类型的指针所指:
DATA_TYPE,TARGET :: scalr_targt_name
DATA_TYPE,DIMENSION(vec_size),TARGET :: vec_targt_name
DATA_TYPE,DIMENSION(matrx_size1,matrx_size2),TARGET :: matrx_targt_name
通过指针赋值语句
=>
可以将指针关联到指定的目标变量:
scalr_ptr_name => scalr_targt_name
vec_ptr_name => vec_targt_name
matrx_ptr_name => matrx_targt_name
当执行指针赋值语句后,目标变量的内存地址就保存在了指针变量中,任何对该指针变量的引用实际上都是对保存在目标变量中的数据的引用(如果是C语言的话则必须用取址符
&
来访问指针所指向的变量)。
当第一次在数据类型声明语句中声明指针时,其指针关联状态被称为未定义;一旦指针和目标变量通过指针赋值语句关联起来,此时的指针关联状态被称为相关联;如果指针后来和其目标变量断开,并且没有和新的目标变量相关联,此时的指针关联状态被称为未关联或空状态。通过Fortran内置逻辑函数
ASSOCIATED
可以判断指针是否和某个目标变量相关联:
log_var = ASSOCIATED(ptr_name)
log_var = ASSOCIATED(ptr_name,targt_name)
因为“未定义”状态是不确定的,所以建议在创建指针时就尽快指明指针的状态,让它指向某个目标变量或者将其置为未关联:
!第一种方法
DATA_TYPE,POINTER :: ptr_name1,ptr_name2
NULLIFY(ptr_name1,ptr_name2)
!第二种方法
DATA_TYPE,POINTER :: ptr_name1,ptr_name2
ptr_name1 => NULL()
ptr_name2 => NULL()
只要指针出现在需要数值的Fortran表达式中,就是使用指针指向的目标变量的值来代替指针本身,这一过程被称为指针的断开引用:
PROGRAM main
IMPLICIT NONE
REAL(KIND = 4),POINTER :: p1 => NULL(),p2 => NULL(),p3 => NULL()
REAL(KIND = 4),TARGET :: a = 1.0,b = 2.0,c
p1 => a
p2 => b
p3 => c
p3 = p1 + p2 !相当于c=a+b;p1、p2、p3出现在了目标变量(普通变量)应该出现的位置,它们都断开引用
p2 => p1 !相当于p2=>a;p1出现在了目标变量(普通变量)应该出现的位置,p1断开引用
END PROGRAM main
指针不仅能指向数组,而且能指向数组子集(部分数组),任何由下标三元组定义的部分数组(不能用于向量下标所定义的部分数组)都可以用作指针的目标变量:
PROGRAM main
IMPLICIT NONE
INTEGER(KIND = 4) :: i
INTEGER(KIND = 4),DIMENSION(16),TARGET :: targt_vec = [(i,i=1,16)]
INTEGER(KIND = 4),DIMENSION(:),POINTER :: p1,p2,p3,p4,p5
p1 => targt_vec
p2 => p1(2::2)
p3 => p2(2::2)
p4 => p3(2::2)
p5 => p4(2::2)
WRITE(*,"('p1 = ',16I3)") p1
WRITE(*,"('p2 = ',8I3)") p2
WRITE(*,"('p3 = ',4I3)") p3
WRITE(*,"('p4 = ',2I3)") p4
WRITE(*,"('p5 = ',1I3)") p5
!p1 = 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
!p2 = 2 4 6 8 10 12 14 16
!p3 = 4 8 12 16
!p4 = 8 16
!p5 = 16
END PROGRAM main
6.2 用指针改善程序性能
在访问、排序、交换大型数组或派生数据类型数据时,操作指向它们的指针要比操作它们本身高效很多,因为不管是哪种数据类型,指针变量都占用相同的内存空间(通常是4字节):
!用指针高效交换大型矩阵
REAL(KIND = 8),DIMENSION(5000,5000),TARGET :: matrx1,matrx2
REAL(KIND = 8),DIMENSION(:,:),POINTER :: p1,p2,temp
p1 => matrx1
p2 => matrx2
temp => p1
p1 => p2
p2 => temp
!用指针高效交换自定义数据类型
TYPE :: person
CHARACTER(LEN = 10) name
REAL(KIND = 4) :: height
REAL(KIND = 4) :: weight
END TYPE person
TYPE(person),TARGET :: targt_p1,targt_p2
TYPE(person),POINTER :: ptr_p1,ptr_p2,ptr_temp
ptr_p1 => targt_p1
ptr_p2 => targt_p2
ptr_temp => ptr_p1
ptr_p1 => ptr_p2
ptr_p2 => ptr_temp
!用指针高效访问高维数组中的某一个元素
INTEGER(KIND = 4),TARGET,DIMENSION(10,10,10) :: targt_cubic
INTEGER(KIND = 4),POINTER :: ptr
ptr => targt_cubic(5,5,5)
!用指针高效访问大型矩阵的某一部分
INTEGER(KIND = 4),TARGET,DIMENSION(5000,5000) :: targt_matrx
INTEGER(KIND = 4),POINTER,DIMENSION(:,:) :: ptr_matrx
ptr_matrx => targt_matrx(20:30,20:30)
6.3 动态内存分配
指针最强大的功能之一是能够使用它们在任何时候动态创建变量或数组,还能在使用完毕后,释放动态变量或数组所使用的空间。使用
ALLOCATE
语句分配内存,使用DEALLOCATE
语句释放内存。这种过程类似于创建可分配数组的过程,它们的区别是:声明成ALLOCATABLE
的数组的生存周期只在声明它的函数内,函数结束后数组会自动释放这些内存;如果用ALLOCATE
分配给指针,等到程序结束才会自动释放这些内存。
!第二种指针的创建方法
PROGRAM main
IMPLICIT NONE
INTEGER,POINTER :: p
ALLOCATE(p) !配置一块内存空间给指针变量p
p = 1 !得到内存后的指针变量p可以像整型数一样使用
WRITE(*,*) p !1
DEALLOCATE(p) !释放掉配置的内存
END PROGRAM main
另外,如果指针指向变量,指针可以随时改变它的指向;但是如果指针是用
ALLOCATE
配置的,在改变指向前需要将它DEALLOCATE
掉。
指针与函数
指针可以作为函数参数和函数返回值,这时必须要写函数的接口块,并且指针参数在声明时不能有
INTENT
属性。
PROGRAM main
IMPLICIT NONE
INTERFACE
FUNCTION get_min(pointer_arr) RESULT(min)
INTEGER,POINTER,DIMENSION(:) :: pointer_arr
INTEGER,POINTER :: min
INTEGER :: i,lenth
END FUNCTION get_min
END INTERFACE
INTEGER,TARGET,DIMENSION(5) :: target_arr = (/2,3,1,5,4/)
INTEGER,POINTER,DIMENSION(:) :: pointer_arr
pointer_arr => target_arr
WRITE(*,*) get_min(pointer_arr)
END PROGRAM main
FUNCTION get_min(pointer_arr) RESULT(min)
IMPLICIT NONE
INTEGER,POINTER,DIMENSION(:) :: pointer_arr !pointer_arr指针作为函数参数
INTEGER,POINTER :: min !min指针作为函数返回值
INTEGER :: i,lenth
lenth = SIZE(pointer_arr,1) !获取矩阵的列数(这里就是获取p的长度)
min => pointer_arr(1) !注意不能写成“=”
DO i = 2,lenth
IF(min > pointer_arr(i)) THEN
min => pointer_arr(i)
END IF
END DO
END FUNCTION get_min
之前讲过,可以将函数封装在模块中的,减少编写接口块带来的麻烦:
MODULE func_module
IMPLICIT NONE
CONTAINS
FUNCTION get_min(pointer_arr) RESULT(min)
INTEGER,POINTER,DIMENSION(:) :: pointer_arr
INTEGER,POINTER :: min
INTEGER :: i,lenth
lenth = SIZE(pointer_arr,1)
min => pointer_arr(1)
DO i = 2,lenth
IF(min > pointer_arr(i)) THEN
min => pointer_arr(i)
END IF
END DO
END FUNCTION get_min
END MODULE func_module
PROGRAM main
USE func_module
IMPLICIT NONE
INTEGER,TARGET,DIMENSION(5) :: target_arr = (/2,3,1,5,4/)
INTEGER,POINTER,DIMENSION(:) :: pointer_arr
pointer_arr => target_arr
WRITE(*,*) get_min(pointer_arr)
END PROGRAM main
串行结构
接下来的内容,将介绍指针的一个非常常见的应用——“串行(háng)”。串行是指多个同类的东西连接在一起。先来看一个单向串行的程序:
MODULE type_def
IMPLICIT NONE
TYPE node
INTEGER :: content !节点存储的内容
TYPE(node),POINTER :: next !指向下一个节点的指针
END TYPE node
END MODULE type_def
PROGRAM main
USE type_def
IMPLICIT NONE
TYPE(node),POINTER :: node1,node2,node3
ALLOCATE(node1)
ALLOCATE(node2)
ALLOCATE(node3)
!将数据装入节点中
node1%content = 10
node2%content = 20
node3%content = 30
!创建单向串行
node1%next => node2
node2%next => node3
node3%next => NULL() !node3没有下一个可以指向了,需要置空
!根据串行的关系访问节点存储的数据
WRITE(*,*) node1%content !10
WRITE(*,*) node1%next%content !20,相当于node2%content
WRITE(*,*) node1%next%next%content !30,相当于node3%content
DEALLOCATE(node1)
DEALLOCATE(node2)
DEALLOCATE(node3)
END PROGRAM main
上面这个程序只是示范了串行创建的大概原理,实际上创建串行更多用到的是下面这种搭配循环的方法:
MODULE type_def
IMPLICIT NONE
TYPE node
INTEGER :: content
TYPE(node),POINTER :: next
END TYPE node
END MODULE type_def
PROGRAM main
USE type_def
IMPLICIT NONE
TYPE(node),POINTER :: head !串行头
TYPE(node),POINTER :: p !访问串行节点的指针变量
INTEGER :: i,n
WRITE(*,*) "请输入节点数量:"
READ(*,*) n
!节点头的数据要先设定
ALLOCATE(head)
head%next => NULL()
WRITE(*,*) "请输入第 1个节点数据:"
READ(*,*) head%content
p => head
DO i = 2,n
ALLOCATE(p%next)
p => p%next
WRITE(*,"('请输入第',I2,'个节点数据:')") i
READ(*,*) p%content
END DO
p%next => NULL() !串行最后一个节点的下一个需要置空
p => head
DO WHILE(ASSOCIATED(p))
WRITE(*,*) p%content
p => p%next
END DO
END PROGRAM main
加强一下串行结构,使之成为可以沿前后两个方向移动的双向串行:
MODULE type_def
IMPLICIT NONE
TYPE node
INTEGER :: content
TYPE(node),POINTER :: previous !指向上一个节点的指针
TYPE(node),POINTER :: next !指向下一个节点的指针
END TYPE node
END MODULE type_def
PROGRAM main
USE type_def
IMPLICIT NONE
TYPE(node),POINTER :: head !串行头
TYPE(node),POINTER :: tail !串行尾
TYPE(node),POINTER :: p !访问串行节点的指针变量
TYPE(node),POINTER :: p_temp
INTEGER :: i,n
WRITE(*,*) "请输入节点数量:"
READ(*,*) n
!节点头(或者节点尾)的数据要先设定
ALLOCATE(head)
head%previous => NULL()
head%next => NULL()
WRITE(*,*) "请输入第 1个节点数据:"
READ(*,*) head%content
p => head
DO i = 2,n
ALLOCATE(p%next)
p_temp => p
p => p%next
p%previous => p_temp
WRITE(*,"('请输入第',I2,'个节点数据:')") i
READ(*,*) p%content
END DO
p%next => NULL()
tail => p
!正向输出
p => head
DO WHILE(ASSOCIATED(p))
WRITE(*,*) p%content
p => p%next
END DO
!反向输出
p => tail
DO WHILE(ASSOCIATED(p))
WRITE(*,*) p%content
p => p%previous
END DO
END PROGRAM main
目前为止所介绍的串行结构都是有头有尾的结构,串行结构还有另外一种类型叫做环状串行。它可以把串行的头尾连接起来,变成一个圈:
MODULE type_def
IMPLICIT NONE
TYPE node
INTEGER :: content
TYPE(node),POINTER :: previous
TYPE(node),POINTER :: next
END TYPE node
END MODULE type_def
PROGRAM main
USE type_def
IMPLICIT NONE
TYPE(node),POINTER :: node1,node2,node3
TYPE(node),POINTER :: p
INTEGER :: n = 6 !环状串行可以一直向前或者向后抓取数据,n可以设置成任意大小
INTEGER :: i
ALLOCATE(node1)
ALLOCATE(node2)
ALLOCATE(node3)
node1 = node(10,node3,node2)
node2 = node(20,node1,node3)
node3 = node(30,node2,node1)
!正向输出
p => node1
DO i = 1,n
WRITE(*,*) p%content
p => p%next
END DO
!反向输出
p => node3
DO i = 1,n
WRITE(*,*) p%content
p => p%previous
END DO
DEALLOCATE(node1)
DEALLOCATE(node2)
DEALLOCATE(node3)
END PROGRAM main
利用串行可以实现快速地插入或删除一条数据:
MODULE nodes_module
IMPLICIT NONE
TYPE node
INTEGER :: content
TYPE(node),POINTER :: previous
TYPE(node),POINTER :: next
END TYPE node
CONTAINS
!在pos之前插入new_node(pos%previous、new_node、pos)
SUBROUTINE insert_before_node(pos,new_node)
TYPE(node),POINTER :: pos
TYPE(node),POINTER :: new_node
new_node%next =>pos
new_node%previous => pos%previous
IF(ASSOCIATED(pos%previous)) pos%previous%next => new_node
pos%previous => new_node
END SUBROUTINE insert_before_node
!在pos之后插入new_node(pos、new_node、pos%next)
SUBROUTINE insert_after_node(pos,new_node)
TYPE(node),POINTER :: pos
TYPE(node),POINTER :: new_node
new_node%next => pos%next
new_node%previous => pos
IF(ASSOCIATED(pos%next)) pos%next%previous => new_node
pos%next => new_node
END SUBROUTINE insert_after_node
!删除pos节点(pos%previous、pos、pos%next)
SUBROUTINE delete_node(pos)
TYPE(node),POINTER :: pos
TYPE(node),POINTER :: previous
TYPE(node),POINTER :: next
previous => pos%previous
next => pos%next
DEALLOCATE(pos)
!重新连接pos释放后的前后两个节点需要注意两点问题:
!1.如果pos是第一条数据,不需要向前重连
!2.如果pos是最后一条数据,不需要向后重连
IF(ASSOCIATED(previous)) previous%next => next
IF(ASSOCIATED(next)) next%previous => previous
pos => next
END SUBROUTINE delete_node
!输出串行
SUBROUTINE print_nodes(head)
TYPE(node),POINTER :: head
TYPE(node),POINTER :: p
p => head
DO WHILE(ASSOCIATED(p))
WRITE(*,*) p%content
p => p%next
END DO
END SUBROUTINE print_nodes
!释放整个串行的内存
SUBROUTINE delete_nodes(head)
TYPE(node),POINTER :: head,p_temp
DO WHILE(ASSOCIATED(head))
p_temp => head%next
DEALLOCATE(head)
head => p_temp
END DO
END SUBROUTINE delete_nodes
END MODULE nodes_module
PROGRAM main
USE nodes_module
IMPLICIT NONE
TYPE(node),POINTER :: head
TYPE(node),POINTER :: pos
TYPE(node),POINTER :: p,p_temp
INTEGER i,n
WRITE(*,*) "请输入节点数量:"
READ(*,*) n
ALLOCATE(head)
head%previous => NULL()
head%next => NULL()
WRITE(*,*) "请输入第 1个节点数据:"
READ(*,*) head%content
p => head
DO i = 2,n
ALLOCATE(p%next)
p_temp => p
p => p%next
p%previous => p_temp
WRITE(*,"('请输入第',I2,'个节点数据:')") i
READ(*,*) p%content
END DO
p%next => NULL()
CALL print_nodes(head)
!删掉第三条数据
CALL delete_node(head%next%next)
CALL print_nodes(head)
!在第三个位置插入数据
ALLOCATE(pos)
pos%content = 30
CALL insert_after_node(head%next,pos)
CALL print_nodes(head)
!释放整个串行的内存
CALL delete_node(head)
END PROGRAM main
在读取文件时,有时候我们事先不确定文件中会有多少条数据,就不能用数组来读取,因为不知道数组该声明成多大,这时可以用串行来很好地解决这个问题。现在有两个文件数据
data1.txt
和data2.txt
记录了两个人数不同的班级学生的考试成绩,截取其中一部分如下图所示,现在要编写一个可以读取成绩的程序,让用户输入文件名来决定要读取哪一个文件,还要提供给用户通过座位号来查询成绩的功能。
MODULE nodes_module
IMPLICIT NONE
TYPE student
INTEGER :: id
INTEGER :: chinese
INTEGER :: math
INTEGER :: english
END TYPE student
TYPE node
TYPE(student) :: s
TYPE(node),POINTER :: next
END TYPE node
CONTAINS
!根据id查找学生并返回串行位置
FUNCTION search_student(head,id) RESULT(pos)
TYPE(node),POINTER :: head
INTEGER :: id
TYPE(node),POINTER :: pos
pos => NULL()
DO WHILE(ASSOCIATED(head))
IF(head%s%id == id) THEN
pos => head
RETURN
END IF
head => head%next
END DO
END FUNCTION search_student
END MODULE nodes_module
PROGRAM main
USE nodes_module
IMPLICIT NONE
CHARACTER(20) :: file_name
CHARACTER(79) :: header !记录文件的第一行
TYPE(node),POINTER :: head,p,pos
INTEGER :: id,n,read_state = 0
WRITE(*,*) "请输入文件名:"
READ(*,*) file_name
OPEN(10,file = file_name,STATUS = 'old')
READ(10,"(A79)") header
ALLOCATE(head)
head%next => NULL()
READ(10,*) head%s
p => head
n = 0 !记录学生人数
DO WHILE(read_state == 0)
ALLOCATE(p%next)
p => p%next
n = n + 1
READ(10,FMT = *,IOSTAT = read_state) p%s
END DO
WRITE(*,"('一共有',I2,'名学生。')") n
WRITE(*,*) "请输入想要查询的学生的ID:"
READ(*,*) id
pos => search_student(head,id)
IF(ASSOCIATED(pos)) THEN
WRITE(*,"('查询结果如下:',/,A79)") header
WRITE(*,"(4(I3,2X))") pos%s%id,pos%s%chinese,pos%s%math,pos%s%english
ELSE
WRITE(*,*) "未找到该学生!"
END IF
END PROGRAM main
第7章 面向对象
F o r t r a n 类的主要组件(类成员) Fortran类的主要组件(类成员) Fortran类的主要组件(类成员)
(1)数据域(Field):当从类实例化对象时,封装在对象中的数据(实例化变量)称为数据域。
(2)方法(Method):方法实现类的行为,有些方法可能在类中有明确定义,但有些方法可能从超类(父类)中继承而来。
(3)构造函数(Constructor):当对象被创建后,构造函数用来初始化对象中的变量。
(4)析构函数(Finalizer):在一个对象被销毁前,它将调用的一个方法,此方法可以用来完成对象销毁前所有必需的清除工作(释放资源等)。
7.1 派生数据类型
派生数据类型(Derived Data Type)是用户利用Fortran内置数据类型或者另外一个派生数据类型的组合自行创建出的一个新的数据类型,下面这个程序示例了派生数据类型的创建以及成员初始化的方法:
PROGRAM main
IMPLICIT NONE
TYPE :: Person
CHARACTER(LEN = 10) :: name
INTEGER(KIND = 4) :: age
END TYPE Person
TYPE(Person) :: p1 = Person("Tom", 18)
TYPE(Person) :: p2
p2%name = "Jerry" !派生数据类型的成员用%或.访问,出于本人习惯,今后统一用%
p2%age = 18
END PROGRAM main
当Fortran编译器为派生数据类型的变量分配内存空间时,编译器并不需要为该类型变量的每个元素分配连续的空间。事实上,它们在内存中的位置是随机的,只要能够保证I/O操作时元素之间保持原有的顺序即可。然而,有时如果想要将派生数据类型的变量传给由其他语言编写的过程,就必须严格限制该变量各元素的内存顺序,这时可以在类型定义中使用
SEQUENCE
语句,使得派生数据类型的元素被放在连续的内存空间中:
TYPE :: Vector
SEQUENCE
INTEGER(KIND = 4) :: x
INTEGER(KIND = 4) :: y
INTEGER(KIND = 4) :: z
END TYPE Vector
正如Fortran允许多种整数或实数类别,用户也可以使用参数定义派生数据类型,这种方式叫做参数化派生数据类型。有两种参数可以用来定义派生数据类型,第一种在编译时已知(称为类别类型参数),另一种在运行时获取(称为长度类型参数),它们对应的形式参数值被称为哑元值:
PROGRAM main
IMPLICIT NONE
INTEGER,PARAMETER :: SGL = SELECTED_REAL_KIND(P = 6)
INTEGER,PARAMETER :: DBL = SELECTED_REAL_KIND(P = 13)
TYPE :: Vector(kind_number,len_number)
INTEGER,KIND :: kind_number = SGL !默认为单精度
INTEGER,LEN :: len_number = 10 !默认为10个元素
REAL(KIND = kind_number),DIMENSION(len_number) :: v
END TYPE Vector
TYPE(Vector) v1 !单精度10元素
TYPE(Vector(kind_number = DBL,len_number = 5)) :: v2 !指双精度5元素
TYPE(Vector(kind_number = DBL,len_number = 5)),DIMENSION(10) :: v3 !双精度5元素的数组
END PROGRAM main
Fortran还允许将过程与派生数据类型绑定,称为类型绑定,这为面向对象程序设计打好了基础:
MODULE module_for_point
IMPLICIT NONE
TYPE :: Point
REAL(KIND = 4) :: x
REAL(KIND = 4) :: y
CONTAINS
PROCEDURE,PASS :: add !默认
PROCEDURE,NOPASS :: minus
END TYPE Point
CONTAINS
TYPE(Point) FUNCTION add(this_point,another_point)
IMPLICIT NONE
CLASS(Point),INTENT(IN) :: this_point
CLASS(Point),INTENT(IN) :: another_point
add%x = this_point%x + another_point%x
add%y = this_point%y + another_point%y
END FUNCTION add
TYPE(Point) FUNCTION minus(this_point,another_point)
IMPLICIT NONE
CLASS(Point),INTENT(IN) :: this_point
CLASS(Point),INTENT(IN) :: another_point
minus%x = this_point%x - another_point%x
minus%y = this_point%y - another_point%y
END FUNCTION minus
END MODULE module_for_point
PROGRAM main
USE module_for_point
IMPLICIT NONE
TYPE(Point) :: p1,p2,p3,p4
p1 = Point(1.0,2.0)
p2 = Point(3.0,4.0)
p3 = p1%add(p2)
p4 = p1%minus(p1,p2)
WRITE(*,*) p3
WRITE(*,*) p4
END PROGRAM main
PASS
属性的存在使得调用add
过程的Point
类型变量被当作第一调用参数自动传递到这一过程,与之对应的时NOPASS
属性。过程的实现必须写在和派生数据类型定义相同的模块中并且派生数据类型必须使用CLASS
关键字声明。
7.2 CLASS保留字
在常规的Fortran程序中,过程中形参的类型和调用时相应的实参应该完全匹配,指针类型和它所指向的类型应该完全匹配,可分配变量和相应的的数据也应该完全匹配,否则会出错。
CLASS
保留字以一种特殊的方式放宽了这个要求:如果一个可分配数据项、指针、形参用CLASS(Type_name)
声明,那么数据项将与数据类型以及该数据类型的所有扩展相匹配:
TYPE :: Point_2D
REAL(KIND = 4) :: x
REAL(KIND = 4) :: y
END TYPE Point_2D
TYPE,EXTENDS(Point_2D) :: Point_3D
REAL(KIND = 4) :: z
END TYPE Point_3D
TYPE(Point_2D),POINTER :: p1 !只能接受Point_2D类型的数据
CLASS(Point_2D),POINTER :: p2 !Point_2D、Point_3D类型的数据都能接受
以
CLASS
保留字声明的指针或者形参类型,称为指针或者形参的声明类型;而任何时候分配给指针或者形参的实际对象的类型被称为指针或形参的动态类型。
由于用
CLASS
保留字声明的数据项可以和一种以上的数据类型相匹配,所以被认为是多态的。多态指针或形参仅能用来访问声明类型的数据项,在扩展中定义的数据项不能访问:
CLASS(Point_2D),POINTER :: p
TYPE(Point_2D),TARGET :: p1
TYPE(Point_3D),TARGET :: p2
p => p1
WRITE(*,*) p%x,p%y !可以访问x、y
p => p2
WRITE(*,*) p%x,p%y,p%z !可以访问x、y,不能访问z
也可以用
CLASS(*)
定义指针或者形参,这样定义的指针或形参被称为不受限多态性,它可以与任何派生数据类型相匹配。
7.3 类和对象
在严格的面向对象编程中,类的数据类型应当用
PUBLIC
属性来声明,类成员组件则用PRIVATE
属性来声明。这样做使得创建该类型的对象成为可能,读取或者修改该类型的实例变量是不可能的。对于Fortran来说,如果数据域被声明为PRIVATE
属性,那么构造函数就不允许使用它们。
MODULE module_for_point
IMPLICIT NONE
TYPE,PUBLIC :: Complx
PRIVATE
REAL(KIND = 4) :: r
REAL(KIND = 4) :: i
CONTAINS
PROCEDURE :: add => add
PROCEDURE :: minus
END TYPE Complx
CONTAINS
TYPE(Point) FUNCTION add(this_point,another_point)
IMPLICIT NONE
CLASS(Point),INTENT(IN) :: this_point
CLASS(Point),INTENT(IN) :: another_point
add%x = this_point%x + another_point%x
add%y = this_point%y + another_point%y
END FUNCTION add
TYPE(Point) FUNCTION minus(this_point,another_point)
IMPLICIT NONE
CLASS(Point),INTENT(IN) :: this_point
CLASS(Point),INTENT(IN) :: another_point
minus%x = this_point%x - another_point%x
minus%y = this_point%y - another_point%y
END FUNCTION minus
END MODULE module_for_point
PROGRAM main
USE module_for_point
IMPLICIT NONE
TYPE(Point) :: p1,p2,p3,p4
p1 = Point(1.0,2.0)
p2 = Point(3.0,4.0)
p3 = p1%add(p2)
p4 = p1%minus(p1,p2)
WRITE(*,*) p3
WRITE(*,*) p4
END PROGRAM main
例:开发软件时,确定执行某一段特定程序需要花费多长时间是非常有用的,这样可以帮助我们找出代码中的“热点”,也就是那些程序中花费大量时间的地方,这样就可以对它们进行优化,这个通常由计时器来完成。计时器作为一个对象,类似于一个秒表,用来计算从按下开始按键到按下结束按键之间所经过的时间。
MODULE module_for_timer
IMPLICIT NONE
INTEGER,PARAMETER :: DBL = SELECTED_REAL_KIND(P=14)
TYPE,PUBLIC :: Timer
PRIVATE !PRIVATE属性使得构造函数不能初始化time,必须用用户自定义的方法来初始化
REAL(KIND = DBL) :: time !s
CONTAINS
PROCEDURE,PUBLIC :: start_timer => start_timer_sub !默认为PASS属性
PROCEDURE,PUBLIC :: elapsed_time => elapsed_time_func
END TYPE Timer
PRIVATE :: start_timer_sub,elapsed_time_func !限制对实际子例程的访问
CONTAINS
SUBROUTINE start_timer_sub(this)
IMPLICIT NONE
CLASS(Timer),INTENT(INOUT) :: this
INTEGER(KIND = 4),DIMENSION(8) :: value
CALL DATE_AND_TIME(VALUES = value)
this%time = 86400.0_DBL * value(3) + 3600.0_DBL * value(5) +&
&60.0_DBL * value(6) + value(7) + 0.001_DBL * value(8)
END SUBROUTINE start_timer_sub
REAL(KIND = DBL) FUNCTION elapsed_time_func(this)
IMPLICIT NONE
CLASS(Timer),INTENT(IN) :: this
INTEGER(KIND = 4),DIMENSION(8) :: value
CALL DATE_AND_TIME(VALUES = value)
elapsed_time_func = 86400.0_DBL * value(3) + 3600.0_DBL * value(5) +&
&60.0_DBL * value(6) + value(7) + 0.001_DBL * value(8) - this%time
END FUNCTION elapsed_time_func
END MODULE module_for_timer
PROGRAM main
USE module_for_timer
IMPLICIT NONE
INTEGER(KIND = 4) i,j,k
TYPE(Timer) :: t
CALL t%start_timer() !PASS属性的存在使得调用此过程的Timer类型变量被当作第一调用参数自动传递到这一过程
DO i = 1,100000
DO j = 1,100000
k = i + j
END DO
END DO
WRITE(*,"('Time = ',F5.3,' s')") t%elapsed_time() !Time = 3.381 s
END PROGRAM main
1313123
没有
SEQUENCE
或BIND(C)
属性的自定义数据类型是可以扩展的,这就有点类似于C++的继承关系:
PROGRAM main
IMPLICIT NONE
TYPE :: Point_2D
REAL(KIND = 4) :: x
REAL(KIND = 4) :: y
END TYPE Point_2D
TYPE,EXTENDS(Point_2D) :: Point_3D
REAL(KIND = 4) :: z
END TYPE Point_3D
TYPE(Point_3D) :: p
p%x = 1.0 !或者p%Point_2D%x
p%y = 2.0 !或者p%Point_2D%y
p%z = 3.0
WRITE(*,*) p
END PROGRAM main
数据结构与算法
冒泡排序算法。假设有一个元素大小顺序不规则的数组a(5),要对其进行升序排列。排序思想:
( 1 )把 a ( 1 ) 到 a ( 5 ) 中最小的找出来,跟 a ( 1 ) 交换。 (1)把a(1)到a(5)中最小的找出来,跟a(1)交换。 (1)把a(1)到a(5)中最小的找出来,跟a(1)交换。
( 2 )把 a ( 2 ) 到 a ( 5 ) 中最小的找出来,跟 a ( 2 ) 交换。 (2)把a(2)到a(5)中最小的找出来,跟a(2)交换。 (2)把a(2)到a(5)中最小的找出来,跟a(2)交换。
( 3 )把 a ( 3 ) 到 a ( 5 ) 中最小的找出来,跟 a ( 3 ) 交换。 (3)把a(3)到a(5)中最小的找出来,跟a(3)交换。 (3)把a(3)到a(5)中最小的找出来,跟a(3)交换。
( 4 )把 a ( 4 ) 到 a ( 5 ) 中最小的找出来,跟 a ( 4 ) 交换。 (4)把a(4)到a(5)中最小的找出来,跟a(4)交换。 (4)把a(4)到a(5)中最小的找出来,跟a(4)交换。
DO i = 1,size - 1
DO j = i + 1,size
IF( a(i) > a(j) ) THEN
temp = a(i)
a(i) = a(j)
a(j) = temp
END IF
END DO
END DO
其他
INTEGER(KIND = 4),DIMENSION(5) :: arr1 = [(i,i = 1,5)] !隐式DO循环
INTEGER(KIND = 4),DIMENSION(5) :: arr1 = [1,(i,i = 2,4),5]
COMMON
是FORTRAN77中使用全局变量的方法,它可以将不同作用域下的变量联系起来。
PROGRAM main
IMPLICIT NONE
!INTEGER,COMMON :: a,b !错误写法
INTEGER :: a,b
COMMON a,b
a = 1
b = 2
CALL showCommon() !□□1□□2
END PROGRAM main
SUBROUTINE showCommon()
IMPLICIT NONE
INTEGER :: num1,num2
COMMON num1,num2
WRITE(*,"(2I3)") num1,num2
END SUBROUTINE showCommon
取用全局变量时,是根据它们声明时相对位置关系来做对应,而不是使用变量名称来对应。所以在上面这个程序中
num1
对应a
,num2
对应b
。COMMON
还可以在变量很多的时候进行分组:
PROGRAM main
IMPLICIT NONE
INTEGER :: a,b,c,d,e,f
COMMON /group1/ a
COMMON /group2/ b
COMMON /group3/ c
COMMON /group4/ d
COMMON /group5/ e
COMMON /group6/ f
f = 1
CALL show_common_6() !□□1
END PROGRAM main
SUBROUTINE show_common_6()
IMPLICIT NONE
INTEGER :: num6
COMMON /group6/ num6
WRITE(*,"(I3)") num6
END SUBROUTINE show_common_6
COMMON
变量还可以用BLOCK DATA
程序模块中的DATA
语句批量赋值。
PROGRAM main
IMPLICIT NONE
INTEGER :: a,b
COMMON a,b
INTEGER :: c,d
COMMON /group/ c,d
WRITE(*,"(4(I3))") a,b,c,d
END PROGRAM main
BLOCK DATA assign_common_variable
IMPLICIT NONE
INTEGER a,b
COMMON a,b
DATA a,b /1,2/
INTEGER c,d
COMMON /group/ c,d
DATA c,d /3,4/
END BLOCK DATA assign_common_variable
BLOCK DATA
这一段程序也很类似于子程序。它也是一段独立的程序模块,也拥有自己的变量声明,不过它不需要被别人调用就可以自己执行。事实上这一段程序会在主程序执行前就会生效,不过它的功能只在于设置全局变量的初值,不能有其他执行命令的出现。还有一点需要指出,全局变量不能声明成常量,所以BLOCK DATA
中不能出现PARAMETER
。
PROGRAM main
IMPLICIT NONE
CALL head_sub() !Hello\World!
CALL mid_sub() !World!
END PROGRAM main
SUBROUTINE head_sub()
IMPLICIT NONE
WRITE(*,*) "Hello!"
ENTRY mid_sub() !另一个入口
WRITE(*,*) "World!"
END SUBROUTINE head_sub
PROGRAM main
IMPLICIT NONE
INTEGER :: a
WRITE(*,*) "请随便输入一个数:"
READ(*,*) a
CALL judge_number(a,*100,*200,*300) !指定了三个折返点
100 WRITE(*,*) "Negative."
STOP
200 WRITE(*,*) "Zero."
STOP
300 WRITE(*,*) "Positive."
END PROGRAM main
SUBROUTINE judge_number(num,*,*,*)
IMPLICIT NONE
INTEGER :: num
IF(num < 0) THEN
RETURN 1 !返回第一个折返点
ELSE IF(num == 0) THEN
RETURN 2 !返回第二个折返点
ELSE
RETURN 3 !返回第三个折返点
END IF
END SUBROUTINE judge_number
!批量赋值
INTEGER :: a
REAL :: b
COMPLEX :: c
CHARACTER(10) :: str
!DATA语句算是声明的一部分,必须放在执行语句之前
DATA a,b,c,str /1,2.0,(1.0,2.0),"Hello"/
等价声明可以把两个以上的变量,声明它们使用同一个内存地址。使用同一个内存位置的变量,只要改变其中的一个变量,就会同时改变其他变量的值。等价声明可以节省内存和精简代码。
PROGRAM main
IMPLICIT NONE
INTEGER :: a = 1,b
EQUIVALENCE(a,b)
WRITE(*,*) a,b !a和b的值都为1
b = 2
WRITE(*,*) a,b !a和b的值都变为2
END PROGRAM main
Fortran有一个叫做
ASSOCIATE
的结构,可以在一个代码段的执行过程中,临时将变量或表达式和某个名字关联,简化拥有长名字或长表达式的代码段。举个例子,假设雷达在跟踪一系列目标,每个目标的坐标都存于Trak_file的数据结构中,雷达本身的坐标存于Rader_loc的数据结构中,现在要计算跟踪到的某个目标的距离和方位:
MODULE type_def
IMPLICIT NONE
TYPE :: Track_file
REAL(KIND = 8) :: x !目标的横坐标(m)
REAL(KIND = 8) :: y !目标的纵坐标(m)
REAL(KIND = 8) :: dist !离目标的距离(m)
REAL(KIND = 8) :: bearing !目标的方位(rad)
END TYPE Track_file
TYPE :: Radar_loc
REAL(KIND = 8) :: x !雷达的横坐标(m)
REAL(KIND = 8) :: y !雷达的纵坐标(m)
END TYPE Radar_loc
END MODULE type_def
PROGRAM main
USE type_def
IMPLICIT NONE
TYPE(Track_file) :: track = Track_file(1.0,2.0,0.0,0.0) !距离和方位暂时记为0.0
TYPE(Radar_loc) :: radar = Radar_loc(3.0,4.0)
!track%dist = DSQRT((radar%x - track%x)**2 + (radar%y - track%y)**2)
!track%bearing = DATAN((radar%y - track%y) / (radar%x - track%x))
ASSOCIATE(dist => track%dist,bearing => track%bearing,&
&x1 => radar%x,y1 => radar%y,&
&x2 => track%x,y2 => track%y)
dist = DSQRT((x1 - x2)**2 + (y1 - y2)**2)
bearing = DATAN((y1 - y2) / (x1 - x2))
END ASSOCIATE
END PROGRAM main