Fortran语法汇总(下)(持续更新中)

第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描述符TcTAB:移动到当前行的第c-1列
TL描述符TLnTAB:向当前行左边移动n列
TR描述符TRnTAB:向当前行右边移动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语句:

PRINT fmt,io_list !向标准输出设备写入数据

PRINTWRITE的区别是前者没有设置输出设备的能力,只能针对标准设备输出。今后输出统一用更灵活的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 10BACKSPACE 10ENDFILE 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)。在内部文件中,READWRITE操作发生在内部字符缓冲区中,而不是磁盘文件中。

例:在使用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操作中,如果程序使用WRITEREAD语句向文件中写入数据或从文件中读取数据,那么程序执行到WRITEREAD语句处就会暂停执行,直到数据全部写完或读完,这就是同步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.txtdata2.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

没有SEQUENCEBIND(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对应anum2对应bCOMMON还可以在变量很多的时候进行分组:

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值