Fortran学习

没想到2020年了我还在学习Fortran,因为老师说组里的人都用的Fortran,虽然我还是更偏向C++但暂且还是得会用Fortran吧,简单学习一下~


1 Fortran基础知识

  • &符号标记下一行继续书写

  • Fortran程序单元:

  1. 声明,位于程序开头(变量声明放一起)
  2. 执行
  3. 终止, 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个字符宽、三位小数
  • 格式描述符汇总:(以输出为例,输入差不多)
  1. 整数输出——I

    rIw.m
    !其中 r代表重复计数(使用次数、即有多少变量套用该格式)
    ! w代表域宽、即字符总数
    ! m代表显示的最小位数
  2. 实数输出——F

    rFw.d
    ! d代表小数位数
  3. 指数计数法输出——E

    rEw.d
    ! 一般w>=d+7
  4. 科学计数法——ES

    rESw.d
    ! 同上,为了不让星号填充需要w>=d+7
  5. 逻辑输出——L

    rLw
    !一般为右对齐
  6. 字符输出——A

    rA(w)
    !也是右对齐
  7. 水平定位——X和T

    nX  Tc
    !n为要插入的空格数, c为列号
    ! Tc跳转到特定一列
  8. 格式可通过()进行嵌套

  9. 改变输出行—— /,可单独放在一个逗号里或和接在其他格式符之后

    write(*,100) a,b
    100 format1X,F7.0/,/,F7.1! 推荐在format开头写1X空一格
  10. 双精度输出——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
  • 数组操作

  1. 维度相同的两个数组可以直接相加减(相当于每个元素加减)
  2. 指定部分数组:
    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吧。

Author: zcp
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source zcp !
 Previous
《やはり俺の青春ラブコメはまちがっている》抜粋
1 前書きまず前提として、何年前にこの作品のアニメを見たことがある(今はほぼ忘れた)。日本語を勉強し、そして暇つぶしに何か読もうと思い、何冊のラノベをKindleで購入しました。値段が少し高いだが、読んでいて面白かったから買ってよ
Next 
《美丽新世界》个人摘录
1 前言最近看书的时间不太够了,一是要做毕设(进度本来就有点落后)、二是刚买个显示屏游戏去了;不知不觉今年就过了1/4,读书任务还是得完成的,只能减一减睡眠时间了。《美丽新世界》还是挺有意思的,可能很多人还会向往这样的生活吧,实在
  TOC