基本读了一遍,把剩下的总结完。
基本的调用不解释了,这里强调一些细节。
end
表示函数的结束,不过实际上使用retrun
是标准的用法,并且可以选择让函数在执行到一般的时候return
返回。subroutine
和function
均是传址,但是一般不建议function
中改变传参的值。如果所传的参数值发生改变,那么主程序中的变量值也会变化,这点和matlab或者C中均不同。subroutine
和function
的区别,后者有返回值,另外function
调用之前要先声明real,external :: add
。注意这里的externcal
不是C语言中防止重复编译的关键字,是声明add
是一个函数而不是变量。另外函数声明也可以不用单独写出,例如下面program ex0808
implicit none
real a,b
real add
add(a,b) = a+b
write(*,*) add(a,3.0)
end
common
基本的用法不讲了,这里强调一下全局变量是根据相对位置关系对应的,一个common
中写入多个变量时,一定要注意顺序
int a,b,c
common /abc/ a,b,c
而common
的赋初值不能直接像数组一样使用DATA
,而是要用block data
,具体用法如下,类似subroutine
一样写在程序之外单独一块:
program ex0812
...
end
block data
implicit none
integer a,b,c
common /abc/ a,b,c
data a,b,c /1,2,3/
end block data
实际上在程序中用一般的赋值方式对common
中的变量赋值就可以进行初始化,这一用法的用意除了标准化之外,执行顺序也不同。block data
中声明的初值,在主程序调用之前就已经赋值了。所以这也要求程序中只能有声明和赋初值相关,不能出现程序命令,而且附加parameter
在这里赋初值也不支持。
实际上也属于函数本身的注意事项,但是和变量特别相关。
float
类型的变量被以int
类型的编码读取,数据是错误的。a
时,子函数中的变量定义可以是integer a
,integer a(3)
,integer a(4)
,integer a(2,2)
。甚至子函数中数组长度超过主程序中,语法都是允许的,数据读取时是根据连续内存读取的,所以并不影响使用。但是编译在这里不进行长度校对,如果编程人员不小心,就会出现很多数组操作改变其他变量值的问题,即下标溢出(实操吃过很多次亏,这个一定要小心)即一般子函数中变量的生存周期,只维持到当前子函数执行结束。如果希望像C中静态变量一样,子函数退出后,变量中的值保留,就需要使用关键字save
。
integer :: cout = 1
save cout
而不使用save
关键字的子函数,在有的编译器中,变量也不会在每次执行后清零。
即子函数传参时,传递的参数中含有子函数名,这样的子函数,需要在主程序中声明,例如
real,external :: func !声明func是一个自定义函数
real,intrinsic :: sin !声明sin是库函数
call ExecFunc(func)
两种技巧,
integer,intent(in) :: a !子函数中值不允许改变
integer,intent(out):: b !子函数中值必须改变
这种技巧可以帮助我们进行语法检查,减少调试时的工作量
option
这个关键字subroutine sub(a,b)
...
integer :: a
integer,optional :: b
if (present(b)) then
....
else
...
end if
return
end
而其中的present(b)
表示是否传入了参数b
。从而实现不定个数传递
函数接口interface
其实是命令语句之前的声明语句,在特殊返回值或者调用参数不固定时会被要求使用,具体参阅书中Page189,这里只给出一个例子:
program ex0825
implicit none
interface
function reandom10(lbound,ubound)
implicit none
real :: lbound,ubound
real :: reandom10(10) !函数的返回值为数组
end function
end interface
...
end
第一个为某个子函数为另外一个子函数专属调用时,可以使用contain
直接写在函数体中。后面两个为并行使用。
可以用来封装程序模块,功能和include
有点像,但是并不是纯粹的添加代码段,而是具有一定的语法。主要用途有声明全局变量,声明type结构体,声明函数等。在后面第11章会有详细的介绍。
write
和read
的使用不重复了,这里强调后面出现的新知识。
inquire(file=filename,exist=alive)
Page237,可以检查一个文件是否存在
open(unit = fileld, file=filename, access="direct", &
form="formatted",recl=6,status="old")
Page254,使用direct
模式读取文件,允许在文件中的读取位置进行跳跃,而非按顺序读取。
open(unit = fileld, file=filename, access="direct", form="unformatted",&
access="direct",recl=1,status="replace")
Page258,读取二进制文件时,通过recl=1
设置读取字段的单位长度,1代表为1bytes还是4bytes要看不同的编译器。
write(unit=string,fmt="(I2,'+',I2,'=',I2)") a,b,a+b
write(*,*) string
Page260,成为internal file(内部文件),将字符串生成并赋值给字符串变量,然后字符串可以正常输出。
integer :: a=1,b=2,c=3
namelist /na/ a,b,c
write(*,nml=na)
可以将na
名下的所有变量按顺序输出,不用使用do循环。
和C一样的指针的概念,但是具体的语法约定有很多的不同,先给出最基础的使用方式
方式一
integer, target:: a=1 !声明一个可以当成目标的变量
integer,pointer:: p !声明一个可以指向整数的指针
p=>a !将指针指向变量a的地址
a=2 !改变变量的值
p=3 !改变地址的值,注意,是值,不是地址
方式二
integer,pointer :: p
allocate(p)
p=100
需要注意的就是fortran的指针变量赋值时,也是改变指向的地址的值,这点和C不同。另外就是使用动态数组记得使用deallocate(p)
释放内存。为了方便使用,还有三个工具函数。
这个蛮厉害的,感觉比C要强大
integer,target :: b(5)=(/1,2,3,4,5/)
integer,pointer :: a(:)
a=>b !数组b的所有值都给a
a=>b(1:3) !只把数组b的前三个元素给a
a=>b(1:5:2) !把第1,3,5个元素给a
或者使用动态数组
integer,pointer :: a(:)
allocate(a(5))
a=(/1,2,3,4,5/)
write(*,*) a
deallocate(a)
但注意allocate声明成指针数组时,生存周期不止在当前函数结束,一定要deallocate释放内存。
指针可以是数组的参数或者返回值,但是需要使用接口interface。另外指针参数声明时不需要intent关键字。给一个例子Page282
program ex1007
...
integer,pointer :: p(:)
interface
function getmin(p)
integer,pointer :: p(:)
integer,pointer :: getmin
end function
end interface
...
getmin(p)
end
指针在fortran中可以方便数组中固定元素的访址,以及较大数据的交换给出例子分别如下
b=>a(5,5,5)
a(5,5,5) = 1 !常规写法,但是找到这个元素需要经过地址计算
b=1 !速度更快
type person
...
end type
type(person):: a,b,temp
type(person),pointer :: pa,pb,pt
!正常交换交换的数据较多
temp = a
a = b
b = temp
!指针长只有4bytes,交换指针要比交换数据本身更快
!但是注意此时a,b本身是否交换了需要再验证一下
pt=>pa
pa=>pb
pb=>pt
其实还是蛮基础的,就是数据结构常见的内容,链表,双向链表和环形链表。
基础的用法给出一个,
module bank
implicit none
private money
public LoadMoney,SaveMoney,Report
integer::money = 1000000
contains
subroutine LoadMoney(num)
...
end subroutine
...
program main
use bank
...
和C语言中的类不同,不需要实例,use module
之后直接调用里面的函数就可以了,这一点其实更加直观。而关键字private
表示只有module
内部可以使用,public
表示主程序中也可以调用,并且关键字对函数和变量都可以使用。另外module中可以通过contain
添加函数。module
中的变量默认的生存周期为整个函数,所以money
变量在每次调用之后值改变但不会清零。
另外强调的一点是,module中函数之外声明的变量,函数本身可以使用。即money
变量可以在子函数LoadMoney
中使用,这不需要传参。这和C语言中的类又很像。
直接给实例
program main
use A, aa=>va !把module A中的变量aa改名为va使用
use B, only:vc !只用module B中的变量vc
use C, only:bb=>bc !前两者的组合
另外,module是支持嵌套的,这和类的继承思想类似,但private声明的部分不会被继承。
除去前面函数的参数或者返回值比较特殊时需要使用interface之外,该命令还可以被用来实现重载(overload,2333骨王)和自定义操作符,直接给出两者例子
module MA
interface show
module procedure show_int
module procedure show_character
end interface
contains
!两个子函数的具体实现
...
end module
program main
...
call show(1)
call show("abc")
...
end
module MA
...
interface operator(+)
module procedure add
end interface
contain
integer function add(a,b)
...
end function
end module
program main
...
type(ta) a,b,c
c = a+b
...
end
就是说自己定义了结构体情形下+
号代表的意义,就可以很方便的使用了,另外这个操作符可以是任意符号,不一定非得是当前存在的。
首先讲了一下Visual Fortran如何进行调试,这部分略过。后半部分讲了优化,这是需要留意的知识。提升执行速度书中罗列了几种,避免重复运算等算法相关的优化这里不提。
2*A=A+A
A**2=A*A
X**2+2*X+3 = ((X+2)*X)+3
I = 1+1
F= 1.0+1.0
E = (real(a)/c)*(real(b)/d)
E = real(a*b)/(c*d)
parameter
访存比一般的变量要快,数组访存需要经过额外的运算integer,parameter :: d = 2.0
c = 2.0
a = b/2.0
a = b/c !这个最慢
a = b/d
do k = 1,kmax
do j = 1,jmax
do i = 1,imax
s = s+ A(i,j,k)
end do
end do
end do
最后说下,提高运算速度是好事,但是可读性有时候更加重要,占主要运算时间的语句进行执行速度优化即可。
思想就是将代码中添加注释,并且要调整函数使两者的函数编码等匹配。不过书中主要是将win下如何混编介绍了,linux下未讲。不太用,写了也记不住,真的需要的时候回来补充。
跳过,作图用MATLAB它不香吗,Tecplot它不香吗,ParaView它不香吗。
跳过,参考数学系大一必修的计算方法课程,或者一般研究生学的数值方法,要比这里详细。
跳过,C语言版本的学过,Fortran版本的现在真的有地方会用嘛…
新知识,IMSL是一个module模板,如果当前编译器内置的话,直接在main函数中use
即可。它包含几个模块
结束!!!终于!!!还是有点长的。
最近完成了一个后台阶的算例是从.f文件改写成.f90了,但是仅仅是能够运行。接下来花一点时间将程序改写得更加结构化一些。