目录

  • 第5章 I/O操作
    • 5.1 格式描述符
    • 5.2 OPEN语句
    • 5.3 CLOSE语句
    • 5.4 INQUIRE语句
    • 5.5 READ语句
    • 5.6 WRITE语句
    • 5.7 文件定位语句
    • 5.8 I/O名称列表
    • 5.9 三种文件访问方式
    • 5.10 内部文件
    • 5.11 异步I/O
  • 第6章 指针
    • 6.1 指针和目标变量
    • 6.2 用指针改善程序性能
    • 6.3 动态内存分配
    • 指针与函数
    • 串行结构
  • 第7章 面向对象
    • 7.1 派生数据类型
    • 7.2 CLASS保留字
    • 7.3 类和对象
    • 1313123
  • 数据结构与算法
  • 其他

第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 mainIMPLICIT NONEINTEGER(KIND = 4) :: iINTEGER(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)□=□8WRITE(*,"(*(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 mainIMPLICIT NONEINTEGER(KIND = 4) :: a,b,ca = 1;b = 2;c = 3WRITE(*,"('Please enter a, b, c: ')")READ(*,*) a,b,c !输入:10 ,, 30    WRITE(*,*) a,b,c !10 2 30a = 1;b = 2;c = 3WRITE(*,"('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 mainIMPLICIT NONE INTEGER(KIND = 4) :: a = 1,b = 2CHARACTER(LEN = 10) :: str = "wibibaboo"INTEGER(KIND = 4),DIMENSION(3) :: vec = [1,2,3]NAMELIST /my_list/ a,b,str,vecOPEN(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 mainIMPLICIT NONE INTEGER(KIND = 4) :: a = 1,b = 2CHARACTER(LEN = 10) :: str = "wibibaboo"INTEGER(KIND = 4),DIMENSION(3) :: vec = [1,2,3]NAMELIST /my_list/ a,b,str,vecOPEN(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 mainIMPLICIT NONETYPE :: studentCHARACTER(LEN = 10) :: nameINTEGER(KIND = 4) :: ageEND TYPE student!I/O单元号通常定义成常量,在编写大型程序时,有助于修改以及分清文件!I/O单元号在整个程序中是共享的,不同的函数可以使用同样的I/O单元号来操作文件INTEGER(KIND = 4),PARAMETER :: file_id = 10CHARACTER(LEN = 20) :: file_name = "e:\data.dat"INTEGER(KIND = 4) :: iTYPE(student),DIMENSION(4) :: studentsstudents = [student("Jerry",18),student("Tom",19),student("Rose",20),student("Mike",21)]OPEN(UNIT = file_id,FILE = file_name,STATUS = "NEW")DO i = 1,4WRITE(UNIT = file_id,FMT = "(A8,I3)") students(i)%name,students(i)%ageEND DOCLOSE(UNIT = file_id)!data.dat文件包含的数据信息:!Jerry    18!Tom      19!Rose     20!Mike     21
END PROGRAM main
!读取文件并将其中的数据输出在屏幕上
PROGRAM mainIMPLICIT NONEINTEGER(KIND = 4),PARAMETER :: file_id = 10CHARACTER(LEN = 79) :: file_nameCHARACTER(LEN = 79) :: buffer!这里之所以限制读取字符串的长度为79是因为在标准的DOS及Windows的cmd窗口下,!一行只能显示80个字符,所以读太多字符在cmd窗口就显示不出来了,!但是如果刚好输出80个字符,有的编译器所编译出来的程序会发生断行的现象,所以最好是79INTEGER(KIND = 4) :: read_stat = 0LOGICAL(KIND = 4) :: is_existWRITE(*,*) "Please enter file name: "READ(*,"(A79)") file_nameINQUIRE(FILE = file_name,EXIST = is_exist)IF(.NOT.is_exist) THENWRITE(*,"('File ""',A,'"" doesn''t exist! ')") TRIM(file_name)STOPEND IFOPEN(UNIT = file_id,FILE = file_name,ACCESS = "SEQUENTIAL",STATUS = 'OLD')DO WHILE(.TRUE.)READ(UNIT = file_id,FMT = "(A79)",IOSTAT = read_stat) bufferIF(read_stat /= 0) EXITWRITE(*,"(A79)") bufferEND DOCLOSE(UNIT = file_id)
END PROGRAM main

直接访问文件中的记录可以按照任意顺序来访问,这对于需要以任意顺序读取的信息比如数据库文件的读取非常有用。操作直接访问文件的关键是文件中的每条记录的长度必须相等,因为当记录等长的时候,那么就可以精确计算磁盘文件中第i条记录的位置,这样就可以直接读取含有该条记录的磁盘扇区,而不需要读取该记录前的其他所有扇区。

直接访问文件可以通过在OPEN语句中指定ACCESS = "DIRECTED"的方法来打开,每个记录的长度使用RECL=子句来指明,对于格式化文件来说,必须指定FORM = "FORMATTED",因为直接访问文件的默认格式是"UNFORMATTED"

PROGRAM mainIMPLICIT NONEINTEGER(KIND = 4) :: iINTEGER(KIND = 4) :: irecCHARACTER(LEN = 20) :: bufferOPEN(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) iEND 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)") irecREAD(UNIT = 10,FMT = "(A)",REC = irec) bufferWRITE(*,"('The record is: ',/,A)") bufferCLOSE(UNIT = 10)
END PROGRAM main

例:为了比较格式化和未格式化的直接访问文件的操作以及文件的大小,创建两个各自包含50000条记录的文件,每个文件的每条记录都有四个双精度实型数据。

PROGRAM mainIMPLICIT NONEINTEGER(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 = 50000INTEGER(KIND = 4) :: i,j,irecINTEGER(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 !84WRITE(*,"('The unformatted record length is ',I)") length_unf !8OPEN(UNIT = 10,FILE = "data.fmt",ACCESS = "DIRECT",FORM = "FORMATTED",STATUS = "REPLACE",RECL = length_fmt) !data.fmt大小为4102KBOPEN(UNIT = 20,FILE = "data.unf",ACCESS = "DIRECT",FORM = "UNFORMATTED",STATUS = "REPLACE",RECL = length_unf) !data.unf大小为1563KB!向文件填充数据DO i = 1,MAX_RECORDSDO j = 1,4CALL random_seed()CALL random_number(rnum)records(j) = 30.0_DBL * rnumEND DOWRITE(UNIT = 10,FMT = "(4ES21.14)",REC = i) records !21×4=84WRITE(UNIT = 20,REC = i) recordsEND DOCLOSE(UNIT = 10)CLOSE(UNIT = 20)
END PROGRAM main

流访问文件按照字节读写文件,每次读写一个字节,并且不处理其中的特殊字符,比如回车、换行等。使用一系列的WRITE语句可以将数据写入文件,当想要结束一行时,需要将“新行符”(类似于C语言中的\n)写入文件,Fortran有一个内置函数new_line,可以实现这一功能:

PROGRAM mainIMPLICIT NONEOPEN(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 mainCHARACTER(LEN = 10) :: temp_strINTEGER(KIND = 4) :: i,int_dataLOGICAL(KIND = 4) :: flag = .TRUE.DO WHILE(flag)flag = .FALSE.WRITE(*,*) "Please enter an integer: "READ(*,"(A10)") temp_strDO i = 1,LEN_TRIM(temp_str)IF(LLT(temp_str(i:i),"0") .OR. LGT(temp_str(i:i),"9")) THENWRITE(*,"('Error input, please reenter!')")flag = .TRUE.EXITEND IFEND DOEND DOREAD(temp_str,"(I)") int_data !将temp_str读取为整数WRITE(*,*) int_data
END PROGRAM main

例:输出格式可以事先放在字符串中,程序运行时,动态改变字符串的内容就可以达到动态改变输出格式的目的。

PROGRAM mainIMPLICIT NONEINTEGER(KIND = 4) :: a = 1,b = 2CHARACTER(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 mainIMPLICIT NONEREAL(KIND = 4),POINTER :: p1 => NULL(),p2 => NULL(),p3 => NULL()REAL(KIND = 4),TARGET :: a = 1.0,b = 2.0,cp1 => ap2 => bp3 => cp3 = p1 + p2 !相当于c=a+b;p1、p2、p3出现在了目标变量(普通变量)应该出现的位置,它们都断开引用p2 => p1 !相当于p2=>a;p1出现在了目标变量(普通变量)应该出现的位置,p1断开引用
END PROGRAM main

指针不仅能指向数组,而且能指向数组子集(部分数组),任何由下标三元组定义的部分数组(不能用于向量下标所定义的部分数组)都可以用作指针的目标变量:

PROGRAM mainIMPLICIT NONEINTEGER(KIND = 4) :: iINTEGER(KIND = 4),DIMENSION(16),TARGET :: targt_vec = [(i,i=1,16)]INTEGER(KIND = 4),DIMENSION(:),POINTER :: p1,p2,p3,p4,p5p1 => targt_vecp2 => p1(2::2)p3 => p2(2::2)p4 => p3(2::2)p5 => p4(2::2)WRITE(*,"('p1 = ',16I3)") p1WRITE(*,"('p2 = ',8I3)") p2WRITE(*,"('p3 = ',4I3)") p3WRITE(*,"('p4 = ',2I3)") p4WRITE(*,"('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 :: personCHARACTER(LEN = 10) nameREAL(KIND = 4) :: heightREAL(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 mainIMPLICIT NONEINTEGER,POINTER :: pALLOCATE(p) !配置一块内存空间给指针变量pp = 1 !得到内存后的指针变量p可以像整型数一样使用WRITE(*,*) p !1DEALLOCATE(p) !释放掉配置的内存
END PROGRAM main

另外,如果指针指向变量,指针可以随时改变它的指向;但是如果指针是用ALLOCATE配置的,在改变指向前需要将它DEALLOCATE掉。

指针与函数

指针可以作为函数参数和函数返回值,这时必须要写函数的接口块,并且指针参数在声明时不能有INTENT属性。

PROGRAM mainIMPLICIT NONEINTERFACEFUNCTION get_min(pointer_arr) RESULT(min)INTEGER,POINTER,DIMENSION(:) :: pointer_arrINTEGER,POINTER :: minINTEGER :: i,lenthEND FUNCTION get_minEND INTERFACEINTEGER,TARGET,DIMENSION(5) :: target_arr = (/2,3,1,5,4/)INTEGER,POINTER,DIMENSION(:) :: pointer_arrpointer_arr => target_arrWRITE(*,*) get_min(pointer_arr)
END PROGRAM mainFUNCTION get_min(pointer_arr) RESULT(min)IMPLICIT NONEINTEGER,POINTER,DIMENSION(:) :: pointer_arr !pointer_arr指针作为函数参数INTEGER,POINTER :: min !min指针作为函数返回值INTEGER :: i,lenthlenth = SIZE(pointer_arr,1) !获取矩阵的列数(这里就是获取p的长度)min => pointer_arr(1) !注意不能写成“=”DO i = 2,lenthIF(min > pointer_arr(i)) THENmin => pointer_arr(i)END IFEND DO
END FUNCTION get_min

之前讲过,可以将函数封装在模块中的,减少编写接口块带来的麻烦:

MODULE func_moduleIMPLICIT NONECONTAINSFUNCTION get_min(pointer_arr) RESULT(min)INTEGER,POINTER,DIMENSION(:) :: pointer_arrINTEGER,POINTER :: minINTEGER :: i,lenthlenth = SIZE(pointer_arr,1)min => pointer_arr(1)DO i = 2,lenthIF(min > pointer_arr(i)) THENmin => pointer_arr(i)END IFEND DOEND FUNCTION get_minEND MODULE func_modulePROGRAM mainUSE func_moduleIMPLICIT NONEINTEGER,TARGET,DIMENSION(5) :: target_arr = (/2,3,1,5,4/)INTEGER,POINTER,DIMENSION(:) :: pointer_arrpointer_arr => target_arrWRITE(*,*) get_min(pointer_arr)
END PROGRAM main

串行结构

接下来的内容,将介绍指针的一个非常常见的应用——“串行(háng)”。串行是指多个同类的东西连接在一起。先来看一个单向串行的程序:

MODULE type_defIMPLICIT NONETYPE nodeINTEGER :: content !节点存储的内容TYPE(node),POINTER :: next !指向下一个节点的指针END TYPE node
END MODULE type_defPROGRAM mainUSE type_defIMPLICIT NONETYPE(node),POINTER :: node1,node2,node3ALLOCATE(node1)ALLOCATE(node2)ALLOCATE(node3)!将数据装入节点中node1%content = 10node2%content = 20node3%content = 30!创建单向串行node1%next => node2node2%next => node3node3%next => NULL() !node3没有下一个可以指向了,需要置空!根据串行的关系访问节点存储的数据WRITE(*,*) node1%content !10WRITE(*,*) node1%next%content !20,相当于node2%contentWRITE(*,*) node1%next%next%content !30,相当于node3%contentDEALLOCATE(node1)DEALLOCATE(node2)DEALLOCATE(node3)
END PROGRAM main

上面这个程序只是示范了串行创建的大概原理,实际上创建串行更多用到的是下面这种搭配循环的方法:

MODULE type_defIMPLICIT NONETYPE nodeINTEGER :: contentTYPE(node),POINTER :: nextEND TYPE node
END MODULE type_defPROGRAM mainUSE type_defIMPLICIT NONETYPE(node),POINTER :: head !串行头TYPE(node),POINTER :: p !访问串行节点的指针变量INTEGER :: i,nWRITE(*,*) "请输入节点数量:"READ(*,*) n!节点头的数据要先设定ALLOCATE(head)head%next => NULL()WRITE(*,*) "请输入第 1个节点数据:"READ(*,*) head%contentp => headDO i = 2,nALLOCATE(p%next)p => p%nextWRITE(*,"('请输入第',I2,'个节点数据:')") iREAD(*,*) p%contentEND DOp%next => NULL() !串行最后一个节点的下一个需要置空p => headDO WHILE(ASSOCIATED(p))WRITE(*,*) p%contentp => p%nextEND DO
END PROGRAM main

加强一下串行结构,使之成为可以沿前后两个方向移动的双向串行

MODULE type_defIMPLICIT NONETYPE nodeINTEGER :: contentTYPE(node),POINTER :: previous !指向上一个节点的指针TYPE(node),POINTER :: next !指向下一个节点的指针END TYPE node
END MODULE type_defPROGRAM mainUSE type_defIMPLICIT NONETYPE(node),POINTER :: head !串行头TYPE(node),POINTER :: tail !串行尾TYPE(node),POINTER :: p !访问串行节点的指针变量TYPE(node),POINTER :: p_tempINTEGER :: i,nWRITE(*,*) "请输入节点数量:"READ(*,*) n!节点头(或者节点尾)的数据要先设定ALLOCATE(head)head%previous => NULL()head%next => NULL()WRITE(*,*) "请输入第 1个节点数据:"READ(*,*) head%contentp => headDO i = 2,nALLOCATE(p%next)p_temp => pp => p%nextp%previous => p_tempWRITE(*,"('请输入第',I2,'个节点数据:')") iREAD(*,*) p%contentEND DOp%next => NULL()tail => p!正向输出p => headDO WHILE(ASSOCIATED(p))WRITE(*,*) p%contentp => p%nextEND DO!反向输出p => tailDO WHILE(ASSOCIATED(p))WRITE(*,*) p%contentp => p%previousEND DO
END PROGRAM main

目前为止所介绍的串行结构都是有头有尾的结构,串行结构还有另外一种类型叫做环状串行。它可以把串行的头尾连接起来,变成一个圈:

MODULE type_defIMPLICIT NONETYPE nodeINTEGER :: contentTYPE(node),POINTER :: previousTYPE(node),POINTER :: nextEND TYPE node
END MODULE type_defPROGRAM mainUSE type_defIMPLICIT NONETYPE(node),POINTER :: node1,node2,node3TYPE(node),POINTER :: pINTEGER :: n = 6 !环状串行可以一直向前或者向后抓取数据,n可以设置成任意大小INTEGER :: iALLOCATE(node1)ALLOCATE(node2)ALLOCATE(node3)node1 = node(10,node3,node2)node2 = node(20,node1,node3)node3 = node(30,node2,node1)!正向输出p => node1DO i = 1,nWRITE(*,*) p%contentp => p%nextEND DO!反向输出p => node3DO i = 1,nWRITE(*,*) p%contentp => p%previousEND DODEALLOCATE(node1)DEALLOCATE(node2)DEALLOCATE(node3)
END PROGRAM main

利用串行可以实现快速地插入或删除一条数据:

MODULE nodes_moduleIMPLICIT NONETYPE nodeINTEGER :: contentTYPE(node),POINTER :: previousTYPE(node),POINTER :: nextEND TYPE nodeCONTAINS!在pos之前插入new_node(pos%previous、new_node、pos)SUBROUTINE insert_before_node(pos,new_node)TYPE(node),POINTER :: posTYPE(node),POINTER :: new_nodenew_node%next =>posnew_node%previous => pos%previousIF(ASSOCIATED(pos%previous)) pos%previous%next => new_nodepos%previous => new_nodeEND SUBROUTINE insert_before_node!在pos之后插入new_node(pos、new_node、pos%next)SUBROUTINE insert_after_node(pos,new_node)TYPE(node),POINTER :: posTYPE(node),POINTER :: new_nodenew_node%next => pos%nextnew_node%previous => posIF(ASSOCIATED(pos%next)) pos%next%previous => new_nodepos%next => new_nodeEND SUBROUTINE insert_after_node!删除pos节点(pos%previous、pos、pos%next)SUBROUTINE delete_node(pos)TYPE(node),POINTER :: posTYPE(node),POINTER :: previousTYPE(node),POINTER :: nextprevious => pos%previousnext => pos%nextDEALLOCATE(pos)!重新连接pos释放后的前后两个节点需要注意两点问题:!1.如果pos是第一条数据,不需要向前重连!2.如果pos是最后一条数据,不需要向后重连IF(ASSOCIATED(previous)) previous%next => nextIF(ASSOCIATED(next)) next%previous => previouspos => nextEND SUBROUTINE delete_node!输出串行SUBROUTINE print_nodes(head)TYPE(node),POINTER :: headTYPE(node),POINTER :: pp => headDO WHILE(ASSOCIATED(p))WRITE(*,*) p%contentp => p%nextEND DOEND SUBROUTINE print_nodes!释放整个串行的内存SUBROUTINE delete_nodes(head)TYPE(node),POINTER :: head,p_tempDO WHILE(ASSOCIATED(head))p_temp => head%nextDEALLOCATE(head)head => p_tempEND DOEND SUBROUTINE delete_nodesEND MODULE nodes_modulePROGRAM mainUSE nodes_moduleIMPLICIT NONETYPE(node),POINTER :: headTYPE(node),POINTER :: posTYPE(node),POINTER :: p,p_tempINTEGER i,nWRITE(*,*) "请输入节点数量:"READ(*,*) nALLOCATE(head)head%previous => NULL()head%next => NULL()WRITE(*,*) "请输入第 1个节点数据:"READ(*,*) head%contentp => headDO i = 2,nALLOCATE(p%next)p_temp => pp => p%nextp%previous => p_tempWRITE(*,"('请输入第',I2,'个节点数据:')") iREAD(*,*) p%contentEND DOp%next => NULL()CALL print_nodes(head)!删掉第三条数据CALL delete_node(head%next%next)CALL print_nodes(head)!在第三个位置插入数据ALLOCATE(pos)pos%content = 30CALL insert_after_node(head%next,pos)CALL print_nodes(head)!释放整个串行的内存CALL delete_node(head)
END PROGRAM main

在读取文件时,有时候我们事先不确定文件中会有多少条数据,就不能用数组来读取,因为不知道数组该声明成多大,这时可以用串行来很好地解决这个问题。现在有两个文件数据data1.txtdata2.txt记录了两个人数不同的班级学生的考试成绩,截取其中一部分如下图所示,现在要编写一个可以读取成绩的程序,让用户输入文件名来决定要读取哪一个文件,还要提供给用户通过座位号来查询成绩的功能。

MODULE nodes_moduleIMPLICIT NONETYPE studentINTEGER :: idINTEGER :: chineseINTEGER :: mathINTEGER :: englishEND TYPE studentTYPE nodeTYPE(student) :: sTYPE(node),POINTER :: nextEND TYPE nodeCONTAINS!根据id查找学生并返回串行位置FUNCTION search_student(head,id) RESULT(pos)TYPE(node),POINTER :: headINTEGER :: idTYPE(node),POINTER :: pospos => NULL()DO WHILE(ASSOCIATED(head))IF(head%s%id == id) THENpos => headRETURNEND IFhead => head%nextEND DOEND FUNCTION search_studentEND MODULE nodes_modulePROGRAM mainUSE nodes_moduleIMPLICIT NONECHARACTER(20) :: file_nameCHARACTER(79) :: header !记录文件的第一行TYPE(node),POINTER :: head,p,posINTEGER :: id,n,read_state = 0WRITE(*,*) "请输入文件名:"READ(*,*) file_nameOPEN(10,file = file_name,STATUS = 'old')READ(10,"(A79)") headerALLOCATE(head)head%next => NULL()READ(10,*) head%sp => headn = 0 !记录学生人数DO WHILE(read_state == 0)ALLOCATE(p%next)p => p%nextn = n + 1READ(10,FMT = *,IOSTAT = read_state) p%sEND DOWRITE(*,"('一共有',I2,'名学生。')") nWRITE(*,*) "请输入想要查询的学生的ID:"READ(*,*) idpos => search_student(head,id)IF(ASSOCIATED(pos)) THENWRITE(*,"('查询结果如下:',/,A79)") headerWRITE(*,"(4(I3,2X))") pos%s%id,pos%s%chinese,pos%s%math,pos%s%englishELSEWRITE(*,*) "未找到该学生!"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 mainIMPLICIT NONETYPE :: PersonCHARACTER(LEN = 10) :: nameINTEGER(KIND = 4) :: ageEND TYPE PersonTYPE(Person) :: p1 = Person("Tom", 18)TYPE(Person) :: p2p2%name = "Jerry" !派生数据类型的成员用%或.访问,出于本人习惯,今后统一用%p2%age = 18
END PROGRAM main

当Fortran编译器为派生数据类型的变量分配内存空间时,编译器并不需要为该类型变量的每个元素分配连续的空间。事实上,它们在内存中的位置是随机的,只要能够保证I/O操作时元素之间保持原有的顺序即可。然而,有时如果想要将派生数据类型的变量传给由其他语言编写的过程,就必须严格限制该变量各元素的内存顺序,这时可以在类型定义中使用SEQUENCE语句,使得派生数据类型的元素被放在连续的内存空间中:

TYPE :: VectorSEQUENCEINTEGER(KIND = 4) :: xINTEGER(KIND = 4) :: yINTEGER(KIND = 4) :: z
END TYPE Vector

正如Fortran允许多种整数或实数类别,用户也可以使用参数定义派生数据类型,这种方式叫做参数化派生数据类型。有两种参数可以用来定义派生数据类型,第一种在编译时已知(称为类别类型参数),另一种在运行时获取(称为长度类型参数),它们对应的形式参数值被称为哑元值

PROGRAM mainIMPLICIT NONEINTEGER,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) :: vEND TYPE VectorTYPE(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_pointIMPLICIT NONETYPE :: PointREAL(KIND = 4) :: xREAL(KIND = 4) :: yCONTAINSPROCEDURE,PASS :: add !默认PROCEDURE,NOPASS :: minusEND TYPE PointCONTAINSTYPE(Point) FUNCTION add(this_point,another_point)IMPLICIT NONECLASS(Point),INTENT(IN) :: this_pointCLASS(Point),INTENT(IN) :: another_pointadd%x = this_point%x + another_point%xadd%y = this_point%y + another_point%yEND FUNCTION addTYPE(Point) FUNCTION minus(this_point,another_point)IMPLICIT NONECLASS(Point),INTENT(IN) :: this_pointCLASS(Point),INTENT(IN) :: another_pointminus%x = this_point%x - another_point%xminus%y = this_point%y - another_point%yEND FUNCTION minus
END MODULE module_for_pointPROGRAM mainUSE module_for_pointIMPLICIT NONETYPE(Point) :: p1,p2,p3,p4p1 = Point(1.0,2.0)p2 = Point(3.0,4.0)p3 = p1%add(p2)p4 = p1%minus(p1,p2)WRITE(*,*) p3WRITE(*,*) p4
END PROGRAM main

PASS属性的存在使得调用add过程的Point类型变量被当作第一调用参数自动传递到这一过程,与之对应的时NOPASS属性。过程的实现必须写在和派生数据类型定义相同的模块中并且派生数据类型必须使用CLASS关键字声明。

7.2 CLASS保留字

在常规的Fortran程序中,过程中形参的类型和调用时相应的实参应该完全匹配,指针类型和它所指向的类型应该完全匹配,可分配变量和相应的的数据也应该完全匹配,否则会出错。CLASS保留字以一种特殊的方式放宽了这个要求:如果一个可分配数据项、指针、形参用CLASS(Type_name)声明,那么数据项将与数据类型以及该数据类型的所有扩展相匹配:

TYPE :: Point_2DREAL(KIND = 4) :: xREAL(KIND = 4) :: y
END TYPE Point_2D
TYPE,EXTENDS(Point_2D) :: Point_3DREAL(KIND = 4) :: z
END TYPE Point_3DTYPE(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_pointIMPLICIT NONETYPE,PUBLIC :: ComplxPRIVATEREAL(KIND = 4) :: rREAL(KIND = 4) :: iCONTAINSPROCEDURE :: add => addPROCEDURE :: minusEND TYPE ComplxCONTAINSTYPE(Point) FUNCTION add(this_point,another_point)IMPLICIT NONECLASS(Point),INTENT(IN) :: this_pointCLASS(Point),INTENT(IN) :: another_pointadd%x = this_point%x + another_point%xadd%y = this_point%y + another_point%yEND FUNCTION addTYPE(Point) FUNCTION minus(this_point,another_point)IMPLICIT NONECLASS(Point),INTENT(IN) :: this_pointCLASS(Point),INTENT(IN) :: another_pointminus%x = this_point%x - another_point%xminus%y = this_point%y - another_point%yEND FUNCTION minus
END MODULE module_for_pointPROGRAM mainUSE module_for_pointIMPLICIT NONETYPE(Point) :: p1,p2,p3,p4p1 = Point(1.0,2.0)p2 = Point(3.0,4.0)p3 = p1%add(p2)p4 = p1%minus(p1,p2)WRITE(*,*) p3WRITE(*,*) p4
END PROGRAM main

例:开发软件时,确定执行某一段特定程序需要花费多长时间是非常有用的,这样可以帮助我们找出代码中的“热点”,也就是那些程序中花费大量时间的地方,这样就可以对它们进行优化,这个通常由计时器来完成。计时器作为一个对象,类似于一个秒表,用来计算从按下开始按键到按下结束按键之间所经过的时间。

MODULE module_for_timerIMPLICIT NONEINTEGER,PARAMETER :: DBL = SELECTED_REAL_KIND(P=14)TYPE,PUBLIC :: TimerPRIVATE !PRIVATE属性使得构造函数不能初始化time,必须用用户自定义的方法来初始化REAL(KIND = DBL) :: time !sCONTAINSPROCEDURE,PUBLIC :: start_timer => start_timer_sub !默认为PASS属性PROCEDURE,PUBLIC :: elapsed_time => elapsed_time_funcEND TYPE TimerPRIVATE :: start_timer_sub,elapsed_time_func !限制对实际子例程的访问CONTAINSSUBROUTINE start_timer_sub(this)IMPLICIT NONECLASS(Timer),INTENT(INOUT) :: thisINTEGER(KIND = 4),DIMENSION(8) :: valueCALL 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_subREAL(KIND = DBL) FUNCTION elapsed_time_func(this)IMPLICIT NONECLASS(Timer),INTENT(IN) :: thisINTEGER(KIND = 4),DIMENSION(8) :: valueCALL 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%timeEND FUNCTION elapsed_time_func
END MODULE module_for_timer
PROGRAM mainUSE module_for_timerIMPLICIT NONEINTEGER(KIND = 4) i,j,kTYPE(Timer) :: tCALL t%start_timer() !PASS属性的存在使得调用此过程的Timer类型变量被当作第一调用参数自动传递到这一过程DO i = 1,100000DO j = 1,100000k = i + jEND DOEND DOWRITE(*,"('Time = ',F5.3,' s')") t%elapsed_time() !Time = 3.381 s
END PROGRAM main

1313123

没有SEQUENCEBIND(C)属性的自定义数据类型是可以扩展的,这就有点类似于C++的继承关系:

PROGRAM mainIMPLICIT NONETYPE :: Point_2DREAL(KIND = 4) :: xREAL(KIND = 4) :: yEND TYPE Point_2DTYPE,EXTENDS(Point_2D) :: Point_3DREAL(KIND = 4) :: zEND TYPE Point_3DTYPE(Point_3D) :: pp%x = 1.0 !或者p%Point_2D%xp%y = 2.0 !或者p%Point_2D%yp%z = 3.0WRITE(*,*) 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 - 1DO j = i + 1,sizeIF( a(i) > a(j) ) THENtemp = a(i)a(i) = a(j)a(j) = tempEND IFEND 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 mainIMPLICIT NONE!INTEGER,COMMON :: a,b !错误写法INTEGER :: a,bCOMMON a,ba = 1b = 2CALL showCommon() !□□1□□2
END PROGRAM mainSUBROUTINE showCommon()IMPLICIT NONEINTEGER :: num1,num2COMMON num1,num2WRITE(*,"(2I3)") num1,num2
END SUBROUTINE showCommon

取用全局变量时,是根据它们声明时相对位置关系来做对应,而不是使用变量名称来对应。所以在上面这个程序中num1对应anum2对应bCOMMON还可以在变量很多的时候进行分组:

PROGRAM mainIMPLICIT NONEINTEGER :: a,b,c,d,e,fCOMMON /group1/ aCOMMON /group2/ bCOMMON /group3/ cCOMMON /group4/ dCOMMON /group5/ eCOMMON /group6/ ff = 1CALL show_common_6() !□□1
END PROGRAM mainSUBROUTINE show_common_6()IMPLICIT NONEINTEGER :: num6COMMON /group6/ num6WRITE(*,"(I3)") num6
END SUBROUTINE show_common_6

COMMON变量还可以用BLOCK DATA程序模块中的DATA语句批量赋值。

PROGRAM mainIMPLICIT NONEINTEGER :: a,bCOMMON a,bINTEGER :: c,dCOMMON /group/ c,dWRITE(*,"(4(I3))") a,b,c,d
END PROGRAM mainBLOCK DATA assign_common_variableIMPLICIT NONEINTEGER a,bCOMMON a,bDATA a,b /1,2/INTEGER c,dCOMMON /group/ c,dDATA c,d /3,4/
END BLOCK DATA assign_common_variable

BLOCK DATA这一段程序也很类似于子程序。它也是一段独立的程序模块,也拥有自己的变量声明,不过它不需要被别人调用就可以自己执行。事实上这一段程序会在主程序执行前就会生效,不过它的功能只在于设置全局变量的初值,不能有其他执行命令的出现。还有一点需要指出,全局变量不能声明成常量,所以BLOCK DATA中不能出现PARAMETER

PROGRAM mainIMPLICIT NONECALL head_sub() !Hello\World!CALL mid_sub() !World!
END PROGRAM mainSUBROUTINE head_sub()IMPLICIT NONEWRITE(*,*) "Hello!"ENTRY mid_sub() !另一个入口WRITE(*,*) "World!"
END SUBROUTINE head_sub
PROGRAM mainIMPLICIT NONEINTEGER :: aWRITE(*,*) "请随便输入一个数:"READ(*,*) aCALL judge_number(a,*100,*200,*300) !指定了三个折返点
100 WRITE(*,*) "Negative."STOP
200 WRITE(*,*) "Zero."STOP
300 WRITE(*,*) "Positive."
END PROGRAM mainSUBROUTINE judge_number(num,*,*,*)IMPLICIT NONEINTEGER :: numIF(num < 0) THENRETURN 1 !返回第一个折返点ELSE IF(num == 0) THENRETURN 2 !返回第二个折返点ELSERETURN 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 mainIMPLICIT NONEINTEGER :: a = 1,bEQUIVALENCE(a,b)WRITE(*,*) a,b !a和b的值都为1b = 2WRITE(*,*) a,b !a和b的值都变为2
END PROGRAM main

Fortran有一个叫做ASSOCIATE的结构,可以在一个代码段的执行过程中,临时将变量或表达式和某个名字关联,简化拥有长名字或长表达式的代码段。举个例子,假设雷达在跟踪一系列目标,每个目标的坐标都存于Trak_file的数据结构中,雷达本身的坐标存于Rader_loc的数据结构中,现在要计算跟踪到的某个目标的距离和方位:

MODULE type_defIMPLICIT NONETYPE :: Track_fileREAL(KIND = 8) :: x !目标的横坐标(m)REAL(KIND = 8) :: y !目标的纵坐标(m)REAL(KIND = 8) :: dist !离目标的距离(m)REAL(KIND = 8) :: bearing !目标的方位(rad)END TYPE Track_fileTYPE :: Radar_locREAL(KIND = 8) :: x !雷达的横坐标(m)REAL(KIND = 8) :: y !雷达的纵坐标(m)END TYPE Radar_loc
END MODULE type_defPROGRAM mainUSE type_defIMPLICIT NONETYPE(Track_file) :: track = Track_file(1.0,2.0,0.0,0.0) !距离和方位暂时记为0.0TYPE(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

Fortran语法汇总(下)(持续更新中)相关推荐

  1. 有关树的常见算法汇总【持续更新中】

    关于数据结构中--树的算法汇总[持续更新中] 0.树的顺序和链式存储结构 [完成] 1.树的前序遍历(递归和非递归java实现) [完成] 2.树的中序遍历(递归和非递归java实现) [完成] 3. ...

  2. Cisco 产品下载链接汇总 2023 持续更新中

    Cisco 产品链接汇总 2023 持续更新中 IOS-XE, IOS-XR, NX-OS & FXOS based on linux kernel 请访问原文链接:https://sysin ...

  3. 《LeetCode 热题 HOT 100》Java答案汇总版---持续更新中

    <LeetCode 热题 HOT 100>Java答案汇总版-持续更新中 个人认为<LeetCode 热题 HOT 100>中的题目特别适合算法新手进行一个入门的刷题,而且作者 ...

  4. 开源工业缺陷数据集汇总,持续更新中(已更新28个)

    欢迎大家关注我的公众号:一刻AI 本文目前汇总了常见的28个开源工业缺陷数据集,持续更新中 (欢迎大家留言补充,共同建设一个为大家提供便利的文章) 东北大学热轧带钢表面缺陷数据集 官方链接:Visio ...

  5. C++学习资源汇总(持续更新中)

    以下收集汇总一些C++的学习资料(持续更新中) 网站和论坛: http://www.csdn.net/ http://www.iteye.com/ http://www.bccn.net/  编程中国 ...

  6. 清华2021计算机学院复试,清华大学2021年硕士研究生复试名单汇总(持续更新中)...

    清华大学2021年硕士研究生复试名单汇总已出来,下面金程考研小编整理了:清华大学2021年硕士研究生复试名单汇总 (持续更新中),希望对同学有帮助~ 加小助手微信(备注网校)jckyyxm领取历年考研 ...

  7. 猴子都能懂得Git(入门篇汇总版)持续更新中~~~

    文章目录 前言 一.Git的基础 1.Git是什么? 2.管理历史记录的数据库 远程数据库和本地数据库 创建数据库 修改记录的提交 工作树和索引 2.安装Git 初期设定 新建数据库 提交文件 pus ...

  8. STM32 之十五 奇怪问题处理及驱动库 BUG 汇总(持续更新中)

      在使用 STM32 的 MCU 开发过程中,难免遇到各种各样的奇葩问题.或许是开发环境的问题,或许是 MCU 使用的问题,也或许是驱动库的 BUG 等等.这些问题可能不局限于某一种具体型号的 MC ...

  9. CVPR 2022 论文/代码分类汇总!持续更新中!

    关注公众号,发现CV技术之美 CVPR 2022 的论文官方还没有完全公布,但有作者陆续公布出来一些.为方便大家跟进论文,了解最新技术,CV君在Github建了一个仓库,对已经出来的论文(目前是340 ...

  10. CVPR 2021 论文/代码分类汇总!持续更新中!

    CVPR 2021 的论文官方还没有完全公布,但有作者陆续公布出来一些.为方便大家跟进论文,了解最新技术,CV君在Github建了一个仓库,对已经出来的论文(目前是340多篇)进行了按类别汇总.对于O ...

最新文章

  1. 遍历百万级Redis的键值的续集
  2. 王者争雄服务器维护,王者争雄_王者争雄官网_攻略-第一手游网
  3. C语言经典例80-猴子分桃
  4. 云计算背后的秘密(1)-MapReduce
  5. mysql如何优化where子句
  6. python逐行读取txt文件readline_Python - 无法读取整个.txt文件:.readlines错误?
  7. java实现使用JDBC-ODBC桥操作数据库。
  8. Android studio 如何导入并引用Library工程
  9. 1e-5 java_内功心法 -- java.util.LinkedListE (5)
  10. IAR_STM32_BootLoader
  11. 三桥君:如何把SQL Server的数据库导为sql文件
  12. 计算机故障诊断知识,故障诊断
  13. Mac终端神器iTerm2配置(oh-my-zsh+shell integration+Powerlevel9k)
  14. 异常检测——线性模型
  15. 陈省身文集51——闭黎曼流形高斯-博内公式的一个简单的内蕴证明
  16. python设置默认utf8编码_Python设置默认编码为utf8的方法
  17. phx.gen.html 生成器
  18. vcs -xprop的理解
  19. 使用@PersistenceContext获取EntityManager报NullPointerException异常
  20. MySQL 基础入门_04SQL基础

热门文章

  1. 【word 应用篇】word如何插入页码以及word插入图片显示不全的解决方法
  2. NSSCTF刷题wp——Crypto入门
  3. mysql定时任务,每天凌晨1点执行
  4. 实在智能CEO孙林君:以AI与RPA的深度融合为核心,打造真正人人可用的超级自动化平台 | 数据猿专访...
  5. 以post的方式发请求,传参在url中
  6. 极速进阶,小i智慧学堂联合复旦大学教授推出人工智能冬令营
  7. 固定光束扫描器行业研究及十四五规划分析报告
  8. 信息收集之外网信息收集
  9. 卡尔曼滤波模型及Matlab模型建立
  10. 4. “随机漫步的傻瓜--纳西姆.尼古拉斯.塔勒布”读后感