***本文对于Fortran95的编程规则进行了概括性的总结,以程序分析为主,练习与论述相结合。本文原创,第一次使用CSDN博客,对于文中代码或语句的错误尽请谅解,希望可以帮助到广大Fortran初学者。***
program main !程序开始,main是program的名字,完全自定义
write(*,*) “Hello” !主程序
stop !终止程序
end program main !end用于封装代码,表示代码编写完毕。end后的内容可省略,下同。
(1)输入:
read(*,*) a !从键盘读入,第一个星号表示输入位置使用默认值(键盘),第二个星号代表不特别设置输出格式。
read(5,*) a !作用同上,5代表键盘
read(unit=5,fmt=*) a !作用同上
多变量输入:
read(*,*) a,b,c !输入时可以用空格或者逗号来分隔输入,也可以输入一个数就按一次Enter。
(2)输出:
write(*,*) “text” !在屏幕上输出text,第一个星号表示输出位置使用默认值(屏幕),第二个星号代表不特别设置输出格式。
输出双引号要连用如"“text”“将输出"text”
其他使用规则:
write(*,*)”data:”,a !将输出data:(变量a的值)
write(*,*)a,”+”,b,”=”,a+b !可以直接放入一个算式
(3)输出:
print *,”text” !只能用于屏幕输出,星号代表不特别设置输出格式。
PS:(*,*)完整写为(unit=*,fmt=*)
。其中unit为输入/输出位置,如屏幕,文件等;fmt为格式。如这两项都写成*,则按默认的方式进行,即上面描述的。
(4)格式化的输入输出
write(*,100) a !使用行代码100的要求格式来输出变量a
100 format(I4) !行代码100的格式要求。
部分使用说明:
(Iw[.m])表示输出时只会用w个字符宽来输出整数,至少输出m个数字,当设置的文本框过大时前面补自动空格,不足时会输出星号*作为警告,设置的数字多余实际输出时前面自动补零,[]内的表示可以省略。
例:
write(*,”(I5.3)”)10 !将会输出两个空格后有010。
其他括号内format的使用说明见P53。
(1)数学表达式
+ - * / **(乘幂) ()括号
PS:乘幂运算:
a=4.0**0.5 !
a=8.0**(1.0/3.0) !
(2)数学函数库
可以直接调用如:
sin() cos() tan() !均使用弧度制
sqrt() log() abs()
声明规则:可以使用下划线或数字但前缀必须是英文字母。
(1)整数类型
声明:
integer a !默认为长整型
声明:
integer::a !作用同上
分为长整型和短整型,(可存储数值范围大小不同)
长整型声明:
integer(kind=4) a !kind=4即要使用四个bytes来存放整数
短整型声明:
integer(kind=2) a
可以一行同时声明多个变量integer a,b,c
(2)浮点型
real a !默认单精度
或
real::a !默认单精度
real(kind=4) a !单精度浮点数(6位)
real(kind=8) a !双精度浮点数(15位)
PS:在使用浮点型时要使用如3.0/2.0的格式做除法,最好令整数也全部写成带小数点形式,确保运算正确。
在单精度浮点型中,10的10次方可表示为1E10,10的-10次方可表示为1E-10。
双精度浮点型中,表示为1D10和1D-10。
如a声明成整型,在程序执行过程中可以用库函数real(a)对其进行浮点型转换,这可以用于同时具有整型、浮点型数据的混合运算情况。
(3)复数
复数声明与赋值:
complex::a !声明复数的方法
或
complex a
复数中也分为单精度和双精度
a=(1.0,2.0) ! a=1+2i
complex(kind=4) a !单精度
complex(kind=8) a !双精度
(4)字符及字符串
声明:
character a !单声明一个字符
声明:
character(len=10) a !声明10个字符的长度给a
PS:双引号封装不影响单引号使用,使用双引号要连续敲两个,单引号封装使用单引号要连续敲两个。
空格也是字符,字符属于数据所以区分大小写。
其他使用规则:
character(len=10) a
a=”123456”
a(5)=”67” !改变第五个及以后的字符,原字符串变成123467
a(1:2)=”GO” !字符串最前面两个字符将变成GO
a(6:6)=”!” !字符串第六个字符将变成感叹号
c=a//b !两个连续的斜杠可以使a、b两个字符串连接起来变成c
(5)逻辑变量
声明:
logical a
设置逻辑变量:
a=.true. !前后两个点,设置为真
b=.false. !前后两个点,设置为假
PS:当write这两个变量时,屏幕上只会对应出现T和F,逻辑变量可以设置占用内存大小但意义不大。
Fortran标准中的变量不一定要经过程序声明将被自动定义,一般在program开始后加入implicit none指令来关闭默认类型功能。
real pi !定义一个浮点型变量pi
parameter(pi=3.14159) !在parameter后的括号内出现的变量都会变成常数,且只能设置一次,后续不能更改。
real,parameter ::pi=3.14159 !声明与参数设置同时进行
声明与赋值同时进行,如
integer::a=1
real::b=2.0
complex::c=(1.0,2.0)
character(len=10)::str=”Fortran 90”
integer a,b
equivalence(a,b)
声明a,b这两个变量共用一块内存,即始终等价,改变一个就可以同时改变其他变量。
所有声明要在程序开头处就声明结束,即implicit后到程序执行指令开始之前完成。
相当于自定义结构体类型,以下以创建一个person数据类型为例,共含有5个元素,都使用定义+赋值的方式。
定义:
type :: person !数据类型名
character(len=30) :: name ! 人名(元素1)
integer :: age ! 年龄(元素2)
integer :: height ! 身高
integer :: weight ! 体重
character(len=80) :: address ! 地址
end type person
声明:
type(person) :: a ! 声明一个person类型的变量a
设置元素:
read(*,*) a%name ! 填写变量a内的name元素
直接设置全部元素:
a=person(“Peter”, 20, 170, 60, ”Taiwan”)
(1)逻辑运算符
== 判断是否相等
/= 判断是否不等
PS:Fortran77使用英文缩写做判断,不使用符合
.AND. 交集(两侧)
.OR. 并集(两侧)
.NOT. 非(后面连接的表达式不成立时,真)
.EQV. 两侧逻辑运算结果相同,真
.NEQV. 两侧逻辑运算结果不同,真
.true. 真
.false. 假
PS:对于数据类型中的逻辑变量,是本身保存有真、假的布尔变量,可以直接放在IF的括号内使用。程序中可以用逻辑表达式来设置逻辑变量的内容。
例:
Logical_var=A>B
当A大于B时,Logical_var这个变量会被设定成真,否则为假。
(2)浮点数的逻辑判断
在用浮点数进行如a==b的运算时,因为有效计算位数的误差导致本应相等的变量判断错误,应使用如下误差判断逻辑运算:
real,parameter::e=0.0001 !自定义合理的误差范围
abs(a-b)<=e !代替了a==b的判断
(3)字符的逻辑判断
“a”与“b”比较时用二者的ASCⅡ码比较,ASCⅡ码规则:
按字母顺序从小到大,字母大写小于字母小写,字符串比较时按从前向后顺序依次比较。
IF(逻辑判断式1)THEN
...
ELSE IF(逻辑判断式2)THEN
...
ELSE IF(逻辑判断式3)THEN
...
ELSE !可省略
...
END IF
PS:当用IF要判断的内容只有一行时,可以省略THEN和END IF,如
IF(speed>100.0) write(*,*) “slow down”
select case(变量)
case(数值1)
...
case(数值2)
...
case(数值3)
...
case default !变量不等于任何数值时执行,此段可省略
...
end select
使用规则:
case(1) !变量=1时执行
case(1:5) !1<=变量<=5时执行
case(1:) !1<=变量时执行
case(:5) !变量<=5时执行
case(1,3,5) !变量=1或3或5时执行
PS:使用select case只能对整数、字符和逻辑变量。每个case中的数值必须是固定常量,不能使用变量。
GOTO 100 !接行代码100的指令
100 write(*,*)”Hello”
使用GOTO可提供任意跳跃到所赋值行代码的能力。
执行时遇到pause会暂停,用户按下Enter键继续。
执行遇到continue继续向下执行。(与C中不同,无实际作用)
执行遇到stop结束程序执行。(结束整个程序)
integer counter !声明(计数器)
integer,parameter::lines=10 !声明并赋值(计数器终止数值)
do counter=1,lines,2 !循环从1开始,在counter<=lines时重复循环,每次增量为2(最后的2省略时内定值为1)
end do
PS:do循环中计数器的初值、终止值和增量都可以使用常量或是变量,注意上述程序跳出循环时counter=11。Fortran77中使用行代码号结束循环。
循环不用累加器的增、减来决定,改为由逻辑运算决定。
do while(逻辑运算) !任意常量或变量
end do
括号内可直接写成(.true.)或(.false.)逻辑运算符
(1)CYCLE(C中的continue)
跳过cycle后的所有代码,直接进入下一次循环。
(2)EXIT(C中的break)
直接跳出循环
(3)循环署名
为循环命名,防止多层嵌套混淆。
outter: do i=1,3 !循环取名为outter
inner: do j=1,3 !循环取名为inner
write(*, "('(',i2,',',i2,')')" ) i,j !格式化输出两个字符段长输出i, j,并输出(i,j)的格式。
end do inner !结束inner循环
end do outter !结束outter循环
(1)一维数组
声明:
Datatype name(size) !Datatype指数组类型
除(integer,real,complex,logical)四种基本类型外,也可使用type自定义的类型。name为数组的名字,size是数组的大小。或
Datatype::name(size)
对于自定义类型
Type::person
real::height,weight
End type !完成类型定义
Type(person)::a(10) !用person类型声明数组
PS:声明时只能使用整型、常数来赋值数组的大小,常数包括直接填入数字或是使用parameter声明的常数。
读写数组:
do i=1, students
read(*,*) student(i) !数组输入
end do
do while(.true.) !将一直循环下去
write(*,*) student(i) !数组输出
end do
使用type自定义类型数组规则:
a(2)%height=180.0
(2)二维数组
integer a(3,3) !声明时要给出两个坐标索引值
a(1,1)=3 !赋值要求同上
声明与读写规则同一维数组。
(3)多维数组
integer a(D1,D2,...,Dn) !n维数组
a(I1,I2,...,In) !使用
Fortran最高可使用7维数组。
(4)其他声明方式
integer a(0:5) !坐标使用范围,这个数组定义了a(0)到a(5)共六个元素
事实上索引坐标可以改成任意值,如a(-2:2)。
(1)赋初值
使用DATA赋值,如:
integer a(5)
data a /1,2,3,4,5/
或使用“隐含式”循环
integer a(5)
integer I
data(a(I),I=2,4,2)/2,4/ !这里加入了一个隐含式循环,I从2开始到4结束间隔2,因此赋值结果为a(2)=2,a(4)=4,其中括号内最后一个2省略时默认间隔值为1。可见使用data赋值可以只对部分元素赋值。
输出时的隐含式循环:
write(*,*)(a(i),I=2,4) !将显示a(2),a(3),a(4)的值。
隐含式的嵌套用于二维数组赋值:
nteger a(2,2)
integer I,J
data((a(I,J),I=1,2),J=1,2)/1,2,3,4/ !括号内的循环将先执行,按照a(1,1),
a(2,1),a(1,2),a(2,2)的顺序依次赋值。
integer a(2,2)
data a/1,2,3,4/ !与上述结果相同
省略data,声明与赋值同时操作的情况,注意只能对所有元素都赋赋值,不能再部分赋值,如:
integer::a(5)=(/1,2,3,4,5/) !注意/和(间不能有空格。
或使用隐含式循环
integer::a(5)=(/1,(2,I=2,4),5/) !2到4将都赋2,所有元素都要赋值。
想要赋不同值时如下例
integer::a(5)=((I,I=1,5)) !从a(1)到a(5)分别赋1到5
(2)对整个数组的操作
设a,b,c都是同样维数同样大小的数组,大小和维数任意。
a=5 !a各元素全部充为5
a=(/1,2,3/) !赋值操作,右侧元素个数应等于a的大小
a=b !b各元素对应赋给a
a=b+c !a各元素等于b与c中各元素对应相加,二维时相当于
a(i,j)=b(i,j)+c(i,j)
a=b-c !a各元素等于b与c中各元素对应相减,二维时相当于
a(i,j)=b(i,j)-c(i,j)
a=b*c !a各元素等于b与c中各元素对应相乘,二维时相当于
a(i,j)=b(i,j)*c(i,j),注意相当于Matlab矩阵点乘而非矩阵乘法
a=b/c !a各元素等于b与c中各元素对应相除,二维时相当于
a(i,j)=b(i,j)/c(i,j)
a=sin(b) !a各元素为b各元素的sin值,注意b必须是浮点型
a=b>c !b与c是相同类型的数值变量,比较后将逻辑判断的真假对应位置存入a中,a是一个逻辑类型矩阵
(3)对部分数组的操作(类似于Matlab)
a(3:5)=5 !仅对3到5赋值
a(3:)=5 !对3及以后的所有元素赋值
a(3:5)=(/3,4,5/) !对3到5的元素指定赋不同值
a(1:3)=a(4:6) !对1到3赋4到6的值
a(1:5:2)=3 !隐含式循环,从1到5,增值为2,也就是对1,3,5赋值3
a(1:10)=a(10:1:-1) !实现数组值翻转
a(:)=b(:,2) !将a的所有值赋给b第2列的所有行
a(:,:)=b(:,:,1) !将a的所有值赋给b在1这一面上的所有元素
PS:使用部分操作时注意,等式左右元素个数一致,多个隐含式循环嵌套使用时将低维的看成高维的内层循环,如下例:
integer::a(2,2),b(2,2)
b=a(2:1:-1,2:1:-1) !b没特别赋值时默认为b(1:2:1,1:2:1)
括号内第一项相对第二项是低维(内层循环),因此赋值结果为:
b(1,1)=a(2,2)
b(2,1)=a(1,2)
b(1,2)=a(2,1)
b(2,2)=a(1,1)
实现了实数矩阵沿主、副对角线的连续两次翻转。
(4)WHERE
WHERE可以经过对数组数值的逻辑判断从而数组中的一部分进行操作,如:
where(a<3) !将找出a中数值小于3的元素
b=a !注意操作的前提是a、b大小相同
!赋值时元素位置一一对应
end where
使用WHERE做多重判断,如:
where(a<2)
b=1
elsewhere(a>5)
b=2
elsewhere !不跟逻辑运算时表示剩下的部分
b=3
end where
WHERE也可以嵌套。
(5)FORALL
一种强大的隐含式循环调用数组操作,以一维数组为例:
forall(i=1:5)
a(i)=i
end forall
对于二维数组的操作:
forall(I=1:5,J=1:5,a(I,J)<10) !只处理数组a中小于10的元素,第三项逻辑判断
a(I,J)=1
end forall
forall(I=1:5,J=1:5,I=J) !只处理数组a中I=J的元素,第三项逻辑判断
a(I,J)=1
end forall
还可以同时限制好几个条件
forall(I=1:5,J=1:5,((I=J).and.a(I,J)>0))
a(I,J)=1
end forall
PS:forall模块中只有一行时可以省略end forall,整体写成一行的形式。注意WHERE中不可以嵌套FORALL,但FORALL中可以嵌套WHERE(FORALL更强大且包含了WHERE的功能)。
由于数组使用内存时,从低维度到高维度按序排列,内存相邻。设计程序时应使维度越低的使用越内层的循环。
这也是为什么二维数组integer::a(2,2)=(/1,2,3,4/),会按a(1,1)a(1,2)a(2,1)a(2,2)的顺序赋值。
ALLOCATE、DEALLOCATE和ALLOCATED的使用
声明:
integer,allocatable::a(:) !声明一个可变大小的一维数组
integer,allocatable::a(:,:) !声明一个可变大小的二维数组
integer,allocatable::a(:,:,:) !声明一个可变大小的三维数组
分配内存:
allocate(a(i)) !动态分配内存,i为已声明并赋值的整型变量
allocate(a(i,j)) !二维情况
allocate(a(i,j,k)) !三维情况
也可特别指定数组索引坐标的起始及终止范围,如:
allocate(a(-5:5))
allocate(a(-5:5,-3:3))
PS:在动态分配内存并使用完毕后要及时释放内存,使用deallocate(a)释放数组a的内存,使用allocated(a)返回一个逻辑值,用于检查是否分配成功。
(1)命名与调用
主程序调用子程序使用call name(),子程序命名使用subroutine name(),子程序可以调用子程序(其它子程序或自己)。子程序的最后一个命令一般是return,但return可以出现在子程序的任何位置用以提前结束调用,返回主程序。
使用示例:
program main
...
call sub1() !调用子程序sub1
...
call sub2() !调用子程序sub2
...
end program main
subroutine sub1() !第一个子程序命名
...
return
end subroutine sub1
subroutine sub2() !第二个子程序命名
...
return
end subroutine sub2
PS:
①子程序独立的拥有属于自己的变量声明,主程序与其他子程序之间声明的变量无论如何命名都不相干。
②子程序独立的拥有自己的“行代码”,主程序与子程序各自定义自己的行代码。
(2)传递参数(传址调用)
使用示例:
call add(a,b,c)
...
subroutine add(first,second,third)
三个变量来接收传递进来的参数,名字完全自定义,与子程序中变量名无关
implicit none
integer::first,second,third
接收参数的变量需要再次声明了类型,初值就等于传入的参数值,注意变量类型要与主程序中完全一致
write(*,*)first+second
third=first+second
return
end subroutine add
PS:由于fortran的传址调用,子程序中接收的参数与主程序中输入的参数将使用相同的内存地址来记录数据,因此重新设定的参数值将被返回主程序。注意输入的参数也可以是数组,这就一次实现了多数据输入输出。
(1)基本使用
自定义函数的运行和子程序相似,但在调用自定义函数前需要先声明,自定义函数执行后将返回一个数值。使用external声明函数。
使用示例:
program main
implicit none
real::a=1
real::b=1
real,external::add !声明add是一个函数而不是变量
write(*,*)add(a,b) !调用函数add不必使用call指令
stop
end
function add(a,b)
implicit none
real::a,b
real::add
!add与函数名称一样,这里不是用来声明变量,是声明函数返回的数值类型
add=a+b
return
end
PS:
①使用real function add(a,b)在函数的最开头可以省略real::add。
②传递给函数的参数,只调用数值,不会改变原数据,唯一可改变的是函数的因变量。
(2)传递参数
在函数中用于接收参数的数组可以不声明其大小,原因是传入的数组在主程序中已声明大小分配了内存空间,函数中使用示例:
subroutine sub1(num)
implicit none
integer::num(*) !不赋值数组大小
...
return
end
或
subroutine sub2(num,size)
implicit none
integer::size
integer::num(size) !输入数组的大小可以用变量来赋值
...
return
end
或
subroutine sub3(num)
implicit none
integer::num(-2:2) !(数组大小为5)可以重新定义数组坐标范围
...
return
end
综上,函数中只要注意使用数组参数时,不要超过其实际范围就可以了。注意在传递字符串变量时,也可以不特别赋值他的长度,原因与数组相同character(len=*)string
。但在多维数组传递时,只有最后一维(最高维)可以不赋值大小,示例:
subroutine sub(a,dim)
...
integer::a(dim1,*) !最后一维可以不赋值(不建议)
integer::i
write(*,*)(a(:,i),i=1,2) !输出时必须制定输出的是哪几位
...
end
(3)函数中其他功能
①在声明函数、fortran中的库函数后,可以将函数作为传递量传入子程序或函数,在子程序中需要再次声明函数,之后可以任意使用。
②使用integer,intent(in)::a指定a变量是只读,integer,intent(in)::b指定b在子程序中应重新设置数值。
③使用interface在主程序开头,定义函数的使用接口,声明函数的传入、返回类型或定义函数的返回值是一个数组。
使用函数时自己调用自己,注意在函数调用自己时每次声明的局部变量都使用不同的内存地址,下面给出计算阶乘使用递归的完整示例:
program ex0828
implicit none
integer :: n
integer, external :: fact
write(*,*) 'N='
read(*,*) n
write(*, "(I2,'! = ',I8)" ) n, fact(n) !调用递归函数
stop
end
recursive integer function fact(n) result(ans)
!在函数最开头使用recursive才可以递归调用,result可以用来在程序代码中使用另外一个名字来设置函数传回值,这里用ans代替了face的值。
implicit none
integer , intent(in) :: n
if ( n < 0 ) then !不合理的输入
ans = -1 !随便设定一个返回值,注意已使用ans代替face
return !n不合理, 直接return
else if ( n <= 1 ) then
ans = 1
return !递归结束, return
end if
ans = n * fact(n-1) !执行到这里代表n>1循环使用n*(n-1)来计算n!
return
end
将变量写在module里,使用use就可以在各个主程序、子程序中反复调用这些变量,不需要重复声明。module中的变量如果不是全局变量,在被各程序段use时只会是局部变量,可以在module声明中使用save延长变量生存周期,使用示例:
module global !注意要在程序最开始使用module
implicit none
integer a,b
common a,b
end module
program ex0834
use global !使用module模块,注意这一条应放在implicit none前
implicit none
a=1
b=2
call sub()
end program
subroutine sub()
use global !子程序中使用module声明的变量
implicit none
write(*,*) a,b
return
end subroutine
读取文件的操作分为顺序读取和直接读取的方法,文件的输入输出格式可以是文本文件或二进制文件,文件相关指令十分丰富,具体查阅参考手册。
打开文件:
open(unit=10,file=’hello.txt’)
!打开文件,unit=给文件制定一个代码, 后面读写操作都使用代码10表示该文件,file=后跟文件名
注意上述file后只需填文件名而非路径,需要加上是何种文件类型(.txt)或(.bin),并且要放在与源程序同一路径下
write(10,*)”hello” !把hello写入文件
read(10,*)string !把文件中的字符读入string变量中
rewind(10) !由于默认顺序读写,指令rewind将文件10 的读写位置移回最前面
使用inquire指令,通过返回逻辑变量值,检查文件是否存在,先检查再打开比较稳妥,使用示例:
inquire(file=filename,exist=alive)
!file接文件名,alive将返回一个逻辑变量值,有返回真无返回假。
使用close在文件读写结束后及时关闭文件,使用示例:
close(unit=10,status=’...’)
!将文件10关闭,当status=’keep’时,文件关闭后将保留这个文件,当status=’delete’时,将消除这个文件。
下面的程序实现了查找文件是否存在,如果存在就循环读取并输出文件中的内容。
program main
implicit none
character(len=79) :: filename
character(len=79) :: buffer
integer, parameter :: fileid = 10
integer :: status = 0
logical alive
!以上声明部分结束
write(*,*) "Filename:"
read (*,"(A79)") filename !这里读取了输入的文件名
inquire(file=filename, exist=alive) !使用inquire判断文件是否存在
if (alive) then
open(unit=fileid, file=filename, access="sequential", status="old")
!unit跟文件号10(这里fileid已被设置成常量10),file跟文件名。access后两种写法,跟sequential顺序读取,跟direct直接读取,省略默认顺序读取。status跟old代表是已存在的文件,跟new表示本不存在第一次打开,跟replace若文件不存在会创建,文件已存在会覆盖重新创建。
do while(.true.)
read(unit=fileid, fmt="(A79)", iostat=status) buffer
!打开文件10,fmt制定读入格式,iostat会设置一个整数值给status这个变量,用于说明文件打开的状态,当status>0表示读取操作错误,status=0读取正常,status<0表示文件终了。read命令每读一次,自动把读取位置移动到下一行。
if (status/=0) exit !没有数据就跳出循环
write(*,"(A79)") buffer
end do
else
write(*,*) TRIM(filename)," doesn't exist."
end if
stop
end
其他注意事项:
&代表与下、上一行衔接。
/代表在输出时换行。
文本文件中一个中文字符需要两个character来保存。
P53(格式化输入输出)+P233(文件操作指令)参考手册*