参考
1、[Fortran权威指南/(英)奇弗斯(Chivers,I.),(英)斯莱索尔姆(Sleightholme,J.)著;陈宝国等译.–北京:人民邮电出版社,2009.10]
2、https://www.yiibai.com/fortran/fortran_basic_input_output.html
3、[Fortran 程序设计:第四版/(美)史蒂芬·查普曼著;王志强等译.-北京:中国电力出版社,2018.10(2022.8重印)]
基础内容
! 作为注释标记
Fortran规则:
- 自由源格式
- 不区分大小写
- 存在语句顺序
- PROGRAM
- 类型声明语句
- 处理与I/O语句
- END PROGRAM
- !用于引入注释
- 变量名称长度最多31字符,包括_
- 每行最多132个字符
- 最多可以允许39个连续行
- 如果不使用
IMPLICIT NONE
语句,则没有显式声明的变量被看成默认的类型。即:首个字符是A-H或O-Z,则默认为REAL型。若为I-N,则默认INTEGER型。
1、输出你的名字
1 | PROGRAM ch0701 !程序命名 |
2、计算平均值
遵循的原则:声明所有变量
1 |
|
运算
- 加:+
- 减:-
- 乘:*
- 除:/
- 乘方:**
注意:整型运算最终结果也是整型!
注意:实型运算最终结果需要考虑精度!尤其是结果不是有限小数的情况
字符常数和变量
- 显式变量:
需要定义1
INTEGER :: VAR1[,VAR2,VAR3]
- REAL 定义实型
- INTEGER 定义整型
- CHARACTER 定义字符型
字符型可以定义长度,缺省时为1。1
CHARACTER(len=<len>) ::
注:实际上,‘::’是可选的。其有无不影响程序运行。存在的目的是为了兼容早期的Fortran。不过,::看起来有种独特的美感(某本教程的作者说它很可爱),所以还是加上。
- 默认式变量:i,j,k,l,m,n为开头的变量名为INTEGER整型;其他为REAL实型。
常数定义:
1 | type,PARAMETER :: name=value [,name1=value1 ...] |
注:大写小写都可以。大写看起来好看,小写看起来更舒服(对于像我这样英语不好的人)。
运算顺序
- 圆括号优先。
- 从右到左做指数运算
- 从左到右做乘法和除法运算
- 从左到右做加法和减法运算
运算法则与C或Python相同,但是需要注意:
- 求幂运算:从右往左的顺序
- 加减法:不需要注意运算顺序
- 乘法除法:非整型运算不一定按照从左到右的顺序,需要加括号保证顺序
注意:使用时加括号时最好的方式。
个人更喜欢多加括号。有些教程说熟练掌握,记住顺序。但是对于非计算机专业使用而言,加上括号的可读性更高,不需要纠结自己是否用对了。例如++i这种运算,个人认为十分“反人类”。
字符格式转换函数
- INT(x) 实数-整数 截尾取整
- NINT(x) 四舍五入取整
- CEILING(x) 大于或等于x的最小整数
- FLOOR(x) 小于或等于x的最大整数
- REAL(X) 整数转化实数
前面即行x表示实数,X表示整数
内置函数
内置函数为最通用的函数。
函数名 | 参数类型 | 结果类型 | 说明 |
---|---|---|---|
SQRT(X) | real | real | X的平方根 |
ABS(X) | real/integer | * | 绝对值 |
ACHAR(I) | |||
SIN(X) | 弧度制 | ||
SIND(X) | 角度制 | ||
COS(X) | |||
COSD(X) | |||
TAN(X) | |||
TAND(X) | |||
EXP(X) | |||
LOG(X) | |||
LOGIO(X) | |||
IACHAR(C) | |||
MOD(A,B) | |||
MAX(A,B) | |||
MIN(A,B) | |||
ASIN(X) | |||
ASIND(X) | |||
ACOS(X) | |||
ACOSD(X) | |||
ATAN(X) | |||
ATAND(X) | |||
ATAN2(Y/X) | |||
ATAN2D(Y,X) |
注:*指输出类型与输入类型相同。
表控输入与输出
表控输入:变量列表中的变量类型决定输入数据需要的格式。
READ(*,*)input_list
WRITE(*,*)output_list
PRINT *. output_list
# (*,*)表示任意长度,使用``,``隔开
WRITE与PRINT等价。
关于PRINT:
PRINT fmt, i/o_list
fmt是读取格式说明,示例如下:
PRINT 100, x , y
100 FORMAT (2F10.2)
string = '(2F10.2)'
PRINT string,x,y
IMPLICIT NONE语句
加入
IMPLICIT NONE
所有变量需要显式定义,因此可以避免计算机因为变量名出错而输出错误的答案。
debug
养成良好编程习惯:
- 使用IMPLICIT NONE 语句。
- 返回所有输入值以及计量单位。(这也是为什么在mineos中看到运行时返回输入值的原因)
- 初始化所有变量:显式输入,不要忘记初始化变量。
- 圆括号使赋值语句更清晰。
截断
注意截断。整型截断为舍入
PARAMETER 语句
REAL , PARAMETER :: value=12
类似于C语言中的固定变量,即在类型声明语句之前将某个变量名与变量关联,从而不可更改该变量名的值。
类似于#define
。
精度问题
变量状态
- 已确定的状态 defined:已定义值
- 未确定的状态 undefined:未定义值
思考题
运行示例4
1 | PROGRAM ch0804 |
在运行ch0804出现错误:
Error: Result of exponentiation at (1) exceeds the range of INTEGER(4)
表示结果超出整型范围。
疑问:??? 未解决
计算摆的周期
1 | PROGRAM period_of_pendulum |
1 | Please input the length of pendulum: |
在计算器中的输出值为:2.006066681。
进制转换
1 | program base_conversion |
输出结果如下:
1 | 1.00000000 |
1.0保持不变,但是其他值均发生了改变。
简单减法
1 | program subtract |
输出结果:
1 | 1.00020003 |
理论结果为0.0001,但是结果输出不同了,而且a和b的值也发生了变化。这与十进制与二进制的转换有关。
fortran下的表达式等价原则
1 | program expression_equivalence |
输出结果
1 | x-y=9.99927521E-04 |
计算器:
1 | x-y=0.001 |
LibreOffice:
1 | x-y=0.001 |
ch0804变形
1 | PROGRAM ch0804changed |
输出结果:
1 | 6 | light_year=9.46*10**12 |
疑问: ???未解决
数组
DIMENSION 属性
使用dimension属性将变量定义为数组:
real , dimension(len) :: wages
integer , dimension(len) :: sample
赋值数组内的元素:
wages(1)=1.
wages = [1.,2.,33.]
wages = 1 # 赋值为相同值
(arg1, arg2, ... , index = istart, iend, incr) # 隐藏的循环初始化赋值
示例1:
integer, dimension(25)::array4=[(0,i=1,4),5*j,j=1,5]
结果:
0,0,0,0,5,0,0,0,0,10,0,0,0,0,15,0,0,0,0,20
示例2:
1 | program ch0901 |
输出:
1 | Type in the rainfall values |
改变数组下标取值范围
一般使用1,2,...,N
表示下标索引。但是有时以0
开始更为方便。
指定下标的方式为:
real, dimension(lower_bound : upper_bound) :: array
数组宽度=上界值-下界值+1
数组下标越界
边界检测消耗运算资源。debug时开启,运行时关闭。
开启边界检测器在运行时输入:
# windows情况下
ifort -check filename.f90 # 编译
filename.f90 # 运行
使用命名常数
REAL, DIMENSION(MAX_SIZE)::array1
MAX_SIZE可以修改。
使用整个数组
数组a
与数组b
相加,为两个数组中的逐个元素相加。但是两个数组纬度必须相同!标量值与数组相同,将作用于每个元素。
使用部分数组
部分数组,即数组的子集。
subscript_1 : subscript_2 : stride # 最小下标,最大下标,下标增量
若缺省,则分别为:数组的最小下标,数组的最大下标,1。
可变数组
定义可变数组:
1 | real, dimension(:,:), allocatable :: array1,array2 |
分配内存空间:
1 | allocate(array1(len1,len2),array2(len3,len4)) |
释放内存空间:
1 | deallocate(array1,array2) |
数组输入输出
数组元素输入输出
可以使用WRITE
语句。
1 | WRITE (*,100)a(1),a(2),a(3) |
隐式DO循环:
1 | WRITE (*,100) (a(i), i=1,3) |
1 | WRITE (unit, format)(arg1, arg2,...,index = istart, iend, incr) |
arg为要输入或者输出的数值。index控制循环。READ
同上。
arg如果为数组,可以使用:arg(index)
表示输出。
嵌套隐式DO循环:
1 | write(*,100) ((i,j,j=1,3),i=1,2) |
输出:
1 | 1 1 |
整个数组的输入输出
1 | real, dimension(5)::a=[1,2,3,4,5] |
do循环
do counter = start, end, increment
...... !语句
enddo
其中,increment默认为1,可以省略。
使用参数设置数组大小
例:
1 | program ch0902 |
输出:
1 | type in the weight for person 1 |
语句:
real , dimension(number) :: weight
若下界未指定,则为1。
格式化输出形式
format输出
常用描述符号与举例:
参考:https://www.yiibai.com/fortran/fortran_basic_input_output.html
例句:
1 | print 100 |
思考题
2、计算5个数的平均值
注意! 定义数组时使用:
分隔上下界,使用do循环使用,
分隔上下界。
1 | program average_5 |
输出:
1 | Please input 5 numbers, the 1 one is: |
3-1、改写体重总数,计算身高总数和平均数
1 | program ch0902 |
忘了改program名了,但是也证明了内部program的名称和外部文件名称可以不同,以外部文件名为准。
输出:
(ps:输出有些杂乱,因为还没有掌握输出书写的一些方式。)
1 | type in the weight and height for person 1 use space as the interval: |
3-2 计算BIM值
使用语句:
a.out < data.dat > results.txt
实现文件的输入和输出操作。
1 | program ch3_2 |
输出:
1 | 文件 result.txt |
6、计算摆的周期(制表)
1 | program ch6 |
输出:
1 | Length period |
分支结构
IF
1 | IF(logical_expr1)THEN |
嵌套IF语句命名使其运行更加稳妥。
1 | outer IF(test1)THEN |
逻辑IF语句
IF(logical_expr) a,b,c
算数IF语句
IF(expr) a, b, c
- <0 执行a
- =0 执行b
- 大于0 执行c
SELECT CASE结构
1 | [name:]SELECT CASE( case_expr) |
命名case结构,使得更加清晰。
DEBUGE
在分支结构中插入WRITE是最简单的方式,输出表明运行到哪一步。
测试相等性时,使用近似测试。
循环与字符操作
do循环
不确定性执行:
1 | [name:]DO |
EXP为真,执行EXIT,退出循环。
do while循环
1 | DO WHILE(EXP) |
EXP为真,循环进行,为假时立刻退出循环。
迭代计数循环
[name:]do counter = start, end, increment
...... !语句
end do [name]
- increment 默认为1,可以省略。
- counter 整型变量,作为循环计数使用。
- start end 计数循环的参数,表示开始和结束。
- 计算规则:
counter*increment≤end*increment
时进行循环。
注:为了防止永久循环,不可在循环中修改控制变量。
CYCLE语句与EXIT语句
CYCLE
使用CYCLE使得当前循环被终止,循环返回顶部。
在嵌套循环中,使用CYCLE [name]
语句,可以使其CYCLE
正确的循环。
IF(j=2)CYCLE outer
可以实现在内部循环中终止当前的外循环。
EXIT
使用EXIT
使得循环被终止。
字符操作
子串提取
定义CHARACTER时。使用len=<len>
语句,使得变量只能存储相应长度的字符。
连接操作符
//
是连接操作符。
将两个或者多个字符串或者子串合并成一个大的字符串。
1 | a = 'ABCDEFGHIJ' |
c
的值为:’ABC45‘
内置字符函数
参考3 表4-1
I/O内容
格式和格式化WRITE
1 | WRITE(*,100)i,result |
- 100 指的是FORMAT的语句标号
- I3 F7.3 格式符号
格式描述符
符号 | 含义 |
---|---|
c | 列号 |
d | 实数输入或者输出的小数位右边的位数 |
m | 要显示的最小位数,不够补零 |
n | 要跳过的空格数 |
r | 重复计数,一组或者一个描述符的使用次数,即该格式符重复r次 |
w | 域宽,输入或者输出占用的字符数;若不能显示所有字符则使用*代替 |
整数输出:I描述符
rIw
或者rIw.m
。
实数输出:F描述符
rFw.d
右对齐显示,不足补空格。
实数输出:E描述符
科学计数法:rEw.d
±0.ddddE±ee
一般w大于等于d+7。才能容纳整个数字字符。
科学计数法:ES描述符
该格式为输出数值在1.0-10.0之间的数。rESw.d
逻辑输出:L描述符
rLw
右对齐显示逻辑输出T
与F
字符输出:A描述符
rA
或者rAw
水平定位:X T描述符
nX
缓冲区中插入间距。n
:插入的空格数。Tc
在缓冲区中跳过特定列。c
:转到的列号。转到相应列号开始打印。
注意:确保T跳到的位置不会覆盖之前的内容。
格式描述符重复使用
可以使用语句2(I6,F10.2)
表示(I6,F10.2,I6,F10.2)
。
可以使用*(I6,F10.2)
使其一直重复直至没有输出数据。
改变输出行:/描述符
在打印机中使用。
格式使用
如果FORMAT
中的格式符不足,则在没有重复次数的开始括号处重新开始。
格式化READ语句
整数输入:I描述符
rIw
实数输入:F描述符
rFw.d
注:在实数描述符中输入,一定要输入带有小数点的整数!否则会产生错误!
逻辑输入:L描述符
rLw
字符输入:A描述符
rA
或rAw
水平定位:X和T描述符
垂直定位:/描述符
文件及文件处理
OPEN
将一个文件与一个给定的i/o单元号关联起来。格式为:
OPEN(open_list)
open_list子句包含存取文件信息等。
- UNIT=指明与文件关联的io单元号。
UNIT=int_expr
- FILE=指定要打开的文件名。
FILE=char_expr
- STATUS=指定要打开文件的状态。
STATUS=char_expr
其中,char_expr为下列值之一:”OLD”,”NEW”,”REPLACE”,”SCRATCH”,”UNKNOW”。 - ACTION=指定一个文件是否以只读、只写或者读写方式打开。
ACTION=char_expr
其中,char_expr为下列值之一:“READ”,”WRITE”,”READWRITE”。 - IOSTAT=指定一个整数变量名,打开操作的状态可以返回到这个变量中。
IOSTAT=int_var
其中,Open成功执行后,ISOTAT值为0。如果不成功,为一个包含错误信息的正整数。 - IOMSG=指定一个字符变量名,包含错误信息。
IOMSG=chart_var
字符变量。OPEN语句成功执行,字符变量内容不变。否则返回错误信息。
CLOSE
CLOSE(close_list)
clost_list必须包含一个指定io单元号的子句,可以指定其它选项。(目前未阅读到那一章)。
磁盘文件读写
READ(unit,*)变量名
READ
中()
内第一个为读取的文件的io单元号。WRITE同。
文件定位
unit:与要使用的文件关联的IO单元号
BACKSPACE
BACKSPACE(UNIT=unit)
回退一条记录。
REWIND
REWIND(UNIT=unit)
从文件头重新开始文件。
过程
外部过程:把每个子任务作为独立的程序单元进行编码。
两种外部过程:子例程和函数子程序(函数)。
子例程
CALL语句使用过程名调用,格式如下:
定义:
1 | SUBROUTINE subroutine_name (argument_list) |
INTENT属性:
定义argument_list
中的变量,需要定义IN或OUT变量,以表示变量是输入还是输出。
1 | REAL, INTENT(IN)::IN_VALUE1 |
其中,INOUT
可以写为:IN OUT
。
使用INTENT属性可以避免修改了IN
类型的变量导致之后的该变量被改变,从而产生的错误。记住每一个形参都要设置INTENT属性!!!
定义内部变量时,定义临时变量。
1 | REAL ::temp |
调用:
1 | CALL subroutine_name (argument_list) |
调用时注意argument_list
中的变量类型必须一一对应!
传递数组
数组可以显式传递或者不定大小传递。
1 | REAL, INTENT(IN), DIMENSION(n)::data1 # n为数组大小,在输入列表中给出。 |
注:目前基本不使用不定数组的格式,因为会产生越界问题,且不易监测。
传递字符变量
1 | CHARACTER (len=*), INTENT(IN)::STRING1 |
使用*
表示长度即可。因为没有必要在编译时必须知道字符串长度。
小问题
不要在子例程中加入STOP
语句,否则程序会“神秘终止”。
正确方法是对可能存在的错误进行检测。使用IF
语句进行检测,采取相应操作。例如返回error=0
。
模块共享数据
模块,使用MODULE
声明。
1 | MODULE shared_data |
使用模块中的数据:该语句必须出现在其他语句之前。
1 | USE shared_data |
注意:share的数据和局部变量不要重名!
模块过程
1 | MODULE my_subs |
使用:
1 | USE my_subs |
即可在调用程序单元中使用子例程sub1。
使用模块调用子进程,可以使程序完全掌握子进程内的所有参数等,返回必要的错误信息。而直接调用子进程,主程序无法了解子进程中的任何信息。
FORTRAN函数
函数:结果是单个数值、逻辑值、字符串或数组的过程。只能返回一个值。
用户定义函数格式:
1 | FUNCTION name (参数列表) |
由于函数必须返回一个值,因此需要定义函数返回值类型,有两种,如下:
1 | INTEGER FUNCTION my_function(i,j) |
1 | FUNCTION my_function(i,j) |
函数名为输出变量的名称,并且该变量已被定义,因此不需要在函数中再次定义。但是输入参数需要定义!
过程作为参数传递给其它过程
用户定义函数作为参数传递
1 | # 外部定义函数,声明函数是外部调用的 |
子例程作为参数传递
在SUBROUTINE中,传递名为sub
的子例程。
1 | SUBROUTINE subs(sub, ...) |
部分内置子进程
EXP() 求指数
- 单精度 EXP()
- 双精度 DEXP()
- 复数 CEXP()
复数
示例:
1 | program test |
一些已经不再使用的内容
equivalence 等价
示例:
equivalence (nn,abuf)
nn和abuf等价,即二者完全相同,改变任何一方都会改变另一方。在定义变量时写入。