没想到2020年了我还在学习Fortran,因为老师说组里的人都用的Fortran,虽然我还是更偏向C++但暂且还是得会用Fortran吧,简单学习一下~
1 Fortran基础知识
&
符号标记下一行继续书写Fortran程序单元:
- 声明,位于程序开头(变量声明放一起)
- 执行
- 终止, end program
必须按这顺序来,比如声明不能插到执行语句里面,这点让我有点没法接受。
变量类型声明
integer:: var1, var2 real:: var1 character(len=*) :: var1 !没数字时默认长度为1,若字符赋值长度小于len,则多余的用空格补 logical :: var1 type, parameter :: PI = 3.14 !常数定义,type为前面四种
整数和实数的转换
integer:: x x2=real(x) x3=nint(x2) !四舍五入
尽可能使用整形指数、因为实数的幂精度更低
fortran中乘方操作符是
**
:a**b
永远不要对负数进行实数幂运算(没有定义负数的自然对数)
输入输出
real a read(*,*) a !输入时用逗号或空格分隔 ! 第一数据域: 从哪个输入输出单元读入,*表示计算机标准输入 ! 第二数据域: 读入数据的格式,*表示表控输入(自由格式输入)
implicit none
: 必须显示声明变量
2 分支结构
逻辑数据类型:
.true. 和 .false.
(注意有两个句点)/=
,不等于 的写法比较特殊if分支
if (condition) then ... end if
select case (expr) !根据expr的值判断属于哪个case case (1) ... case (2,3,4) ... case (5:) ... case default ... end select
写分支条件时时刻考虑四舍五入误差!尽量用不等式而不是
==
if (abs(x-10.)<=1.e-5) then ... end if
3 循环和字符操作
DO循环
do ... end do
DO WHILE循环(一般不用或尽量少用)
do while (condition) ... end do
DO计数循环
do i = start, end, incr ... end do
CYCLE和EXIT语句
do i=1,5 if (i==3) cycle !相当于continue,退出当前循环 if (i==4) exit !直接退出外层循环 write(*,*) i end do
字符串小tips
character(len=12) :: a='hello'//'world'//"as" !连接操作符 write(*,*) a(1:1) !子串抽取方式
4 基本I/O操作
- 格式化write
write(*,100) i,result 100 format ('The result for iteration ', I3, ' is ', F7.3) !100为语句标号, I3表示3个字符宽,F7.3表示7个字符宽、三位小数
- 格式描述符汇总:(以输出为例,输入差不多)
整数输出——I
rIw.m !其中 r代表重复计数(使用次数、即有多少变量套用该格式) ! w代表域宽、即字符总数 ! m代表显示的最小位数
实数输出——F
rFw.d ! d代表小数位数
指数计数法输出——E
rEw.d ! 一般w>=d+7
科学计数法——ES
rESw.d ! 同上,为了不让星号填充需要w>=d+7
逻辑输出——L
rLw !一般为右对齐
字符输出——A
rA(w) !也是右对齐
水平定位——X和T
nX Tc !n为要插入的空格数, c为列号 ! Tc跳转到特定一列
格式可通过
()
进行嵌套改变输出行——
/
,可单独放在一个逗号里或和接在其他格式符之后write(*,100) a,b 100 format(1X,F7.0/,/,F7.1) ! 推荐在format开头写1X空一格
双精度输出——D编辑符
Dw.d !使用方法与E编辑符相仿,只是把字母“E”换成“D”。F编辑符也可用于双精度数据的输出,和用于实型数据输出相似。
- 文件处理
open(unit = fp, file = 'filename', status = 'unknown', iostat = ierr) ! fp为一个数字,I/O单元号; status为old时打开旧文件、new打开新文件、replace替换文件、scratch打开临时文件 write(fp,100) x 100 format (...) close(unit=fp)
5 数组
声明数组
type, dimension(n) :: arr (/1,2,3,4,5/) !数组建构器 !下标默认从1到n integer, dimension(5)::arr2=(/ (i,i=1,5) /) ! 隐式DO循环
指定下标范围
real, dimension(0:5)::arr3
数组操作
- 维度相同的两个数组可以直接相加减(相当于每个元素加减)
- 指定部分数组:
real, dimension(n) :: arr=(/1,2,3,4,5/) arr(:) !整个数组 arr(1:3:1) !第三个数为增量,若省略则默认1 arr(:5) !从起始到下标为5的数 arr(::1) !1代表增量,前面一个:代表整个数组
- 时刻记得隐式Do循环
write(*,*) (arr(i), i=1,5,1) ! arg1,arg2,..., index=istart,iend,incr
6 过程
6.1 子程序(subroutine)
subroutine name(a,b,c)
real, intent(in):: a(*) !无法检测越界,不应该用这种形式
!tip: 永远别用不定大小的形参数组
real, intent(in):: b
character(len=*), intent(in) :: string !*声明字符变量长度
real, intent(out):: c
! intent属性声明输入输出
end subroutine name
call name(arg)
指针,pass-by-reference: 子程序和主程序是用地址传递方案通信的!
(注意主程序和子程序相对应的参数类型一致)tip: 永远不要在子程序中使用stop语句
子程序能够作为参数传递
subroutine sub_as_arg(sub,x) external:: sub !作为参数传递时需要声明external real, intenet(in)::x end subroutine sub_as_arg
6.2 模块
一种提供程序单元共享数据的方式(独立编译)
module modulename implicit none save !保证在模块中声明的数据被保护在不同过程间的引用中,一定记得加 real, dimension(5)::arr contains ! 模块也可以含子程序和函数 subroutine sub1() ... end subroutine sub1 end module modulename use modulename
为什么要在模块里套个子程序?直接写个子程序文件也行啊
答: 模块中编译过程和使用模块时,过程接口的所有细节对编译器都是有效的,即编译器可以捕捉程序员调用时可能犯的错误(如参数类型、个数错误), explicit interface。而不在模块里的过程为implicit interface推荐把过程放在模块里
6.3 函数(function subprogram)
声明函数
integer function name(a) ! or function name(a) integer :: name !注意函数声明和调用时都要声明其类型
函数有多个输入和一个输出,当需要多个输出时请用子程序!!
函数记得用
intent(in)
声明所有输入参数,防止函数意外修改(函数和子程序都是通过指针传参的)函数可以当做参数传递,前提是被声明为外部量(external)
real, external :: func1
7 数组的高级特性
多维数组
real,dimension(3,6)::sum
多维数组内存分配: 一列一列来的以列储存数组数据、按行序读取数据
(第一个下标变化最快、最后一个下标变化最慢)数组构造器产生一维数组,需要借助
reshape
初始化多维数组real,dimension(4,3)::arr arr=reshape((/1,1,1,1,2,2,2,2,3,3,3,3/),(/4,3/)) !数列为: ! 1 2 3 ! 1 2 3 ! 1 2 3 ! 1 2 3
用read语句能够方便地初始化多维数组
!文件数据为: 1 1 1 1 2 2 2 2 3 3 3 3 real,dimension(4,3)::arr open(unit = 7, file = 'data.dat', status = 'old', iostat = ierr) read(7,*) arr !当然也可以使用类似 ((arr(i,j),j=1,3),i=1,4) 的隐式do循环,不过麻烦一点;但可以避免矩阵转置的问题
masked array assignment, 对多个数组元素进行操作
(对满足特定条件的元素执行指定操作)where(value>0.) logval=log(value) elsewhere logval=-99999. endwhere !value 可以是数组,where逐个对元素进行运算
forall, 逐个操作数组元素
forall(i=1:n,j=1:m,work(i,j)/=0.) work(i,j)=1./work(i,j) end forall ! 相比于嵌套的do循环,forall的优点在于语句可以按照任意顺来执行 ! 若有多条语句,则上一条完全执行完了(对所有元素)再进行下一条的执行
可分配数组
real, allocatable, dimension(:,:) :: arr1 allocate(arr1(100,0:10),stat=status) ! 分配成功,stat返回0
传递多维数组
!plan 1 subroutine process (data,n,m) integer, intent(in)::n,m !通过每一维度取值范围传递多维数组 end subroutine process !plan 2 subroutine process1 (data) real , intent(in), dimension(:,:) :: data !不定结构的形参数组(只有子程序有显式接口时才能使用) ! 使用时常在子程序外套一个模块 end subroutine process1 !plan 3 !不定大小的数组(用*代替最后一个维度),不应该再使用
save属性和语句
real,save::sums !保证在调用过程之间不修改保存的局部变量和数组。(需要多次调用时) ! save不能出现在与形参的关联中 ! 类型声明中初始化的局部变量都会自动保存,隐含save属性
fortran程序在退出过程后,默认所有局部变量和数组的值成为未定义,用save可以保存这些变量
save:: var1,var2 save !不指定变量时,该过程或模块中所有的局部变量都会被无改变的保存起来
自动数组: 子程序中的非形参数组,过程完成后自动释放空间
pure
前缀的function或subroutine的限制:
不修改输入参数、局部变量无save属性(不初始化)…elemental
前缀,定义一个过程为‘逐元’的,即该过程用标量输入和输出定义。内部过程
内部过程(internal procedures)包含在宿主程序单元(host program unit)中,只能在宿主中调用,且能够访问宿主中的变量。用于完成一些重复低级操作program name ... contains real function name2(...) ... end function name2 end program name
8 附加的内置数据类型
real
,默认4字节(32位),最多7个有效数字?real(kind=1):: val_1 real(kind=2):: val_2 real(kind=4):: val_3 real(kind=8):: val_4 !对不同计算机、一个32位的实数可能是kind=1,也可能是kind=4... write(*,'("The kind for single precision is ",I2)') kind(0.0) !可用上述命令判断计算机对类别号的定义
为了使程序移植性好,一般用selected_real_kind函数
kind_number=selected_real_kind(p=precision,r=range) real(kind=kind_number)::var ! p为精度位数,r为所需指数范围(10^r)
9 派生数据类型
任何数值、元素组合在一起,定义用户自己的类型
type :: type_name sequence !当需要派生数据占据连续内存空间时使用,否则随机分配内存空间 character(len=14):: first integer::age character:sex end type type_name type(type_name):: variables !声明此类型的变量 var=type_name('first_var',18,'M') !直接初始化方式 var%age=15 !通过%访问成员
通常将一个程序中所有派生数据定义在一个模块中
派生数据类型的函数(常放入模块的contains里)
type::vector ... end type vector type(vector) function vector_add(v1,v2) ... end function vector_add
associate结构,临时关联某个变量或表达式,增强可读性(fortran2003特性)
associate(x=>.... & y=>.... ) statements end associate
10 过程和模块的高级特性
作用域相关
递归过程
recursive subroutine name(n,result) ... call name(n-1,result) end subroutine recursive function fact(n) RESULT(answer) ! 对于递归函数比较特殊,需指定一个形参answer接受调用返回值 answer=n*fact(n-1) end function fact
可选参数与参数顺序
calc(first=3.,second=1.,third=2.) !指定参数顺序,有可选参数时比较实用 function calc(a,b,c) integer, intent(in),optional::a !定义一个可选参数 if present(a) !如果使用时有这个参数 ... end if end function calc
显式接口,除了module还可用interface
interface subroutine name(a,b) ... end subroutine name end interface
每一个接口都是个独立的作用域
不行学不下去了,我还是去啃我的C++ Primer吧。