Fortran基础课程学习笔记
author: Mr.Bear
date: 2022/9/19
注:参考代码是用锚点链接的,文章里显示不了,懒得改了,需要的可以拿.html笔记文件
所有课程内容和代码文件均来自 http://www. fcode.cn/ ,可自行下载
课程链接
1. Fortran语言简介
1.1 Fortran是什么?
Fortran是一门 编译型 的编程 语言 ,特长是 科学计算 。
1.2 Fortran的特点
- 编译型 语言,执行效率高;
- 语法严格,是和严谨的科学计算;
- 相比于C++更易掌握,接近自然的 数学 语言;
- 矩阵 运算强大,自带 复数 运算;
- 并行 运算的最佳选择。
- 可视化开发性能差;
- 比较底层,各类算法需要自己实现。
1.3 Fortran的主要版本
F66、F77:早期版本,固定格式
F90、F95:动态数组,模块化,结构体
F2003、F2008:面向对象
1.4 Fortran的开发工具
集成开发环境(IDE):简单易学,可视化操作;工程管理;不易于交流,不易于理解概念
命令行:生涩难学,纯字符界面;makefile管理;易于交流,理解概念
不同的编译器之间差距可能较大。为提高代码的可移植性,应尽量符合语法规范,避免使用平台扩展语法。
1.5 Fortran教材
《 Fortran95 程序设计》 《 Morden Fortran Explained 》 …
1.6 Fortran学习建议
- 直接学习 Fortran90+ ;
- 注意书写格式,使用 大小写混合 的 自由格式 、书写 Implicit None ;
- 解决实际的 数学 、 物理 问题;
- 保存写过的程序 ,回顾并尝试重写以前写的程序,将 新知识 融入进去;
- 经常在网络上 交流 ,善用网络 资源 。
1.7 Fortran网络资源
可利用的一些资源:
- Fortran Wiki
- Fortran-lang
- 中文Fortran网站、论坛
- Fortran简洁教程
- Fortran常用函数
- 众多函数库、软件包(netlib)
- 开源项目管理(sourceforge)
- 代码链接、工具(付费)
1.8 小结
- 养成良好的编写习惯,包括语法、结构和书写等,提高代码的可读性和可移植性;
- 入门可以先使用IDE工具,有一定基础后使用命令行工具;
- 善于利用各类网络资源,多积累、多总结。
2. 常见编译环境的操作
2.1 编译和链接
编译:针对源代码,输出为目标代码
链接:针对一个或多个目标代码、库文件,输出为可执行程序
.f90→编译→.o→+routime.lib(运行时库) 链接→.exe(可执行程序)→运行
编译和链接是开发者的工作,开发者同时也要充当用户运行程序。
2.2 工程管理
工程,用于管理大量代码的一种工具,规定源代码的编译、链接的参数及代码间的依赖关系。 命令行编译方式一般采用makefile进行项目管理。 工程实际上就是可视化的makefile。
2.3 gfortran
GNU组织发布的开源软件,是GCC的一部分,支持Linux和Windows操作系统,支持Fortran2003语法及部分Fortran2008语法。 一般采用命令行编译,也有IDE支持gfortran的编译环境。
Windows:Code::Blocks、Simply Fortran、Eclipse/Photran
Linux:Code::Blocks、Eclipse/Photran
原生gfortran:
- Windows
- Linux:sudo yum install gcc-gfortran/sudo apt-get install gfortran
2.4 IVF+VS(intel Fortran Compiler+VS)
课程中使用的编译器为IVF。目前Intel公司推出了oneAPI,已经将intel Fortran免费,本人使用的为IFC+VS环境,附有安装教程及链接。
具体安装注意事项参考课程及安装教程。
IFC | VS |
---|---|
编译器(ifort) | 链接器 |
调试器(idb/gdb) | 编辑器 |
函数库(MKL/IMSL) | 集成开发环境 |
VC++运行库 | |
使用VS时的注意事项: |
- VS只是集成开发环境,提供编辑器。编译和调试使用的是IVF(IFC),链接使用的是微软的link;
- 关注 解决方案管理器 (solution explorer)和 输出 窗口(output);
- 了解并使用“ 工具 ”菜单“ 选项 ”,以及“ 工程属性 ”,学会配置属性(Debug、Release等);
- 理解 运行 (run without debug)和 调试 (debug)两种方式;
- 查看 帮助 。
配置属性Debug和Release
Debug | Release |
---|---|
生成文件较大 | 生成文件较小 |
运行速度较慢 | 运行速度较快 |
允许调试 | 不允许调试 |
基本没有优化 | 进行了合理优化 |
2.5 小结
- Fortran作为一种编译型语言,主要的工作环节有编译、链接和运行;
- 工程管理对于包含大量代码的项目来说是必不可少的;
- gfortran主要用于Linux系统,IFC+VS是Windows系统常用的编译环境;
- 使用IDE时善用环境配置、属性配置等环境工具。
3. 常见问题的检测和排查
错误信息 十分重要,可能会因编译器不同而不同。
主要有编译、链接和运行时错误。
3.1 编译错误
- 编译错误主要是编译器检查代码是否符合**语法规范;
- 编译错误的 种类最多 ,但也 最容易解决 ;
- 编译错误一般可以获得错误发生的 行数 ;
- 修改时编译错误只看 第一个 ,后续错误暂时不管;
- 一般的编译错误都是语法结构错误, Syntax Error 是最基础的编译错误。
3.2 链接错误
- 链接时拼接各代码的过程,编译器检查代码 完整性 ;
- 链接错误 种类最少 ,涉及用户代码部分容易解决。涉及编译器运行时库、第三方函数库、混编时不易解决;
- 链接错误通常 无法获得 错误发生的准确行数,错误一般也不是发生在某一行;
- 链接错误一般先看第一个,后续可暂时不管;
- 一般涉及用户代码部分的链接错误发生在调用子例程、函数、模块等过程中。
3.3 运行时错误
- 运行时错误时运行时库(Runtime Library)动态检查的错误;
- 运行时错误 种类较少 ,大多数 难以解决 ;
- 复杂的代码几乎难以避免运行时错误;
- 极端情况下,运行时库给出的运行时错误可能并不准确;
- 运行时错误在具有 调试信息 时,可以 初略获得 错误发生的行数;
- 运行时错误一旦发生,程序就会 中止 ,此时可配合 调试器 (debugger)动态检查原因;
- 一般容易发生的运行时错误有数组越界、数学计算错误、变量类型不匹配、输入输出等;
- 比较难缠的错误: Program Exception - access violation 、 SIGSEGV: Segmentation fault 等。
3.4 小结
- 正确认识错误,重视编译器给出的错误信息;
- 一般的编译错误都是语法结构错误;
- 一般涉及用户代码部分的链接错误发生在调用子例程、函数、模块等过程中;
- 一般容易发生的运行时错误有数组越界、数学计算错误、变量类型不匹配、输入输出等;
- 学会使用调试运行对代码进行调试。
4. Fortran源代码结构
4.1 固定格式和自由格式
早期Fortran使用固定格式,从F90以后推荐使用自由格式代码。
源代码文件的扩展名一般与支持的语法无关,与代码格式有关。
固定格式可以转换为自由格式:
- F77toF90
- FIXCON
- pilusFORT/SPAG
- Forquill
4.2 程序单元
字符=>Token=> 语句 => 程序单元 =>模块=>程序
程序单元:执行某一 特定任务 的具有一定 独立性 的代码区域,并且程序单元间的变量一般不互通。
例如主程序(main program)、子例程(subroutine)、函数(function)。
- 主程序只是默认被系统首次调用的,具有程序入口作用的函数;
- 程序单元能够提高代码的重复利用率。一次书写,多次调用;
- 一组程序单元及一组相关联的变量,可以组成模块。
因此,程序单元彼此间应尽可能独立,才可以充分考虑重复利用率。
4.3 语句类型
Fortran语句大致有三种类型:
- 声明语句:在 编译 时,告知编译器关于变量的信息;
- 执行语句:在 运行 时,执行以便完成特定的任务;
- 结构语句:仅作为代码结构的划分作用。
Fortran是 强变量 类型的语言:
- 变量类型必须在编译时确定,并且不允许被改变;
- 建议显式声明所有变量;
- 若未显式声明变量,则按照IN规则确定类型(ijklmn开头的变量为整形,其他变量为实型);
- 建议在每一个 程序单元 中都使用Implicit None。
变量声明的基本格式:
类型(属性),形容词,形容词...::变量名1(数组外形)=值,变量名2(数组外形)=值,...
形容词不建议单独书写,双冒号表示形容词的结束。
如果定义变量时同时赋值,默认具有save属性,必须书写双冒号。
建议在声明时添加形容词,并附上 Kind值 。
5. 数据类型与浮点数
5.1 数据类型
采用不同法则与二进制对应的数据就是不同的数据类型。
计算机往往只存储数据,不说明数据类型。所以可能对同一个数据产生 不同解释 。
为避免这种情况,编译器可以帮助 管理 数据类型。
Fortran的标准数据类型:
- Integer(Kind=):整型
- Real(Kind=):实型、浮点型
- Complex(Kind=):复数型(两个实型的组合)
- Logical(Kind=):逻辑型/布尔型
- Character(Kind=1,len=):字符型
- Type():派生类型(不同数据类型的组合)
数据除了有不同的类型外,还具有一定的 属性 ,可以通过定义时的形容词来赋予。
如Parameter可以将数据定义为常量。
5.2 Kind
5.2.1 Kind值简介
Kind的正式名称为 种别 ,是区分同一种数据类型,但具有不同 精度 、 长度 、或 编码方式 的代号,在 编译时 决定。
Kind受编译器的影响,具体数值可能会有差异。并且对不同的变量类型,表达意义也不同。 常数也有Kind值!
- 对Integer,Kind值影响整数能表达的 最大范围 ;
- 对Real和Complex,Kind值影响实数的 最大范围 和 最小精度 .一般为;
- 对Character,Kind值表示 编码 。一般为1,通常为ASCⅡ码;
- 对Logical,Kind值表示长度,对逻辑型无影响。
5.2.2 Integer的Kind值
一般为1、2、4、8,大多数编译器默认为4,占4个字节。
在部分编译器可能不支持1、8,在某些编译器可能为1、2、3、4。
k=Selected_Int_Kind(i)
函数可以用于选择满足要求的Kind值,其中i表示所需要的最大的十进制
位数
,k表示能满足该范围的最小Kind值
5.2.3 Real的Kind值
一般为4、8、16等,大多数编译器默认为4,占4个字节。
部分编译器不支持16,某些编译器用1、2、3表示。
k=Selected_Real_Kind(r,p)
函数可以用于选择满足要求的Kind值,其中r表示所需要的最大的十进制
位数
,p表示最小的有效位数,k表示能满足该要求的最小Kind值。
5.2.4 Complex的Kind值
Complex数据类型与Real一致,但相同Kind值的Complex函数占位为 Real的两倍 。
所以Complex(Kind=16)实际上是四精度的复数类型,而非双精度。
Complex的Kind选择函数与Real相同。
5.2.5 Character的Kind值
Character的Kind值通常只有一种,即Kind=1,表示为ASCⅡ编码,通常忽略;
也可以使用
k=Selected_Char_Kind('ASCⅡ')
函数来选择Kind值。
5.2.6 Logical的Kind值
Logical的Kind值通常与Integer一致,一般忽略。
在大多数编译器上,Kind=4,占4个字节. 任何Kind值的Logical都只能表示真、假两个状态。
Logical没有可以用于选择Kind值的函数。
通常只有在 混编 时,才需要更改Kind值。
5.3 整型和字符型
5.3.1 整型
语法中表示 次数(循环变量 )、 个数(数组下标) 、 Kind值 以及 内存地址 等都是整数。
需要注意整型变量的 最大范围 ,尤其在阶乘、指数等可能产生较大数据的计算中,必须先做出评估。
整型和整型的计算结果仍然是整型!
整数和浮点数的计算结果一般是浮点数,但尽量在浮点数的计算中将常数写为浮点数,最好标注 数据精度 。
5.3.2 字符型
字符串定义时 必须有长度 ,且通常是 固定 的。
Fortran的字符串没有 结束符\0 ,所以在字符串操作中必须注意。
有必要时需用 trim 去除尾部空格。
可通过内部文件在字符串和整型、实型间进行转化:
-
Read(字符串,*) 整型或实型
:字符串->数字 -
Write(字符串,*) 整型或实型
:数字->字符串
字符串允许使用 子字符串 表示字符串的一部分,是一种特殊的数组。
语法中有很多内容本身也是字符串,如 文件名 , Open语句的子句 和 格式Format 等。
利用字符串可以实现 批量文件处理 以及 动态Format 等功能,后续会讲到。
5.4 浮点数
浮点数是用 有限的数据 来表示 无限的可能 。
Real和Complex都是浮点数类型。 计算机中的浮点数规则很多,其中应用最多的是IEEE标准,其格式为:符号位+有效数+指数。
- 符号位0表示正数,1表示负数;
- 有效数表示小于1的小数,参与计算时会 加1 ;
- 指数位表示整数,参与计算时作为 2的指数 ,使用其 与127的差值 ,便于表示负指数;
- 最后表示值为: (-1)**(符号位)*(1+有效数)*(2**指数) 。
除了恰好能被2**(-n)线性组合出来的数,用浮点数表达会存在误差。
一般单精度浮点数可以达到十进制的约 7位 有效数字;双精度浮点数可以达到十进制的 15位 有效数字左右。
由于浮点数存在误差,我们应注意以下操作:
-
对浮点数进行相等判断:
if(a==1.3)
->"if(abs(a-1.3)<1.0e-5)` -
用浮点数作为数组角标:
b=a(2.0)
->"b=a(2)` -
用浮点数作循环变量:
Do r=0.0,2.0,0.1
->"Do i=0,20 r=i/20`
在绝大多数计算中我们需要注意误差的 放大 和 积累 ,如 累加 、 指数运算 等。
对此需要设计合理的算法,以减小误差放大和积累。
5.5 小结
- 注意变量的 Kind 值,及其对于不同类型数据所代表的含义;
- 建议在 定义变量时指定其Kind值 ,可以使用Parameter和Module用于统一代码中的Kind值;
- 合理使用 Selected函数 能够有效提高代码的 可移植性 ;
- 注意Character是一种特殊的数组,后续会详细讲到;
- 注意浮点数的 误差 问题,避免其误差的放大和积累。
6. 流程控制
参考代码:
<a name="Back Code.6"><a href="#Code.6">
"Code.6"
</a></a>
6.1 条件判断
6.1.1 If语句
- 一般的If结构:
If(Expression) Then
End If
-
简单If结构:
If(Expression) ...
- 多层If结构:
If(ConditionA) Then
Else If(ConditionB) Then
End If
If结构中的逻辑表达式结果必须为 单值 ,不能为数组:如果要进行数组比较可以使用 ANY 或 ALL 等函数。
- 署名的If:
name:If(ConditionA) Then
Else If(ConditionB) Then [name]
Else [name]
End If name
6.1.2 SELECT CASE语句
- 基本结构:
Select Case(Expression)
Case(A)
Case(B)
Case Default
End Select
自Fortran2003起,新增了Select Type功能。(参考代码:
<a name="Back Demo_Type_main"><a href="#Demo_Type_main">
"Demo_Type_main"
</a></a>
)
6.2 循环
6.2.1 Do循环
- 一般的Do语句:
Do i=m,n
End Do
- 修改增值:
Do i=m,n,r!//修改每次循环时循环变量的增值
End Do
- Do While语句:
Do While(Expression)
End Do
- 隐式循环:
a=[(i,i=1,10)]
- 无穷循环:
Do
End Do
- 署名的Do循环:
Outer:Do i=1,10
Inner:Do j=1,10
End Do Inner
End Do Outer
6.2.2 循环控制
- Cycle:忽略本次循环的剩余内容,直接 进入下一次循环
- Exit:忽略循环剩余内容,直接 跳出(指定)循环
使用Cycle和Exit可跳过或退出循环,配合署名可以针对特定循环进行该操作:
Outer:Do i=1,10
Inner:Do j=1,10
If(ConditionA) Then
Cycle Outer
Else If(ConditionB) Then
Exit Outer
End If
End Do Inner
End Do Outer
(参考代码:
<a name="Back Demo_IF_main"><a href="#Demo_IF_main">
"Demo_IF_main"
</a></a>
)
注意:
- 循环必须使用 整型变量 ;
- 循环过程中循环变量的值不允许人为改变,如果想达到类似的效果,可以采用 无限循环配合Exit 的方式完成;
- 循环的上下限如果通过 表达式指定 ,在进入循环计算期间 不再改变 ;
-
Fortran没有规定循环
正常结束
后循环变量的值。但如果通过
Exit退出
循环,循环变量为退出前的值。 (参考代码:
<a name="Back Demo_IF_func"><a href="#Demo_IF_func">
"Demo_IF_func"</a></a>
)
6.3 Fortran77的替代
- Goto:配合行号使用,自己编程时应尽量避免使用
- Computed Goto:类似If或Select Case的作用
- Continue:配合/或行号作为Do循环的结尾
- Arithmetic If:逻辑表达式与0做大小对比
7. 数组和结构体
参考代码:
<a name="Back Code.7"><a href="#Code.7">
"Code.7"
</a></a>
"
7.1 聚合数据
-
枚举(Parameter):一般定义为
常量
(参考代码:
<a name="Back ENUM"><a href="#ENUM">
"ENUM"</a></a>
) -
数组(Dimension):最基本、最简单的高级数据结构,通常使用循环结构进行访问。字符串是一种特殊的数组(参考代码:
<a name="Back ARR"><a href="#ARR">
"ARR"</a></a>
) -
结构体(Type):可以容纳
不同的数据类型
(参考代码:
<a name="Back TYPE"><a href="#TYPE">
"TYPE"</a></a>
) -
类(Class,Type Derived):结构体的
面向对象
的扩展,数据封装、类的多义性、类的扩展、类的继承等(参考代码:
<a name="Back CLASS"><a href="#CLASS">
"CLASS"</a></a>
)
7.2 数组
7.2.1 数组的分类
(参考代码:
<a name="Back ARR_TYPE"><a href="#ARR_TYPE">
"ARR_TYPE"
</a></a>
)
- 固定大小数组
- 静态数组(声明)
- 自动数组(传参)
- 动态分配数组(Allocatable、Deallocatable)
7.2.2 数组的定义
type,Dimension(bound)[,attr]::name
type[,attr]::name(bound)
例如:
Integer,Dimension(100)::IARR
Real::FARR(1:100),CARR(0:99)
Character(Len=256)::CONTENTS(80)!//派生数据数组
Real,Dimension(5)::RARR,RXARR(10)!//混合定义
7.2.3 数组的访问方式
(参考代码:
<a name="Back ARR_ACCESS"><a href="#ARR_ACCESS">
"ARR_ACCESS"
</a></a>
)
Subroutine FUNARR(A,N)
Interger,Parameter::NMAX=100
Real,Dimesion(NMAX)::ARR
Integer,Dimension(5)::VECSUBSCP=(/1,4,6,7,5/)
Interger::NARR,I
I=10!//有效数组位数
- 整体
ARR=1!//对全部元素赋值
ARR(:NARR)=1!//推荐采用该方式,访问有效数组位数
- 元素
ARR(1)=-1!//要考虑bound
- 片段
ARR(2:4)=(/(sin(Pi*(I-1.)/(NARR-1)),I=2,4)/)
Call FUNARR(ARR(2:4),3)!//连续数组片段的传递为地址传递,在子程序内对其改变会改变原数组
- 不连续段
ARR(VECSUBSCP(:3))=0.5
Call FUNARR(ARR(VECSUBSCP(3:5)),3)!//不连续数组片段的传递会构造新的暂时数组,子程序内对其改变不改变原数组
7.2.4 数组的子程序
子程序对数组作用分为两种:第一是对数组元素进行处理,如
Sqrt
、
sin
等;第二是对数组本身进行处理,如
Dot_product
、
Matmul
、
Sum
、
Product
、
Maxloc
、
Maxval
等。
-
举例:求解ax=b,a为带宽p+q-1的矩阵(参考代码:
<a name="Back BANDMAT"><a href="#BANDMAT">
"BANDMAT"</a></a>
) - 矩阵的压缩保存
- 矩阵的LU分解
- 方程的求解
7.3 结构体
7.3.1 结构体的定义
Type[[,arrt-list]::]name[(type-param-name-list)]
[type-param-def-stmts]
[Private statement or Sequence statement]
[component-definition]...
[type-bound-procedure-part]
End Type [name]
7.3.2 结构体访问方式
Type%Mem
(参考代码:
<a name="Back TYPE_ACCESS"><a href="#TYPE_ACCESS">
"TYPE_ACCESS"
</a></a>
)
7.4 小结
- 注意数组定义的两种方式,编写代码时根据所需数组的特点合理选用;
- 注意对数组访问的四种方式,在访问时区分有效数组位数和最大数组容量;
- 连续数组片段的传递为地址传递,在子程序内对其改变会改变原数组,不连续数组片段的传递会构造新的暂时数组,子程序内对其改变不改变原数组 ;
- 注意子程序对数组作用的两种方式;
- 注意结构体的赋值方法,包括片段赋值、构造赋值等方法;
- 注意结构体的访问方式,尤其是多重结构体的逐级访问。
8. 函数和子例程
参考代码:
<a name="Back Code.8"><a href="#Code.8">
"Code.8"
</a></a>
"
8.1 函数和子例程简介
计算机编程的函数是对数学函数概念的扩展;此外它也作为引导程序执行流程的重要方式。 使用函数的优点:
- 将长过程 拆分 为若干小过程,有利于代码的 逻辑结构 ;
- 封装过程 使得函数的使用者不必了解内部实现细节;
- 一次书写多次调用,便于 修改和维护 ;
- 函数内部与调用者是 隔离 的,不能直接使用调用者的变量,避免紊乱;
- 函数局部变量在函数返回后丢失,下次进入时它们的值不确定,节约内存。
缺点:牺牲很小一部分效率(inline展开) 典型函数的书写和调用:
书写:
[形容词][返回类型] Function 名称([虚参1,虚参2...])
[虚参的声明]
[局部变量的定义]
函数内部实现
名称=返回值
[return]!//尾部可忽略
End [Function [名称]]
返回变量=名称([实参1,实参2...])
典型子例程的书写和调用:
!//书写:
[形容词] Subroutine 名称([虚参1,虚参2...])
[虚参的声明]
[局部变量的定义]
子例程内部实现
[return]!//尾部可忽略
End [Subroutine [名称]]
!//调用:
Call 名称([实参1,实参2...])
函数与子例程的区别:
函数 | 子例程 |
---|---|
有返回值 | 无返回值 |
var=函数名() | Call 子例程名() |
虚参可输出数据 | 虚参可输出数据 |
允许多个输出 | 允许多个输出 |
可包含文件操作 | 可包含文件操作 |
由于Fortran默认传址而非传值,所以可通过虚参输出数据达到返回值的作用。 | |
因此函数和子例程其实没有什么区别。 |
8.2 虚参和实参
(参考代码:
<a name="Back Func"><a href="#Func">
"Func"
</a></a>
)
虚参和实参时调用者与子程序互换数据最直接的方式:
- 调用者 指定 实参 (Actual Arguments也叫实元)
- 子程序 指定 虚参 (Dummy Arguments也叫形参,哑元)
虚参和实参一般按顺序结合,实参会传递到子程序中称为虚参。 传递的方式有两种:
- 传址(By reference):高效,可回传,节约内存
- 传值(By Value):不可回传,速度慢,浪费内存,安全
建议:
- 严格匹配 实参和虚参,包括 数据类型 、 Kind值 、 数组维度 等;
- 未特别指定可选参数时,严格匹配 参数个数 ;
- 大多数情况下,实参和虚参应该使用不同的名字。 实参命名 应考虑在 调用者中的标识 , 虚参命名 应考虑在 子程序中的作用 ;
- 要充分考虑子程序可能的变化,将能够交予 外部配置 的信息都定义为虚参,以提高子程序的 可重复使用性 。
8.3 传递数组和结构体
8.3.1 传递数组
数组除了元素之外,还有 维度 、每个维度的 上下限 和 大小 等信息。
Call sub(a)
子程序调用过程中,虽然在源码上看只有一个参数,但实际编译后,传递的信息除了
第一个元素的地址
(Address),还可能包括上述的部分信息(以Descriptor的形式)。
传递数组的三种方式:
- 自动数组(Auto Arrays)
Subroutine sub(a,m,n)
Integer m,n
Real a(m,n)
End Subroutine sub
只传递地址,维度由定义确定,各维度上下限由其他虚参确定, 参数多 。
- 假定大小(Assumed-size)
Subroutine sub(a)
Real a(*)
a(1:6)=1
End Subroutine sub
只传递地址,虚参只能是1维,下线为1,不传递上限, 容易越界 。
-
假定形状(Assumed-shape)(参考代码:
<a name="Back WriteMatrix"><a href="#WriteMatrix">
"WriteMatrix"</a></a>
)
Subroutine sub(a)
Real a(:,:)
End Subroutine sub
传递地址和各维度大小,下限可自动,上限自动计算, 需要Interface 。
8.3.2 传递结构体
(参考代码:
<a name="Back passtype_degree"><a href="#passtype_degree">
"passtype_degree"
</a></a>
)
传递结构体时需注意,即使两个结构体完全一样,但其 分别独立定义 ,编译器也认为不是同一类。
因此虚参实参如果传递结构体,必须是由 同一个定义Type 得到的结构体实例. 一般采用模块定义Type,在调用者和子程序中分别调用同一个模块来定义实参和虚参。
(参考代码:
<a name="Back passtype"><a href="#passtype">
"passtype"
</a></a>
)
8.4 特殊用法
8.4.1 变量的Save属性
子程序局部变量具有 临时性 。
子程序返回后,局部变量被收回,下次进入子程序时其值不确定。
Save属性可以指定变量在子程序返回后留存,待下一次进入同一子程序保留上一次的值。
Integer,Save::var
Integer::var=0!//虽然没有书写Save,但定义时初始化值,默认具有Save属性
8.4.2 虚参的Intent属性(需要Interface)
明确指定虚参的使用目的:输入参数(In)、输出参数(Out)、中性参数(Inout)
!//输入参数,在子程序内部不允许被改变
Integer,Intent(In)::input_arg
!//输出参数,子程序返回前必须被改变(对应实参不能是常数或表达式)
Integer,Intent(Out)::output_arg
!//中性参数
Integer,Intent(Inout)::nerter_arg
Integer::nerter_arg!//未指定Intent则为中性
注意:Intent的检查是在 编译时进行 ,而非运行时检查。 建议对每一个虚参都指定Intent属性!
8.4.3 虚参的Value属性(需要Interface)
指定该参数为 传值 参数,而 非传址 参数。
!//传值参数,只能作为输入参数,改变后不影响实参
Integer,Value::by_value_arg
注意:除 混编 外,一般 不使用 Value属性。
8.4.4 可选参数Optional属性(需要Interface)
被指定的参数在某些情况下可以不赋予,运行时动态决定参数的个数。
例如Open语句的大多数子句不需要书写,可以被认为是可选参数的子程序。
Integer,Optional::a
(参考代码:
<a name="Back optional"><a href="#optional">
"optional"
</a></a>
)
8.4.5 更改参数顺序
子程序的实参、虚参可以 不按照顺序 对应。
类似的,Open语句的子句也可以不按照顺序书写,这在拥有较多的可选参数时是必要的。
就算不需要改变参数顺序,采用这种方式书写代码也可以 提高可读性 。
Call writeresult(Data=var,file="res.txt",size=1000)
Call writeresult(var,1000,"res.txt")
8.4.6 函数名作为参数
实参和虚参都是函数名,让子程序本身 以另外的函数作为参数 。
例如:书写一个数值积分的子程序,该子程序负责对另一函数进行数值积分,则另一函数由外部提供,是一个函数名的虚参。
此时虚参可以用External定义,也可以用Interface定义,推荐后者。
(参考代码:
<a name="Back func_name_arg"><a href="#func_name_arg">
"func_name_arg"
</a></a>
)
8.4.7 Result后缀
旨在通过对 返回值重命名 以便于理解或书写。
主要用于区分返回值的内外名称,外部名称为函数名,内部名称为Result重命名。
8.4.8 递归子程序
自我调用 的子程序,在特殊的循环或迭代中非常有效。 但是该种用法易出错,对 堆栈的使用开销较大 。
可递归的子程序前应添加Recursive。
Recursive Function Func(n) Result(r)
(参考代码:
<a name="Back recursive_do"><a href="#recursive_do">
"recursive_do"
</a></a>
"、
<a name="Back recursive"><a href="#recursive">
"recursive"
</a></a>
)
代码中介绍的只是 一进一出 的最简单的递归形式,而更复杂的 一进多出 递归,往往与链表结合使用。
例如著名的二叉树(数据结构),要遍历这个结构就可以用到递归函数。
操作系统的树形文件夹结构为我们带来了方便。其实操作系统自身也使用一进多出的递归函数来管理文件系统。
递归函数的使用原则:
- 在 不涉及链表 的数据结构中,能不使用则尽量不用;
- 一定要控制好递归 终止条件 ,否则容易无穷递归;
- 递归函数中尽量 少使用局部变量 ;
- 多级,一进多出 的递归函数尤其需要特殊的优化。
8.4.9 不建议使用的特殊用法
- 多入口函数(Entry)
- 返回语句标号的函数(Alternate returns)
- 语句函数(Statement function)
8.5 小结
- 在编程过程中善于利用子程序的独立性和可重复使用性,将长过程拆解为小过程,封装为子程序便于使用;
- 由于Fortran 默认传址 的特性,函数和子例程没有本质上的区别;
- 子程序具有 隔离性 ,局部变量具有 临时性 ;
- 注意虚参和实参的匹配,包括 数值类型 、 Kind 值、 数组维度 等;
- 注意虚参和实参的命名方法,虚参主要代表在子程序内部的作用;
- 充分考虑子程序可能发生的变化,将 外部配置 的信息设置为虚参;
- 注意 数组 和 结构体 类型的虚实参传递,包括数组维度、大小、上下限以及结构体虚实参的定义等。推荐的数组传递方式是 假定形状 ;
- 注意Intent、Value、Optional参数的特殊属性,需要Interface;
- 注意初始化变量的Save属性, 不会 在运行过程中再次初始化,在子程序结束后会 保留 当前值;
- 注意 带有虚参名的变量传递 ,不仅可以用于更改参数顺序,还能提高代码可读性;
- 函数名作为参数时推荐使用 Interface定义 ;
- 函数的Result后缀主要用于区分返回值的内外名,便于理解和书写;
- 注意递归子程序的使用原则;
- 推荐所有虚参声明时都附上Intent属性,对于参数较多的子程序推荐使用带有虚参名的变量传递;
- 在编写子程序时养成良好习惯,包括虚实参的命名、属性声明等,以及各类特殊用法的合理使用。
9. 模块(Module)
参考代码:
<a name="Back Code.9"><a href="#Code.9">
"Code.9"
</a></a>
"
9.1 接口(Interface)
9.1.1 Interface简介
子程序将整个程序 划分 并 隔离 ,它们之间通过虚实参结合、Common、Module等 交换数据 ,通过 相互调用 实现程序流程控制。
接口(Interface)是 控制 子程序相互调用或传递参数的"指导"。
其他语言或Fortran77以前没有显示的Interface语法,函数间调用不安全,只检查 个数 、 类型 。
Fortran90之后出现了显示的Interface语法,指定了函数的 调用方式 、 各参数的传递方式 、 详细信息 、 上下限 、 变量的限定 、 是否可忽略 等。
Interface接口可以在 编译阶段 就查找出许多错误,避免程序运行时出现更多错误。
9.1.2 Interface使用
(参考代码:
<a name="Back interface"><a href="#interface">
"interface"
</a></a>
) 使用以下用法时,必须使用Interface:
- 函数返回值为 数组 、 指针 ;
- 参数为 假定形状 数组;
- 参数具有 Intent 、 Value 等属性;
- 参数具有 可选参数 、改变参数顺序。
函数名 作为虚参和实参时,虽然不强制要求,但也推荐使用Interface。
建议在任何子程序调用时,都使用接口!
Interface用于将被调用者的信息告知调用者。 书写时只需包含在
Interface
End Interface
其中的内容为:将被调用者的 执行语句 和 局部变量定义 去掉,一般只包含 子程序的头尾 和 虚参的声明 。
9.2 Module
Module用于将一部分(有关联的)数据和函数 聚合 起来。 使用Module主要有以下作用:
- 避免手动书写Interface;
- 数据 共享 ;
- 数据与过程的 封装 、 保护 和 继承 。
Module是Fortran90区别于Fortran77的重要特征,也是重大进步。
在Fortran2003以后的语法中, Module与Type结合 使得Fortran 面向对象 编程成为可能。
9.2.1 Module简介
函数是具有输入输出的、在内部实现一个特定功能的过程,而Module将一系列相关的函数及数据 再封装 以实现一系列 功能的组合 。
对于函数,虚实参结合是共享数据的一种方式,而Common和Module也可以用于共享数据。 需要注意的是Fortran中
没有全局变量
,Common叫做(未命名的)公共区,而Module叫做模块,在使用时必须写明
Common /变量列表/
或
Use 模块名
。
- 其中Common按字节数结合,而非变量名结合,建议弃用。
-
Module中的变量在Module内部可以自由使用,任何
Use
该Module的程序单元也可自由使用,其值保持一致。(参考代码:
<a name="Back module"><a href="#module">
"module"</a></a>
)
上述三种共享数据方式在使用时仅需选用一种,尤其是Common和Module方法不可同时使用。
Module比Common更简单更安全。
9.2.2 Module的访问
访问Module:
Module monitorsys
Use smartHome
通过Only仅访问部分变量:
Module monitorsys
Use smartHome,Only:tv,pc
仅访问部分变量且重命名:
Module monitorsys
Use smartHome,Only:screen=>tv,pc
Only可以 避免命名冲突 和 加快编译速度 ,但 不能 节约内存。
Module向外部提供变量(数据共享)和子程序,并且拥有以下几种权限:
变量 | 子程序 | |
---|---|---|
Public | 外部可读取、修改 | 外部可调用 |
Protect | 外部可读取,不可修改 | —— |
Private | 外部无法访问 | 外部无法调用 |
内部均可访问、修改 | 内部均可调用 |
分别注意外部程序和Module内部子程序对Module内部变量的访问权限,以及内部子程序间的调用。
注意Module内部隐含了Interface,在源码中无需显示写出,但是具有接口控制功能。
(参考代码:<a name="Back module_view"><a href="#module_view">"module_view"</a></a>)
注意内部子程序直接访问内部变量时没有接口控制,而通过虚实参结合访问内部变量时需要符合Interface要求。
9.2.3 Module的继承
Module还可以被
继承
,ModA可以通过
Use ModB
获得ModB对外提供的Public变量和子程序。
在较大的程序中,Module的继承会形成类似树型的 模块树 。
需要注意,一个模块可以Use多个模块,也可以被多个模块Use,但要防止出现 环状依赖 。
-
Module会降低程序的
编译效率
。 (参考代码:
<a name="Back module_protect"><a href="#module_protect">
"module_protect"</a></a>
)
9.3 特殊用法
9.3.1 统一接口(函数重载)
通过统一接口可以把 多个子程序 捆绑为一个名字,由编译器根据实参的情况,决定到底调用哪一个函数。
对于功能相同或相似但输入有所不同的子程序,可以采用该方法将这些子程序统一命名,方便程序编写。
Module name
Interface Is
Module Procedure::Is_Int,Is_RR,Is_Char
End Interface Is
Contains
Integer Function Is_Int(logic,itrue,ifalse)...
Real(Kind=8) Function Is_RR(logic,rtrue,rfalse)...
Character(Len=64) Function Is_Char(logic,ctrue,cfalse)...
End Module name
大多数数学函数都利用了统一接口,如
sin()
。
他们绝大多数还是 逐元函数 ,即实参可以是数组。
9.3.2 自定义操作符(操作符重载)
通过Module Procedure和Interface Operator可以将操作符进行重载,从而实现一些更加“人性化”的程序。(参考代码:
<a name="Back module_oper"><a href="#module_oper">
"module_oper"
</a></a>
)
9.4 小结
- Interface作为控制子程序间数据交换的关键,建议在所有子程序调用时都使用接口;
- 注意数据交换(共享)的三种方式,其中推荐使用Module而不使用Common;
- Module的使用能够完成数据共享,并且对数据和过程进行 封装 、 保护 和 继承 ,提高安全性;
- 通过Module和Type的结合,可以完成 面向对象 的编程;
- 注意Module提供数据的三种 权限 ,并且子程序 隐式 提供Interface功能,外部调用时需满足接口要求;
- 注意内部子程序 直接访问 内部变量时没有接口控制,而通过 虚实参结合 访问内部变量时需要符合Interface要求;
- 编写代码时合理使用Module以提高数据的 安全性 、代码的 可维护性 和程序的 拟人性 。
10. 文件读写
参考代码:
<a name="Back Code.10"><a href="#Code.10">
"Code.10"
</a></a>
"
10.1 文件和路径
各类操作系统(Windows、Linux、Mac)都习惯采用 树型目录结构 来管理文件系统。
通常每个文件都有一个 路径 和自己的 文件名 ,依靠路径和文件名去查找文件。
10.1.1 文件
文件名一般分为 主文件名 和 扩展名 。 但是扩展名并不是必须的,只是为了方便区分,用来表示文件的类型,并不影响文件实质。
Windows和部分Linux的界面,采用文件扩展名来决定直接双击通过什么应用程序打开文件。这种文件关联只是为了便于人们的操作。
10.1.2 文本文件
本质上文件都是二进制的。所有数据都是有效的可显示的 字符 的文件叫做 文本文件 。
- 文本文件,如.f90 .txt .log等,用于人类阅读;
- 二进制文件,如.mp3 .avi 。exe等,用于机器阅读。
文本文件通常用文本编辑器(记事本,VI)打开,容易读懂。二进制文件用文本编辑器打开一般会看到乱码,解释方式不正确。
文本文件的行通过换行符(CR+LF(Windows)、CR(Linux)、LF(Mac))分隔,而列通过间隔符(空格、逗号、TAB)分隔。
10.1.3 二进制文件
二进制文件是原始计算机数据,存在解释方式的问题。即对于同样的二进制序列可以有多种不同的解释。
一般二进制文件 只存储数据 , 不存储解释方式 。因此对于一个陌生的未说明的二进制文件,只能进行 猜测 。
对于二进制文件,除了使用专属的应用程序打开,还可以用十六进制编辑器打开(UltraEdit、VIM :%!xxd 、WinHex、VS)。
二进制文件没有行列的概念,是无间隔的存储。
10.1.4 文件读取方式
由于文件形式的不同,Fortran语法规定了不同方式来读取:
文本文件(有格式文件) | 二进制文件(无格式文件) | |
---|---|---|
顺序读取 | 顺序读取有格式文件(用的最多) | 顺序读取无格式文件(在记录前后各增加4字节,表示记录长度) |
直接读取 | 直接读取有格式文件(要求每行一样长) | 直接读取无格式文件(用得较多) |
流文件 | —— | 流文件(非常方便,强大) |
- 内存里是 无格式 的;
- 有格式、无格式是文件本身的 属性 ;
- 顺序读取、直接读取、流文件是 访问的方式 ;
- 文本文件一般不用流文件读写。
文本文件和二进制文件的对比:
文本文件(有格式文件) | 二进制文件(无格式文件) |
---|---|
读写慢速 | 读写快速 |
适合中小文件(100MB以内) | 适合较大文件 |
可能有读写误差 | 读写无误差 |
人类阅读方便 | 人类阅读困难 |
容易出错 | 不容易出错 |
读取方式的对比:
顺序读写 | 直接读写 |
---|---|
按顺序 | 可任意跳转 |
速度慢 | 速度快 |
每次不一样长 | 每次一样长 |
简单 | 复杂 |
灵活 | 死板 |
10.2 顺序读写有格式文件
文本文件一般用顺序读写、直接读写。
- 顺序读写:按照先后顺序读取该文件;
- 直接读写:指定特定的行直接读写(需每行长度一样)。
读写文件至少需要用到4个语句:
- Open:打开文件
- Read:读取文件
- Write:写入文件
- Close:关闭文件
10.2.1 Open
Open(子句=值,...)
Open有二十多个子句,有不同的作用。其中必要的只有两个:
-
Open(Unit=通道号,File="文件名")
-
Open(通道号,File="文件名")
文件通道号由程序员给定,一般用 大于10 的数字。
10以下的数字由不同编译器预留(一些编译器用5、6表示标准输入输出)。
通道号是操作该文件的"凭证",在Read、Write、Close时指定。也可以用变量、循环打开多个文件。
文件打开后,读取位置默认在文件开始处。
通道号 与 句柄 的区别是:句柄由过程内部 自动分配 ,外部不能决定是多少;而通道号由 程序给定 ,万一给错了,就会出错。
Fortran2003以后增加了NewUnit这个子句,可以由编译器 自动分配通道号 ,程序员需要保存这个通道号( File_ In )。
Integer::File_ In
Open(NewUnit=File_ In,File="2d_text.txt")
10.2.2 Read
Read(文件通道号,*)变量列表
如果不特殊指定格式,每个Read语句读取整的N行。
星号*表示表控格式(List-Direct),即让 变量列表自动控制格式 。
一般Read不特别指定读取格式,使用表控格式即可;而Write需要指定写入格式。
所以一般情况下,一个Read语句只读取一行数据。如果要读取多行,需要采用 多个Read语句 或 循环读取 。
下面是一些例子:
-
读取文件到数组:通过
数组片段
,顺序读写有格式文件针对二维数组也非常方便。(参考代码:
<a name="Back Read_2D_text"><a href="#Read_2D_text">
"Read_2D_text"</a></a>
) -
读取(跳过)
指定位置
:可以通过
空Read
跳过一行(包括空白行),通过读取一个
不使用的变量
(r)跳过若干列。(参考代码:
<a name="Back Read_text"><a href="#Read_text">
"Read_text"</a></a>
) -
读取
未知行列数
文件:对于未知行列数的文件,可以采用函数先获取行列数,配合
可分配数组
进行文件读取。注意参考代码中对文件读取位置的处理。(参考代码:
<a name="Back Read_text_alloc"><a href="#Read_text_alloc">
"Read_text_alloc"</a></a>
) - 读取 行的特定内容 :
Read(FILE_IN,100) r 100 Format(11x,f7.3)!//只能读取固定位置的数据-
read(FILE_IN,'(a512)') cStr!//读取第一行
i=index(cStr,"Δt")+3 j=idex(cStr,"ms")
Read(cStr(i+1:j-1),*) r !//通过对字符串处理,读取指定格式位置的数据。
10.3 直接读写有格式文件
直接读取方式读写文本文件,可以 任意指定 读取哪一行。
对于每行不一样长的文件,不适用直接读取。
为了使用直接读取可以将每一行
补齐
成一样的长度。 直接读取Open格式:
Open(NewUnit=FID,File="text_ditect.txt",form="formatted",access="Direct",RecL=64)
-
form="formatted"
:指定为有格式文件(文本文件)。在顺序读取时是默认值,可以不指定; -
access="Direct
:指定为直接读取方式。在顺序读取时可以指定"SEQUENTIAL",它是默认值,可以不指定; -
RecL=64
:指定记录长度(Record Length)是64字节,仅在直接读取时指定。
直接方式读取时,必须指定 读取格式 和 读取记录 :
Read(FID,'(i7,3(1x,g15.6))',Rec=5) i,a,b,c
-
'(i7,3(1x,g15.6))'
:表示读取格式,7长度的整数,3个空格和15长度的浮点数。 -
rec=5
:表示读取第5笔记录。因为RecL=64
,而每一行是64字节,所以rec=5
对应文件第5行(注意:读取时一笔记录的长度和文件一行的长度不同,这里只是设置为相同)。 (参考代码:<a name="Back Read_text_direct"><a href="#Read_text_direct">
"Read_text_direct"</a></a>
)
直接读取需要指定读取格式,对文件格式要求较高,并且不能使用表控格式。
可以先用 字符串读取数据 ,再从字符串中按照一定规则,用表控格式读取数据。尤其在 不规则 文件的读写中,经常使用这种方式。
Read(FID,'(a64)',Rec=5) str
Read(str,*) i,a,b,c
Write(*,*) i,a,b,c
建议:
- 对于 少量的 、起 控制作用 的、需要 经常修改 的数据,采用 顺序读写 文本文件;
- 对于 较大量的 、 规则排列 的数据,采用 直接读写 文本文件;
- 而 对于 大量的 数据,采用 二进制 文件。
10.4 顺序读写无格式文件
10.4.1 无格式文件
无格式文件(二进制文件)用于机器阅读,它的 格式与内存一样 。
因此二进制文件读写是简单的复制,速度非常快。非常适合 大量数据 、 中间数据 、不希望用户看懂和随意更改的数据。(SEGY、netcdf、bmp、surfer grid等都是二进制文件)
代码资源内提供了一款十六进制编辑器MiniHex,可以用于打开任何的二进制文件。
10.4.2 无格式文件的顺序读写
顺序写入 无格式文件,以 记录 为基本单元,读写过程分若干笔记录。
每一次记录,在记录前后各多出4个字节,用于记录本次写入的
长度
.
顺序读取
时,通过这4个字节知悉当前记录长度。 (参考代码:
<a name="Back write_myfile"><a href="#write_myfile">
"write_myfile"
</a></a>
)
再 顺序读取 该文件时,程序会首先读入前4个字节,获得本笔记录的长度。
随后读入后4个字节,以验证是否与前4个字节相同(不同则报错)。
最后将中间字节按顺序读取到变量列表中。
(参考代码:
<a name="Back read_myfile"><a href="#read_myfile">
"read_myfile"
</a></a>
)
由于特殊的前后4字节:
- Fortran顺序写入的文件,尽量用Fortran顺序读取;
- 非Fortran写入的文件,尽量不要用Fortran顺序读取;
- 用其他方式也可以读写顺序写入的文件,但是比较啰嗦繁杂。
所以只用Fortran,可以用顺序读写;如果和其他软件、语言交互,通常不使用顺序读写。
10.5 直接读写无格式文件
-
直接读写
无格式文件,也以记录为基本单元,将读写过程分为若干笔记录。 但是每一笔记录的
长度是固定的
,在Open时指定(注意:RecL在某些编译器上代表
4字节
的个数而非字节的个数)。 (参考代码:
<a name="Back write_mydirect"><a href="#write_mydirect">
"write_mydirect"</a></a>
) -
直接读写可以跳转到
任何整记录
读写,也可以
一边读一边写
。 读取时也不一定按照写入时的顺序。 (参考代码:
<a name="Back read_mydirect"><a href="#read_mydirect">
"read_mydirect"</a></a>
) -
直接读写可以跳转读取位置,比较
灵活
,但要求每个
记录长度
一样,并且Open之后就无法改变。 对于
数据结构比较混乱
(有多种字节)的文件,读取不方便。 很多时候不得不
多次Open
、
Close
,用
不同的RecL
来读写不同长度的数据部分。 过去可以使用Equivalence、Mvbits函数等方法实现一次读取。 现在推荐使用
流文件方式
来读取。 例:直接读写Goldensoft Surfer*.grd文件。 (参考代码:
<a name="Back read_fcode_test_direct"><a href="#read_fcode_test_direct">
"read_fcode_test_direct"</a></a>
) - 直接读写理论上可以读写任何文件,但更适合于 大量的长度相同的 数据,或纯粹的 数组 。 在Fortran2003以前,直接读写是读取二进制文件的唯一选择。在VF系列编译器上,还扩展有Binary方式的读写,类似顺序读写,但没有记录长度字节。但由于是扩展语法,不够规范。
10.6 流文件读写无格式文件
-
流文件是Fortran2003的新语法,允许
自由控制读写位置
,
自动判断
本次读写的长度。 (参考代码:
<a name="Back read_fcode_test_stream"><a href="#read_fcode_test_stream">
"read_fcode_test_stream"</a></a>
) - 流文件打开时不指定RecL,打开后读取位置自动在文件开始处。 执行Read语句时不指定Rec,读取长度L根据后面的变量表自动计算。 读取后,读取位置自动向后移动L个字节。
-
除了可以自动判断位置和长度外,流文件允许随意跳转。 上述例子中,如果只想读取m、n,可以直接跳转到第5字节读取,而不读取DSBB:
Read(FID,Pos=5) m,n
也可以查询当前读取的位置:Inquire(FID,Pos=i)
查询后,可以跳过一定字节继续读取:Read(FID,Pos=i+32) a,b
-
可以通过一个Type来定义文件的
头部信息
,之后一次读取整个头部。 尤其对于类似SEGY这种有多个头部(道头)的文件,非常方便。 (参考代码:
<a name="Back read_fcode_test_stream_type"><a href="#read_fcode_test_stream_type">
"read_fcode_test_stream_type"</a></a>
) - 流文件读写非常方便,甚至可以方便地读取 顺序写入的二进制文件 。
10.7 其他问题
10.7.1 字节序
对于超过1个字节的数据类型,存在字节的存储顺序问题,这就是
字节序
。 一般个人PC采用小端(Little_Endian)字节序,有些工作站采用大端(Big_Endian)字节序。 对于大端字节序数据,intel编译器可以采用如下方式读取:
Open(FID,File="文件名",convert="Big_Endian")
10.7.2 文件路径
- Fortran标准语法中,没有规定任何文件夹(目录/路径)的问题;
- Open语句的File子句,是传递给操作系统(系统调用),因此一般可以使用带(绝对或相对)路径的文件名。
Open(12,File="subfolder\filename.ext")!//Windows
Open(12,File="D:\dir\subdir\filename.ext")!//Windows
Open(12,File="/etc/passwd")!//Linux
Open(12,File="../../otherdir/filename.ext")!//Linux
- 如果不带任何路径,则被认为是 当前路径 (Working Directory);
- 如果带相对路径,以当前路径为 基准 ;
- 通过命令行执行时,当前路径一般在 提示符 上(或pwd命令查看);
- 直接双击可运行程序,当前路径在.exe所在文件夹;
- 通过IDE方式运行,当前路径一般在 工程 所在文件夹;
- 一些IDE允许设置当前路径。
程序中可以通过一些方式,
动态地
更改当前路径,不同的编译器都有类似的扩展。 IVF:ChangeDirQQ;gfortran:ChDir。 有的编译器允许获得当前路径,如IVF/gfortran:GetCWD。 (参考代码:
<a name="Back cwd"><a href="#cwd">
"cwd"
</a></a>
) 注意例程中可执行文件路径和当前路径(工作路径)的区别。
10.8 小结
- 文件分为 文本文件 和 二进制文件 两种;
- Read读取文件时尽量采用 表控 格式(*);
- 注意不同情况下文件读取方式的选择,包括文件的 属性 、 大小 、 格式 等;
- 对于 大量的 、 规律的 纯粹数组,直接读写方式效率最高;
- 注意直接读写方式的记录长度,在Open后就无法改变;
- 二进制文件的读写,一定建立在 知晓文件格式 的前提下;
- 流文件能够方便地读写 任何 二进制文件,善于使用流文件处理无格式文件;
- 编写程序时文件路径尽量采用 相对路径 ,不使用绝对路径;
11. 标准函数
11.1 标准函数的一般问题
- Fortran较为底层,仅提供非常 底层 的数学函数。绝大多数高层的数学问题需要独立书写代码实现。 语法函数的种类和用法与其他软件和语言有所差异,使用需要查阅 帮助文档 。 intel Fortran Compiler帮助文档下载 Fortran常用函数
-
Fortran函数有严格的参数类型和返回值类型。虽然很多语法内的函数提供了不同数据类型的版本,但并非每一种都有。 例如
y=exp(2)
是错误的写法,因为exp()函数未提供Integer版本。正确应写为y=exp(2.0)
。 - Fortran内部的多数数值函数有Generic Name和Specific Name。绝大多数情况下,使用Generic Name足矣。 只有极少数情况下,才必须使用Specific Name。例如 输入和输出类型 不一致,或多个 参数类型 不一致。
- Fortran内部的大多数函数,还是逐元函数(Elemetal)。 所谓逐元,简单而言就是对于 单变量参数 ,可以是 任意维度的数组 。
-
优化:优秀的编译器提供优秀的优化方案。优化的作用是,使得计算方法通过一定的变换实现更快的执行。 例如
a=a*25
→b=a*4+a,a=b*4+a
对于计算机来说,执行会更快。优化水平往往能够体现不同编译器的差别。 对于同一个数学问题,自己写代码实现往往不如编译器厂家优化效果好。因此对于绝大多数问题,如果 能够使用语法内函数,就尽量不要自己写代码 。
11.2 数学数值函数
11.2.1 类型转换函数
- 转换为 Real 型:
r=Real(x)
r=Real(x,Kind=8)!//双精度Real
r=Float(x)/Dble(x)
- 转换为 Integer 类型:
i=Int(x)
i=Int(x,Kind=8)!//双精度Integer
i=Nint(x)!//四舍五入并转换
- 转换为 Complex :
c=Cmplx(a,b)!//将a,b转换为复数,分别为实部和虚部
c=Complex(a,b,Kind=8)
- 内部文件( Char )转换:
Write(字符串,*) 其他数值!//其他类型转字符串
Read(字符串,*) 其他数值!//字符串转其他类型
11.2.2 一般数值函数
-
i=Ceiling(r)
:天花板函数,返回 大于等于 变量的最小整数(Kind); -
i=Floor(r)
:地板函数,返回 小于等于 变量的最大整数(Kind); -
ri=Abs(ri)
: 绝对值 函数; -
c=Conjg(c)
:取 共轭复数 ; -
ri=Max(ri1,ri2,ri3,...)
: 最大值 函数; -
ri=Min(ri1,ri2,ri3,...)
: 最小值 函数; -
ri=Mod(a,b)
: 取余数 函数; - 三角函数(加h为 双曲 三角函数,加D为 角度制 三角函数(不标准,不建议使用)):
-
r=sin(r)/cos(r)/tan(r)
-
r=asin(r)/acos(r)/atan(r)
-
r=log(r)/log10(r)/exp(r)
:对数指数函数; - 向量矩阵函数:
-
c=dot_product(a,b)
:内积函数; -
b=transpose(a)
:转置函数; -
c=matmul(a,b)
:矩阵相乘函数
11.3 字符串和数组函数
灵活地使用字符串函数,可以实现很多类似 批量化处理 的功能。 因为语法中的 文件名 、 格式符 都是字符串,并且允许使用变量。 因此动态变化的字符串变量,可以实现动态的文件名和格式符。
11.3.1 字符串函数
-
str=trim(str)
:去掉字符串尾部的空格并返回; -
str=adjustl(str)
: 左对齐 字符串,长度不变; -
str=adjustr(str)
: 右对齐 字符串,长度不变; -
New_line()
:返回一个 回车符 (因为手动添加不同操作系统不同) -
c=char(i)/achar(i)
:返回编码(ASCⅡ码)为i的 字符 ; -
i=ichar(c)/iachar(c)
:返回c的 编码 (ASCⅡ码); -
i=len(c)
:返回字符串长度( 定义长度 ); -
i=len_trim(c)
:返回字符串长度( trim长度 ); -
i=index(c,cf[,back])
:在字符串c中查找 cf第一次出现的位置 ,可指定back=.true.
从后面开始查询; -
i=scan(c,cf[,back])
:在字符串c中查找 cf中的每一个字符 ,返回第一次出现的位置,可指定back=.true.
从后面开始查询; -
i=verify(c,cf[,back])
:在字符串c中查找 不属于cf中的字符 ,返回第一次出现的位置,可指定back=.true.
从后面开始查询; (参考代码:<a name="Back string"><a href="#string">
"string"</a></a>
)
11.3.2 数组函数
灵活使用数组函数可以减少很多循环代码:
-
b=reshape(array,shape)
:重塑数组外形; -
b=all(array[,dim])
:判断逻辑数组元素是否 全为真 ; -
b=any(array[,dim])
:判断逻辑数组元素是否 至少一个为真 ;if(all(满足))
等效于if(.not.any(.not.满足))
; (参考代码:<a name="Back any_all"><a href="#any_all">
"any_all"</a></a>
) any和all带有dim(维度)参数,可以用于将多维数组按低一级维度分别处理。
integer::a(3,3)=reshape([11,2,3,14,5,6,7,8,9],[3,3])
write(*,*) all(a<10,dim=1)!//将按第1维分别输出结果
-
n=count(array[,dim])
:统计逻辑数组中 真值的个数 ; -
n=maxval(array[,dim][,mask])
:获得数组中的 最大值 ,可附带条件,也可指定维度;
integer::a(5)=[11,2,3,1,-4]
maxval(a,mask=(a<3))!//求a中小于3的最大值
-
n=minval(array[,dim][,mask])
:获得数组中的 最小值 ,可附带条件,也可指定维度; -
[m,n,...]=maxloc(array[,dim][,mask])
:获得数组中 最大值的坐标 ,可附带条件,也可指定维度。注意返回值为在数组中的位置,数据结构为数组,大小为array的维数; 如果加入dim参数,返回值数组的数量为,原数组被拆分成该维度数组的数量,返回值数组的元素代表在该维度下数组内最大值的坐标 ; -
[m,n,...]=maxloc(array[,dim][,mask])
:获得数组中-[m,n,...]=maxloc(array[,dim][,mask])
:获得数组中最大值的下标,可附带条件,也可指定维度。,可附带条件,也可指定维度。 -
array=cshift(array,n[,dim])
:对数组进行 圆周平移 ,可使用dim指定按列(行)平移; -
array=eoshift(array,n[,dim])
:对数组进行 单向平移 ,可使用dim指定按列(行)平移;
11.4 其他函数
-
i=Command_Argument_Count()
:获得 命令行参数 的个数; -
call Get_Command_Argumen(number,str[,leng,status])
:获得某个命令行参数; (参考代码:<a name="Back args"><a href="#args">
"args"</a></a>
) -
call Get_Environment_Variable(name[,value,lenght,satatus,trim_name])
:获取名为name的环境变量,value为结果,length为长度,status为状态,trim_name为逻辑型输入,代表是否trim名称; - 时间相关函数:
-
i=Date_And_Time([date][,time][,zone[,values])
:获得系统日期、时间及时区,精度不高; -
call CPU_Time(time)
:获得当前CPU时钟,可用于检查程序段的执行时间。特点是多线程会多次计算,其他进程占用的CPU不计入; -
call System_Clock([count][,count_rate][,count_max])
:获取系统时间(基于1970年),也可用于检查程序段执行时间,特点是计算物理时间消耗。
12. 参考代码
12.1 <a name="Code.6">"Code.6"</a>(<a href="#Back Code.6">"Back"</a>)
12.1.1
<a name="Demo_IF_func">
"Demo_IF_func"
</a>(<a href="#Back Demo_IF_func">
"Back"
</a>
)
MODULE func
IMPLICIT NONE
CONTAINS
SUBROUTINE readcmd(keyword, pars)
! This SUBROUTINE reads a line of inpur from the commandline, and
! It returns the first phrase as keyword using variable 'keyword', and
! returns the following numerical parameters using the allocatable real
! array 'pars'
! The size of array 'pars' is determined by another FUNCTION ct_pars.
CHARACTER (LEN=:), ALLOCATABLE, INTENT (INOUT) :: keyword
REAL, ALLOCATABLE, INTENT (INOUT) :: pars(:)
CHARACTER (LEN=80) :: cmdline, kwt
INTEGER :: npars
READ (*, '(a80)') cmdline
READ (cmdline, *) kwt
IF (ALLOCATED(keyword)) DEALLOCATE(keyword)
IF (ALLOCATED(pars)) DEALLOCATE(pars)
ALLOCATE (CHARACTER(LEN=len_trim(adjustl(kwt))) :: keyword)
ALLOCATE (pars(ct_pars(cmdline)))
READ (cmdline, *) keyword, pars
END SUBROUTINE
FUNCTION ct_pars(cmdline) RESULT (npars)
! This FUNCTION counts the number of space separated parameters in the
! command line string 'cmdline'.
! Abbreviations:
! * par = Parameter
! * cmd = Command
! * tmp = Temporary
! * n = Number
CHARACTER (LEN=*), INTENT (IN) :: cmdline
CHARACTER (LEN=:), ALLOCATABLE :: ctmp
INTEGER :: npars, i
npars = 0
i = 1
ALLOCATE (CHARACTER(LEN=len_trim(adjustl(cmdline))) :: ctmp)
ctmp = trim(adjustl(cmdline))
IF (ctmp(i:i)/=' ') THEN
i = i + 1
npars = npars + 1
i = i + 1
IF (ctmp(i:i)/=' ') EXIT
END DO
END IF
IF (i==len(ctmp)) EXIT
END DO
END FUNCTION
END MODULE
12.1.2
<a name="Demo_IF_main">
"Demo_IF_main"
</a>(<a href="#Back Demo_IF_main">
"Back"
</a>
)
PROGRAM main
USE func
IMPLICIT NONE
REAL, PARAMETER :: pi = acos(-1.)
CHARACTER (LEN=:), ALLOCATABLE :: keywd
REAL, ALLOCATABLE :: pars(:)
! Try input the following strings in the commandline:
! cir 20.
! sqr 10.
! rect 1. 2.
CALL readcmd(keywd, pars)
IF (keywd=='end') THEN
ELSE IF (keywd=='cir' .OR. keywd=='sqr') THEN
IF (size(pars) /= 1) THEN
WRITE (*, *) 'Incorrect number of parameters.'
CYCLE
END IF
ELSE IF (keywd=='rect') THEN
IF (size(pars) /= 2) THEN
WRITE (*, *) 'Incorrect number of parameters.'
CYCLE
END IF
WRITE (*,*) 'Keyword unknown...'
CYCLE
END IF
IF (keywd=='cir') THEN
WRITE (*, *) 'Area = ', pi*pars(1)**2.
ELSE IF (keywd=='sqr') THEN
WRITE (*, *) 'Area = ', pars(1)**2.
ELSE IF (keywd=='rect') THEN
WRITE (*, *) 'Area = ', pars(1)*pars(2)
END IF
END DO
END PROGRAM
12.1.3
<a name="Demo_Type_main">
"Demo_Type_main"
</a>(<a href="#Back Demo_Type_main">
"Back"
</a>
)
MODULE types
TYPE :: var_base
CHARACTER (LEN=10) :: name
END TYPE
TYPE, EXTENDS (var_base) :: real_var
REAL :: r
END TYPE
TYPE, EXTENDS (var_base) :: int_var
INTEGER :: i
END TYPE
CONTAINS
SUBROUTINE pnt_var(var)
IMPLICIT NONE
CLASS (var_base), INTENT (IN) :: var
SELECT TYPE (var)
CLASS IS (real_var)
WRITE (*, *) 'Real Value ' // trim(var%name) // ' = ', var%r
CLASS IS (int_var)
WRITE (*, *) 'Integer Value ' // trim(var%name) // ' = ', var%i
CLASS DEFAULT
ERROR STOP 'Unknown type.'
END SELECT
END SUBROUTINE
END MODULE
PROGRAM main
USE types
IMPLICIT NONE
TYPE (real_var) :: vr = real_var('a', 3.14)
TYPE (int_var) :: vi = int_var('b', 3)
CALL pnt_var(vr)
CALL pnt_var(vi)
END PROGRAM
12.2
<a name="Code.7">
"Code.7"
</a>(<a href="#Back Code.7">
"Back"
</a>
)
12.2.1
<a name="ENUM">
"ENUM"
</a>(<a href="#Back ENUM">
"Back"
</a>
)
PROGRAM WWW_FCODE_CN
IMPLICIT NONE
!//ENUM WEEKDAY
INTEGER,PARAMETER:: SUN=0,MON=1,TUE=2,WED=3,THU=4,FRI=5,SAT=6
!//USER INPUTS AN INTEGER,THEN PRINT THE WEEKDAY NAME AND OTHER INFORMATIONS.
INTEGER:: WKDAY,IERR
WRITE(*,*)"PLEASE INPUT AN INTEGER NUMBER:"
READ(*,*)WKDAY
IERR=0
SELECT CASE (WKDAY)
CASE(SUN)
PRINT *,WKDAY,"IS SUNDAY"
CASE(MON)
PRINT *,WKDAY,"IS MONDAY"
CASE(TUE)
PRINT *,WKDAY,"IS TUESDAY"
CASE(WED)
PRINT *,WKDAY,"IS WEDNESDAY"
CASE(THU)
PRINT *,WKDAY,"IS THURSDAY"
CASE(FRI)
PRINT *,WKDAY,"IS FRIDAY"
CASE(SAT)
PRINT *,WKDAY,"IS SATURDAY "
CASE DEFAULT
PRINT *,"ARE YOU MARTIAN ?"
IERR=1
END SELECT
IF(IERR/=0)EXIT
SELECT CASE (WKDAY)
CASE(MON:FRI)
PRINT *,WKDAY,"IS WORKDAY"
CASE(SUN,SAT)
PRINT *,WKDAY,"IS WEEKEND"
CASE DEFAULT
PRINT *,"ARE YOU MARTIAN ?"
END SELECT
ENDDO
PRINT *,"PROGRAM TERMINATED"
END PROGRAM WWW_FCODE_CN
12.2.2
<a name="ARR">
"ARR"
</a>(<a href="#Back ARR">
"Back"
</a>
)
PROGRAM WWW_FCODE_CN
IMPLICIT NONE
INTEGER ::I
INTEGER,DIMENSION(5):: ARR
DATA ARR/1, 2, 5, 4, 3/
WRITE(*,'(A,3X,A)')'INDEX','VALUE'
DO I=1,5
WRITE(*,'(2I5)')I,ARR(I)
ENDDO
PRINT *
WRITE(*,'(A,1X,A)')'MAX_LOC','MAX_VAL'
WRITE(*,'(2I5)')MAXLOC(ARR),MAXVAL(ARR)
PRINT *,"PROGRAM TERMINATED"
END PROGRAM WWW_FCODE_CN
12.2.3
<a name="TYPE">
"TYPE"
</a>(<a href="#Back TYPE">
"Back"
</a>
)
PROGRAM WWW_FCODE_CN
IMPLICIT NONE
TYPE :: STUDENT
INTEGER :: ID
CHARACTER(LEN=100)::NAME
INTEGER:: CHINESESCO,MATHSCO,ENGLISHSCO
END TYPE
TYPE (STUDENT):: FLYBIRD
FLYBIRD%ID = 001
FLYBIRD%NAME = "FLYBIRD"
FLYBIRD%CHINESESCO = 60
FLYBIRD%MATHSCO = 97
FLYBIRD%ENGLISHSCO = 17
WRITE(*,*)'STUDENT INFOR:'
WRITE(*,*)"NAME: ",FLYBIRD%NAME
WRITE(*,*)"STUDENT NO.: ",FLYBIRD%ID
WRITE(*,*)"CHINESE SCORE:",FLYBIRD%CHINESESCO
WRITE(*,*)"MATH SCORE: ",FLYBIRD%MATHSCO
WRITE(*,*)"ENGLISH SCORE:",FLYBIRD%ENGLISHSCO
PRINT *,"PROGRAM TERMINATED"
END PROGRAM WWW_FCODE_CN
12.2.4
<a name="CLASS">
"CLASS"
</a>(<a href="#Back CLASS">
"Back"
</a>
)
MODULE STUDENTCLASS
TYPE STUDENT
INTEGER :: ID
CHARACTER(LEN=100)::NAME
INTEGER:: CHINESESCO,MATHSCO,ENGLISHSCO
CONTAINS
PROCEDURE :: PRINT_STUDENT_INFORTMATION => PRINT_STUDENT_INFORTMATION
END TYPE STUDENT
CONTAINS
SUBROUTINE PRINT_STUDENT_INFORTMATION(THIS)
CLASS (STUDENT) :: THIS
WRITE(*,*)'STUDENT INFOR:'
WRITE(*,*)"NAME: ",THIS%NAME
WRITE(*,*)"STUDENT NO.: ",THIS%ID
WRITE(*,*)"CHINESE SCORE:",THIS%CHINESESCO
WRITE(*,*)"MATH SCORE: ",THIS%MATHSCO
WRITE(*,*)"ENGLISH SCORE:",THIS%ENGLISHSCO
END SUBROUTINE PRINT_STUDENT_INFORTMATION
END MODULE STUDENTCLASS
PROGRAM WWW_FCODE_CN
USE STUDENTCLASS
IMPLICIT NONE
TYPE (STUDENT):: FLYBIRD
FLYBIRD=STUDENT(2,"FLYBIRD",60,97,17)
CALL FLYBIRD%PRINT_STUDENT_INFORTMATION()
PRINT *,"PROGRAM TERMINATED"
END PROGRAM WWW_FCODE_CN
12.2.5
<a name="ARR_TYPE">
"ARR_TYPE"
</a>(<a href="#Back ARR_TYPE">
"Back"
</a>
)
SUBROUTINE SWAP(AUTOARR1,AUTOARR2,N)
IMPLICIT NONE
INTEGER:: N
REAL,DIMENSION(N) :: AUTOARR1,AUTOARR2
REAL,ALLOCATABLE,DIMENSION(:)::DYNARR
ALLOCATE(DYNARR(N))
DYNARR(:N)=AUTOARR1(:N)
AUTOARR1(:N)=AUTOARR2(:N)
AUTOARR2(:N)=DYNARR(:N)
DEALLOCATE(DYNARR)
END SUBROUTINE SWAP
PROGRAM WWW_FCODE_CN
IMPLICIT NONE
INTEGER,PARAMETER:: NARR=5
REAL,PARAMETER:: PI=3.14159265
REAL,DIMENSION(NARR):: FIXARR1,FIXARR2
INTEGER::I
! ASSIGN
FIXARR1(:NARR)=(/(SIN(PI*(I-1.)/(NARR-1)),I=1,NARR)/)
! ASSIGN,FUNCTION ARG
FIXARR2(:NARR)=COS( (/(PI*(I-1.)/(NARR-1),I=1,NARR)/) )
WRITE(*,*)"BEFORE SWAP..."
WRITE(*,'(7F12.6)')FIXARR1(:NARR)
WRITE(*,'(7F12.6)')FIXARR2(:NARR)
CALL SWAP(FIXARR1,FIXARR2,NARR)
WRITE(*,*)"AFTER SWAP..."
WRITE(*,'(7F12.6)')FIXARR1(:NARR)
WRITE(*,'(7F12.6)')FIXARR2(:NARR)
END PROGRAM WWW_FCODE_CN
12.2.6
<a name="ARR_ACCESS">
"ARR_ACCESS"
</a>(<a href="#Back ARR_ACCESS">
"Back"
</a>
)
SUBROUTINE FUNARR (A,N)
IMPLICIT NONE
INTEGER:: N
REAL,DIMENSION(N):: A
WRITE(*,*)"FUNARR VECTOR SUBSCRIPT....IN"
WRITE(*,'(10F12.6)')A(:N)
WRITE(*,*)"FUNARR VECTOR SUBSCRIPT....OUT"
A(:N)=A(:N)-100
END SUBROUTINE
PROGRAM WWW_FCODE_CN
IMPLICIT NONE
INTEGER,PARAMETER:: NMAX=100
REAL,PARAMETER:: PI=3.14159265
REAL,DIMENSION(NMAX):: ARR
INTEGER,DIMENSION(5):: VECSUBSCP=(/1,4,6,7,5/)
INTEGER:: NARR,I
NARR=10
ARR=1.
ARR(:NARR)=1. !ÍÆ¼ö
WRITE(*,*)"ASSIGN TOTAL"
WRITE(*,'(10F12.6)')ARR(:NARR)
WRITE(*,*)
ARR(1)=-1.
WRITE(*,*)"ASSIGN ELEMENT"
WRITE(*,'(10F12.6)')ARR(:NARR)
WRITE(*,*)
ARR(2:4)=(/(SIN(PI*(I-1.)/(NARR-1)),I=2,4)/)
WRITE(*,*)"ASSIGN SLICE"
WRITE(*,'(10F12.6)')ARR(:NARR)
WRITE(*,*)
CALL FUNARR(ARR(2:4),3)
WRITE(*,*)"FUNARR SLICE"
WRITE(*,'(10F12.6)')ARR(:NARR)
WRITE(*,*)
ARR(VECSUBSCP(:3))=0.5
WRITE(*,*)"ASSIGN VECTOR SUBSCRIPT"
WRITE(*,'(10F12.6)')ARR(:NARR)
WRITE(*,*)
! ACTUAL ARGUMENT IS AN ARRAY SECTION WITH A VECTOR SUBSCRIPT
! DISCONTINUE,COPY,UNKNOWN POSITION,CANNOT BE INTENT(OUT)
CALL FUNARR(ARR(VECSUBSCP(3:5)),3)
WRITE(*,*)"FUNARR VECTOR SUBSCRIPT"
WRITE(*,'(10F12.6)')ARR(:NARR)
WRITE(*,*)
END PROGRAM WWW_FCODE_CN
12.2.7
<a name="BANDMAT">
"BANDMAT"
</a>(<a href="#Back BANDMAT">
"Back"
</a>
)
!FULL
! DO I=1,N
! DO J=1,N
! KMAX=MIN(I,J)-1
! MATRIX(I,J)=MATRIX(I,J)-SUM(MATRIX(I,1:KMAX)*MATRIX(1:KMAX,J))
! IF(I>J) MATRIX(I,J)=MATRIX(I,J)/MATRIX(J,J)
! ENDDO
! ENDDO
!BAND
! DO I=1,N
! JMIN=MAX(I-(P-1),1)
! JMAX=MIN(I+(Q-1),N)
! DO J=JMIN,JMAX
! KMIN=MAX(JMIN,J-(Q-1))
! KMAX=MIN(I,J)-1
! MATRIX(I,J)=MATRIX(I,J)-SUM(MATRIX(I,KMIN:KMAX)*MATRIX(KMIN:KMAX,J))
! IF(I>J) MATRIX(I,J)=MATRIX(I,J)/MATRIX(J,J)
! ENDDO
! ENDDO
!BAND,COMPRESSED
DO I=1,N
JMIN=MAX(I-(P-1),1)
JMAX=MIN(I+(Q-1),N)
DO J=JMIN,JMAX
KMIN=MAX(JMIN,J-(Q-1))
KMAX=MIN(I,J)-1
DO K=KMIN,KMAX
A=A+MATRIX(I,K-I+P)*MATRIX(K,J-K+P)
ENDDO
MATRIX(I,J-I+P)=MATRIX(I,J-I+P)-A
IF(I>J) MATRIX(I,J-I+P)=MATRIX(I,J-I+P)/MATRIX(J,P)
ENDDO
ENDDO
12.2.8
<a name="TYPE_ACCESS">
"TYPE_ACCESS"
</a>(<a href="#Back TYPE_ACCESS">
"Back"
</a>
)
PROGRAM WWW_FCODE_CN
IMPLICIT NONE
TYPE EMPLOYEE_NAME
CHARACTER(LEN=25):: LAST_NAME
CHARACTER(LEN=15):: FIRST_NAME
END TYPE
TYPE EMPLOYEE_ADDR
CHARACTER(LEN=20):: STREET_NAME
INTEGER(KIND=2) :: STREET_NUMBER
INTEGER(KIND=2) :: APT_NUMBER
CHARACTER(LEN=20):: CITY
CHARACTER(LEN=2) :: STATE
INTEGER(KIND=8) :: ZIP
END TYPE
TYPE EMPLOYEE_DATA
TYPE (EMPLOYEE_NAME):: NAME
TYPE (EMPLOYEE_ADDR):: ADDR
INTEGER(KIND=4) :: TELEPHONE
INTEGER(KIND=2) :: DATE_OF_BIRTH
INTEGER(KIND=2) :: DATE_OF_HIRE
INTEGER(KIND=2) :: SOCIAL_SECURITY(3)
LOGICAL(KIND=2) :: MARRIED
INTEGER(KIND=2) :: DEPENDENTS
END TYPE
TYPE (EMPLOYEE_DATA), DIMENSION (20) :: EMPLOYEES
DATA EMPLOYEES(1)%NAME /EMPLOYEE_NAME("BIRD","FLY")/
EMPLOYEES(2)=EMPLOYEE_DATA( EMPLOYEE_NAME("BIRD","FLY"), &
EMPLOYEE_ADDR("ZGC",12,34,"BEIJING","BJ",100000), &
10086,04,15,(/0,0,0/),.TRUE.,0 &
PRINT *,EMPLOYEES(1)%NAME%FIRST_NAME,EMPLOYEES(1)%NAME%LAST_NAME
PRINT *,EMPLOYEES(2)%NAME%FIRST_NAME,EMPLOYEES(2)%NAME%LAST_NAME
END PROGRAM WWW_FCODE_CN
12.3
<a name="Code.8">
"Code.8"
</a>(<a href="#Back Code.8">
"Back"
</a>
)
12.3.1
<a name="Func">
"Func"
</a>(<a href="#Back Func">
"Back"
</a>
)
Program www_fcode_cn
Implicit None
integer :: i , now_year = 2015
Integer :: PrintCopyRight
i = PrintCopyRight( "fcode.cn" , now_year )
i = PrintCopyRight( "www.fcode.cn" , now_year )
End Program www_fcode_cn
Integer Function PrintCopyRight( cDomain , now_year )
Implicit None
Character(len=*) , Intent( IN ) :: cDomain
Integer , Intent( IN ) :: now_year
write( * , * ) 'Powered by http://' , cDomain , now_year
PrintCopyRight = now_year
!return
End Function PrintCopyRight
12.3.2
<a name="WriteMatrix">
"WriteMatrix"
</a>(<a href="#Back WriteMatrix">
"Back"
</a>
)
Program www_fcode_cn
Implicit None
integer :: x(3,3) = reshape( &
[1,2,3, &
4,5,6, &
7,8,9],[3,3])
call WriteMatrix( x(1:2,3:3) )
contains
Subroutine WriteMatrix( iMat )
Integer , Intent( IN ) :: iMat(:,:)
integer :: i
Do i = 1 , size( iMat , dim = 2 )
write( * , * ) iMat( : , i )
End Do
End Subroutine WriteMatrix
End Program www_fcode_cn
12.3.3
<a name="passtype_degree">
"passtype_degree"
</a>(<a href="#Back passtype_degree">
"Back"
</a>
)
Module typedef
Implicit None
Integer , parameter :: DP = Selected_Real_Kind( p = 9 )
Type ST_Degree
Integer(Kind=2) :: d , m
real :: s
End Type ST_Degree
contains
Type( ST_Degree ) Function RDegree_To_TypeDegree( rrDeg ) result( stt )
Real(Kind=DP) ,Intent( IN ):: rrDeg
real(Kind=DP) :: rr
stt%d = int(rrDeg)
rr = rrDeg - stt%d
stt%m = ( rr ) * 60.0_DP
rr = rr - stt%m/60.0_DP
stt%s = ( rr ) * 60.0_DP**2
End Function RDegree_To_TypeDegree
End Module typedef
Program www_fcode_cn
use typedef
Implicit None
type( ST_Degree ) :: st
real(kind=DP) :: r
read( * , * ) r
st = RDegree_To_TypeDegree( r )
write(*,*) st
end do
End Program www_fcode_cn
12.3.4
<a name="passtype">
"passtype"
</a>(<a href="#Back passtype">
"Back"
</a>
)
Module typedef
Implicit None
type ST
real :: a , b
end type ST
End Module typedef
Program www_fcode_cn
use typedef
Implicit None
type ( ST ) :: st1
call sub( st1 )
End Program www_fcode_cn
Subroutine sub( stda )
use typedef
Implicit None
type ( ST ) :: stda
stda%a = 1.0
End Subroutine sub
12.3.5
<a name="optional">
"optional"
</a>(<a href="#Back optional">
"Back"
</a>
)
Program www_fcode_cn
Implicit None
call sub( 7 , 6 )
call sub( 5 , 4 , 3 )
contains
Subroutine sub( a , b , c )
Integer :: a , b
Integer , optional :: c
write( * , * ) a , b
if ( present( c ) ) write(*,*) 'optional:' , c
End Subroutine sub
End Program www_fcode_cn
12.3.6
<a name="func_name_arg">
"func_name_arg"
</a>(<a href="#Back func_name_arg">
"Back"
</a>
)
Program www_fcode_cn
Implicit None
write( * , * ) integral( x2 , 1.0 , 2.0 , 0.01 )
write( * , * ) 2.0**3/3 - 1.0**3/3
write( * , * ) integral( x3 , 1.0 , 2.0 , 0.01 )
write( * , * ) 2.0**4/4 - 1.0**4/4
contains
Real Function x2( x )
real :: x
x2 = x*x
End Function x2
Real Function x3( x )
real :: x
x3 = x*x*x
End Function x3
Real Function integral( func , low_bound , up_bound , delta ) result ( y )
!Real , External :: func
Interface
Real Function func( x )
real :: x
End Function func
End Interface
Real , intent( IN ) :: low_bound , up_bound , delta
integer :: i
real :: x
y = 0.0
x = low_bound + delta/2.0
Do !// Do x = low_bound , up_bound , delta
y = y + func( x )
x = x + delta
if ( x > up_bound ) exit
End Do
y = y * delta
End Function integral
End Program www_fcode_cn
12.3.7
<a name="recursive_do">
"recursive_do"
</a>(<a href="#Back recursive_do">
"Back"
</a>
)
Program www_fcode_cn
Implicit None
Integer , parameter :: DP = Selected_Real_Kind( p = 12 )
real(Kind=DP) :: r = sqrt( 2.0_DP )
integer :: i
Do i = 1 , 60
r = sqrt( 2.0_DP * r )
write(*,*) r
End Do
End Program www_fcode_cn
12.3.8
<a name="recursive">
"recursive"
</a>(<a href="#Back recursive">
"Back"
</a>
)
module def
Integer , parameter :: DP = Selected_Real_Kind( p = 12 )
end module def
Program www_fcode_cn
use def
Implicit None
Real(Kind=DP) :: Func , r
r = Func( 5 )
write(*,*) r !// 此处不能写为 write(*,*) Func(5)
End Program www_fcode_cn
Recursive Function Func( n ) result( r )
use def
Implicit None
Real(Kind=DP) :: r
Integer :: n
!write(*,*) 'in:',n
if ( n > 0 ) then
r = sqrt( 2.0_DP * Func(n-1) )
r = sqrt( 2.0_DP )
end if
!write(*,*) 'out:',n,r
End Function Func
12.4
<a name="Code.9">
"Code.9"
</a>(<a href="#Back Code.9">
"Back"
</a>
)
12.4.1
<a name="interface">
"interface"
</a>(<a href="#Back interface">
"Back"
</a>
)
Program www_fcode_cn
Implicit None
Interface
Subroutine sub( x )
Real :: x(:,:)
End Subroutine sub
End Interface
Real :: a( 30 , 30 )
call sub( a )
call call_sub()
End Program www_fcode_cn
Subroutine sub( x )
Implicit None
Real :: x(:,:)
write(*,*) size(x,dim=1) , size(x,dim=2)
End Subroutine sub
Subroutine call_sub()
Implicit None
Interface
Subroutine sub( x )
Real :: x(:,:)
End Subroutine sub
End Interface
Real :: b( 15 , 15 )
call sub( b )
End Subroutine call_sub
12.4.2
<a name="module">
"module"
</a>(<a href="#Back module">
"Back"
</a>
)
Module fcode_Mod
Implicit None
Real , save :: a = 0 , b = 0 , c = 0
contains
Subroutine fc_Init()
a = 3 ; b = 4 ; c = 5
End Subroutine fc_Init
End Module fcode_Mod
Program main
use fcode_Mod
Implicit None
write( * , * ) a , b , c
call fc_Init()
write( * , * ) a , b , c
a = 1 ; b = 2 ; c = 3
write( * , * ) a , b , c
call fc_Init()
write( * , * ) a , b , c
End Program main
12.4.3
<a name="module_view">
"module_view"
</a>(<a href="#Back module_view">
"Back"
</a>
)
Module mod_name
Implicit None
real , public :: pub_var =1.
real , private :: prvat_var =2.
real , protected :: prtct_var =3.
contains
Subroutine sub1( dum_a , dum_b )
Real , Intent( IN ) :: dum_a
Real , Intent( INOUT ) :: dum_b
real :: loc_1 , loc_2
write(*,*) dum_a , dum_b , pub_var , prvat_var , prtct_var
loc_1 = 5.0
write(*,*) f1( loc_1 , dum_b )
End Subroutine sub1
Integer Function f1( dum_1 , dum_2 )
Real , Intent( IN ) :: dum_1
Real , Intent( INOUT ) :: dum_2
real :: loc_1 , loc_2
f1 = dum_1 + dum_2 + pub_var + prvat_var + prtct_var
dum_2 = 40.0
End Function f1
End Module mod_name
Program main
use mod_name
Implicit None
!//call sub1( prvat_var , pub_var ) !// 无法编译,因为 prvat_var 是私有的
!//call sub1( pub_var , prtct_var ) !// 无法编译,因为 prtct_var 是被保护的,无法当做Intent(OUT) 的变量
call sub1( prtct_var , pub_var )
End Program main
12.4.4
<a name="module_protect">
"module_protect"
</a>(<a href="#Back module_protect">
"Back"
</a>
)
Module Grid_Mod
Implicit None
Type ST_Info
integer :: m , n
End Type ST_Info
Type( ST_Info ) , protected :: grd_stInfo
Real , allocatable :: grd_rData( : , : )
Character(len=512) :: grd_cFilename
contains
Logical Function Grid_Alloc( x , y ) result( bAlloc )
Integer , Intent( IN ) :: x , y
integer :: i
bAlloc = .false.
if ( (x * y) > 1000 ) return
allocate( grd_rData( x , y ) , STAT = i )
if ( i/=0 ) return
grd_stInfo%m = x
grd_stInfo%n = y
bAlloc = .true.
End Function Grid_Alloc
Logical Function Grid_DeAlloc()
Grid_DeAlloc = .false.
if ( allocated( grd_rData ) ) then
Deallocate( grd_rData )
Grid_DeAlloc = .true.
end if
End Function Grid_DeAlloc
End Module Grid_Mod
Program main
use Grid_Mod
Implicit None
logical :: b
integer :: i
b = Grid_Alloc( 5 , 5 )
if ( .not.b ) stop "cann't allocate"
write( * , '(a,g0,"x",g0)' ) 'Grid Size: ' , grd_stInfo
!//grd_stInfo%m = 50 !// 出错
call FillGrid()
write( * , * ) 'Grid: '
Do i = 1 , grd_stInfo%n
write( * , '(999f6.2)' ) grd_rData( : , i )
End Do
b = Grid_Dealloc()
End Program main
Subroutine FillGrid()
use Grid_Mod
Implicit None
integer :: i , j
Do i = 1 , grd_stInfo%n
Do j = 1 , grd_stInfo%m
grd_rData(i,j) = i * j
End Do
End Do
End Subroutine FillGrid
12.4.5
<a name="module_oper">
"module_oper"
</a>(<a href="#Back module_oper">
"Back"
</a>
)
12.5
<a name="Code.10">
"Code.10"
</a>(<a href="#Back Code.10">
"Back"
</a>
)
12.5.1
<a name="Read_2D_text">
"Read_2D_text"
</a>(<a href="#Back Read_2D_text">
"Back"
</a>
)
Program www_fcode_cn
Implicit None
Real :: a( 3 , 4 )
integer :: FILE_IN , i
Open( NewUnit = FILE_IN , File = "2d_text.txt" )
Do i = 1 , size(a,dim=2)
Read( FILE_IN , * ) a( : , i )
write( * , * ) a( : , i )
End Do
Close( FILE_IN )
End Program www_fcode_cn
12.5.2
<a name="Read_text">
"Read_text"
</a>(<a href="#Back Read_text">
"Back"
</a>
)
Program www_fcode_cn
Implicit None
Real :: a(3,7) , r
integer :: FILE_IN , i
Open( NewUnit = FILE_IN , File = "text.txt" )
Read( FILE_IN , * ) !//跳过第一行
Read( FILE_IN , * ) !//跳过第二行
Do i = 1 , 7
Read( FILE_IN , * ) r , a( : , i )
write( * , * ) a(:,i)
End Do
Close( FILE_IN )
End Program www_fcode_cn
12.5.3
<a name="Read_text_alloc">
"Read_text_alloc"
</a>(<a href="#Back Read_text_alloc">
"Back"
</a>
)
Module DFile_Mod
Implicit None
contains
Integer Function GetDataN( cStr )
Character( Len = * ) , Intent( IN ) :: cStr
Integer :: i
Logical :: bIsSeparator , bIsQuote
GetDataN = 0
bIsSeparator = .TRUE.
bIsQuote = .FALSE.
Do i = 1 , Len_Trim( cStr )
Select Case( cStr(i:i) )
Case( '"' , "'" ) !// 如果遇到引号
If ( .Not.bIsQuote ) GetDataN = GetDataN + 1 !//如果不在引号中,则增加一个数据
bIsQuote = .Not.bIsQuote !// 引号结束或开始
bIsSeparator = .FALSE.
Case( " " , "," , char(9) ) !// 如果遇到分隔符
If ( .Not.bIsQuote ) then !// 分隔符如果不在引号中
bIsSeparator = .TRUE.
End If
Case Default
If ( bIsSeparator ) then
GetDataN = GetDataN + 1
End If
bIsSeparator = .FALSE.
End Select
End Do
End Function GetDataN
Integer Function GetFileN( iFileUnit )
!// 此函数应在打开文件后立即调用。调用后读取位置返回文件起始位置
Implicit None
Integer , Intent( IN ) :: iFileUnit
character( Len = 1 ) :: cDummy
integer :: ierr
GetFileN = 0
Rewind( iFileUnit )
Read( iFileUnit , * , ioStat = ierr ) cDummy
If( ierr /= 0 ) Exit
GetFileN = GetFileN + 1
End Do
Rewind( iFileUnit )
End Function GetFileN
End Module DFile_Mod
Program www_fcode_cn
Use DFile_Mod
Implicit None
Real , allocatable :: a(:,:)
Character(len=512) :: cStr
Real :: r
integer :: FILE_IN , i , nRow , nCol
Open( NewUnit = FILE_IN , File = "text.txt" )
nRow = GetFileN( FILE_IN ) - 2 !//获得文件行数
Read( FILE_IN , * ) !//跳过第一行
Read( FILE_IN , * ) !//跳过第二行
Read( FILE_IN , '(a512)' ) cStr!//读取第三行
nCol = GetDataN( cStr ) - 1 !//获得第三行有多少列
write( *, '("Row:",g0," Col: ",g0)' ) nRow , nCol
Allocate( a( nCol , nRow ) )
Backspace( FILE_IN ) !//退回到第三行
Do i = 1 , nRow
Read( FILE_IN , * ) r , a( : , i )
write( * , * ) a(:,i)
End Do
Deallocate( a )
Close( FILE_IN )
End Program www_fcode_cn
12.5.4
<a name="Read_text_direct">
"Read_text_direct"
</a>(<a href="#Back Read_text_direct">
"Back"
</a>
)
Program pcm
Implicit None
Integer :: FID , i
Real :: a, b , c
Open( NewUnit=FID , File="text_direct.txt" , form="formatted" , access="Direct" , RecL=64 )
Read( FID , '(i7,3(1x,g15.6))' ,Rec=5 ) i , a , b , c
Write( * , * ) i , a , b , c
Close( FID )
End Program pcm
12.5.5
<a name="write_myfile">
"write_myfile"
</a>(<a href="#Back write_myfile">
"Back"
</a>
)
Program www_fcode_cn
Implicit None
Integer :: FID , a = 1000 , c = 2338021
real :: b = 3.141592654
character(len=8) :: s = "fcode.cn"
Open(NewUnit=FID,File="myfile.bin",form='unformatted')
Write( FID ) a , s !//4+8=12 , 加上前后4 = 20
Write( FID ) b , c !//4+4=8 , 加上前后4 = 16
Close(FID)
End Program www_fcode_cn
12.5.6
<a name="read_myfile">
"read_myfile"
</a>(<a href="#Back read_myfile">
"Back"
</a>
)
Program www_fcode_cn
Implicit None
Integer :: FID , a , c
real :: b
character(len=8) :: s
Open(NewUnit=FID,File="myfile.bin",form='unformatted')
Read( FID ) a , s !//4+8=12 , 加上前后4 = 20
Read( FID ) b , c !//4+4=8 , 加上前后4 = 16
write(*,*) a , b , c , s
Close(FID)
End Program www_fcode_cn
12.5.7
<a name="write_mydirect">
"write_mydirect"
</a>(<a href="#Back write_mydirect">
"Back"
</a>
)
Program www_fcode_cn
Implicit None
Integer :: FID , a = 1000 , c = 2338021
real :: b = 3.141592654
character(len=8) :: s = "fcode.cn"
Open(NewUnit=FID,File="mydirect.bin",form='unformatted',access='direct',RecL=12)
Write( FID ,REC = 2 ) a , s !//4+8=12
Write( FID ,REC = 1 ) b , c !//4+4=8,会填充4个字节的 00
Close(FID)
End Program www_fcode_cn
12.5.8
<a name="read_mydirect">
"read_mydirect"
</a>(<a href="#Back read_mydirect">
"Back"
</a>
)
Program www_fcode_cn
Implicit None
Integer :: FID , a , c
real :: b
character(len=8) :: s
Open(NewUnit=FID,File="mydirect.bin",form='unformatted',access='direct',RecL=12)
Read( FID ,REC = 1 ) b , c !//4+4=8,会填充4个字节的 00
Read( FID ,REC = 2 ) a , s !//4+8=12
write(*,*) a , b , c , s
Close(FID)
End Program www_fcode_cn
12.5.9
<a name="read_fcode_test_direct">
"read_fcode_test_direct"
</a>(<a href="#Back read_fcode_test_direct">
"Back"
</a>
)
Program www_fcode_cn
Implicit None
integer :: FID , i , j , k
integer(kind=2) :: m , n
character(len=4) :: flag
real(kind=8) :: minX , maxX , minY , maxY , minZ , maxZ
real , allocatable :: g(:,:)
Open(NewUnit=FID,File="fcode_test.grd",form='unformatted',access='direct',RecL=8)
Read(FID,REC=1) flag , m , n
write( * , '("m=",g0," n=",g0)' ) m , n
allocate( g(m,n) )
read(FID,REC=2) minX
read(FID,REC=3) maxX
read(FID,REC=4) minY
read(FID,REC=5) maxY
read(FID,REC=6) minZ
read(FID,REC=7) maxZ
write( * , '(6f7.3)' ) minX , maxX , minY , maxY , minZ , maxZ
k = 8
Do i = 1 , n
Do j = 1 , m , 2
Read(FID,REC=k) g( j:j+1 , i )
k = k + 1
End Do
write(*,'(99f7.3)') g( : , i )
End Do
Deallocate( g )
Close(FID)
End Program www_fcode_cn
12.5.10
<a name="read_fcode_test_stream">
"read_fcode_test_stream"
</a>(<a href="#Back read_fcode_test_stream">
"Back"
</a>
)
Program www_fcode_cn
Implicit None
integer :: FID , i
integer(kind=2) :: m , n
character(len=4) :: flag
real(kind=8) :: minX , maxX , minY , maxY , minZ , maxZ
real , allocatable :: g(:,:)
Open(NewUnit=FID,File="fcode_test.grd",form='unformatted',access='stream')
Read(FID) flag , m , n , minX , maxX , minY , maxY , minZ , maxZ
write( * , '("m=",g0," n=",g0)' ) m , n
write( * , '(6f7.3)' ) minX , maxX , minY , maxY , minZ , maxZ
allocate( g(m,n) )
Read(FID) g
Do i = 1 , n
write(*,'(99f7.3)') g( : , i )
End Do
Deallocate( g )
Close(FID)
End Program www_fcode_cn
12.5.11
<a name="read_fcode_test_stream_type">
"read_fcode_test_stream_type"
</a>(<a href="#Back read_fcode_test_stream_type">
"Back"
</a>
)
Program www_fcode_cn
Implicit None
integer :: FID , i
type :: GRD_HEAD
SEQUENCE !// 重要
character(len=4) :: flag
integer(kind=2) :: m , n
real(kind=8) :: minX , maxX , minY , maxY , minZ , maxZ
end type
type ( GRD_HEAD ) :: stHead
real , allocatable :: g(:,:)
Open(NewUnit=FID,File="fcode_test.grd",form='unformatted',access='stream')
Read(FID) stHead
write( * , '("m=",g0," n=",g0)' ) stHead%m , stHead%n
allocate( g(stHead%m,stHead%n) )
Read(FID) g
Do i = 1 , stHead%n
write(*,'(99f7.3)') g( : , i )
End Do
Deallocate( g )
Close(FID)
End Program www_fcode_cn
12.5.12
<a name="cwd">
"cwd"
</a>(<a href="#Back cwd">
"Back"
</a>
)
Program www_fcode_cn
Implicit None
character(len=512) :: c
call GET_COMMAND_ARGUMENT( 0 , c )
write(*,'(a,/,a)') 'exe path:',trim(c)
call GETCWD( c )
write(*,'(a,/,a)') 'working directory:',trim(c)
open(12,File="where.txt")
close(12)
read(*,*)
End Program www_fcode_cn
12.6
<a name="Code.11">
"Code.11"
</a>(<a href="#Back Code.11">
"Back"
</a>
)
12.6.1
<a name="string">
"string"
</a>(<a href="#Back string">
"Back"
</a>
)
Program www_fcode_cn
Implicit None
character(len=64) :: str,temp,cfind
real :: weight , distance
integer :: i
str = "Informatin: weight=1.5kg distance=100.0km"
!//换成下面这行的格式也可以正常读取
!str = "Informatin: distance=100km weight= 01.5KG"
cfind= "weight="
i = index(str,trim(cfind))
temp = str(i+len_trim(cfind):)
i = verify(temp,"1234567890. ")
read( temp(:i-1) , * ) weight
write(*,*) trim(cfind) , weight
cfind= "distance="
i = index(str,trim(cfind))
temp = str(i+len_trim(cfind):)
i = verify(temp,"1234567890. ")
read( temp(:i-1) , * ) distance
write(*,*) trim(cfind) , distance
End Program www_fcode_cn
12.6.2
<a name="any_all">
"any_all"
</a>(<a href="#Back any_all">
"Back"
</a>
)
Program www_fcode_cn
Implicit None
integer :: a(5) = [1,2,3,-5,3]
write(*,*) 'a>0',a>0
write(*,*) 'all',all(a>0)
write(*,*) 'a==2',a==2
write(*,*) 'any',any(a==2)
End Program www_fcode_cn
12.6.3
<a name="args">
"args"
</a>(<a href="#Back args">
"Back"
</a>
)
Program www_fcode_cn
Implicit None
integer :: i , leng