write(*,*)a
endif
pause
end
注意事项:
if 后面一定要加 then,结束不是end,是endifif可以嵌套多个使用
2.select case
类似于switch case
骨架
select case(运算表达式,得到一个数)
case(控制表达式1)
!代码块
case(控制表达式2)
!代码块
.....可以有很多case
endselect
示例代码
program main
!a+b=7就走case(7),=8就走case(8)
integer ::a=3,b=5
select case(a+b)
case(7)
write(*,*)7
case(8)
write(*,*)8
endselect
pause
end
4.循环结构
重中之重
1.DO循环
类似于其它语言的for循环
DO循环骨架
DO 循环变量 = E1,E2
代码块
ENDDO
///////////////////////////////////////////////////////////////////////////
do i=1,10
write(*,*) i
enddo
!实际上就是i初始为1,每执行一次猜码快i+1,直到i+1之后>10,退出循环的过程
///////////////////////////////////////////////////////////////////////////
!循环变量的增量是可以变的
do i=1,10,4
!就是i从1开始,每次加4,执行出来i分别等于1,5,9
!注意循环变量退出循环后依然存在
示例代码
program main
real ::plus=1
real::r=1
do i=1,100
!循环变量叫i,i每次循环从1到100依次+1
plus=((2*i)*(2.0*i))/((2*i-1)*(2*i+1))
write(*,*)plus
r=r*plus
write(*,*)n
!do中的代码块,每次都执行这些,然后结束后i+1,不断循环,最后i+1>100时候退出循环
end do
write(*,"(f11.9)")r
pause
end
2.DO-WHILE循环
满足逻辑表达才执行,否则就退出循环
DO-WHILE框架
do while(逻辑表达式)
!代码块
enddo
!满足逻辑表达才执行,否则就退出循环
-示例代码
program main
integer ::a=1
do while(a<10)
a=a+1
write(*,*)a
enddo
!输出1,2,3,4,5,6,7,8,9,10,最后a=10,不满足a<10 就不走循环接下去了
pause
end
3.退出循环语句
EXIT 用来退出当前的循环体,相当于breakCYCLE 用来退出当前这轮循环,进入下一轮循环,相当于continue
4.如何选择两种循环
do循环适合已知循环次数的循环,可以自动增量,不用谢逻辑表达式do while循环适合不知道循环次数的循环
5. forall循环
forall循环类似隐式循环,但是功能更强大,适合数组的遍历在forall 和 endforall中包裹起来代码块在forall中不能进行write操作,
program main
integer ::a(2,2) = (/ 1,2,3,4 /)
forall(i=1:2, j=1:2)
a(i,j) = 0
endforall
write(*,*) a
pause
end
筛选操作
在forall循环中可以对操作到的元素进行筛选,既可以是下标的筛选,也可以是元素的筛选在forall(…, mask), …代表的是i,j,k等等用来遍历下标的变量,mask就是填的逻辑判断条件这个例子就是对下标i==j的条件的时候让这个下标对应值改为0
program main
integer ::a(2,3) = (/ 1,2,3,4,5,6 /)
forall(i=1:2, j=1:3,i==j)
a(i, j) = 0
endforall
write(*,*) a
pause
end
这个例子就是对元素进行判断,满足的才执行
! forall用法
program main
integer ::a(2,3) = (/ 1,2,3,4,5,6 /)
forall(i=1:2, j=1:3,a(i,j)>3)
a(i, j) = 0
endforall
write(*,*) a
pause
end
5.输入语句
read(,)
表控输入语句
read(*,*) a,b,c
read *,a 两种都可以
!表示从键盘输入并且赋值给变量
program main
integer a,b,c,d
read (*,*)a,b,c,d
write(*,*)a,b,c,d
pause
end
! 注意:1. 输入的时候用, 或者空格分割
2. 输入的数量不小于变量数量,多余的不起作用
3. 输入的类型匹配,输入实型给整型变量会报错,输入实型给整型变量会自动转换
read(,)中第一个表示从键盘输入,第二个指输入格式,默认是*不用改
6.输出语句
print *,[输出表]
print只能最普通地输出,不能格式化输出,*代表从默认设备(显示器)输出
write()
write(* , * ) 第一个*表示输出设备(显示器or文件中),第二个星表示输出格式格式化输出format
integer ::a=100
write(*,200)a !200表示名叫200的format格式
200 format(I4) !format里填的是各种类型的输出格式,一一对应
format中的格式(常用的)
整型:Iw w表示共显示多少个字符,超过了就左边用空格代替,不足就显示**,不显示数据
integer ::a=345
write(*,200)a
200 format(i2) =>显示**
200 format(i5) =>显示” 345“左边有俩空格
//////////////////////////////////////////////////////////////////////
实型:Fw.d w表示占位多少个字符宽度,d表示小数部分占几个字符宽度
real ::b=24.687
write(*,200)b
200 format(f10.5)
=>显示' 24.68700'多的左边补空格,小数位数多的用0补
=> 小数位数比原来值小就四舍五入掉多的位数,总共位数小于整数部分,显示*
=> 总之只要使得整数部分不能完整表达,直接*
////////////////////////////////////////////////////////////////////
字符串:Aw w表示显示位数
character(5) ::c='hello'
write(*,200)c
200 format(A3)
=>多了左边补空格,少了直接截去
////////////////////////////////////////////////////////////////////
空格: nX n表示输出位置向右移动几格
character(5) ::c='hello'
write(*,200)c
200 format(6x,A3)
=>向右移动6个位置之后再输出
////////////////////////////////////////////////////////////////////
带上字符串的输出
character(5) ::c='hello'
write(*,200)c
200 format('a=',A3)
=> a=hel
在write中直接写格式 在第二个星号的地方用双引号括起format中写的东西
real ::b=24.687
character(5) ::c='hello'
write(*,"(f6.3,A3)")b,c
write(*,"('b=',f6.3)")b
. 写程序中容易犯的错误
声明变量且赋值的时候不加 ::忘记写endif enddo 写结构的时候先把选择/循环结构的结构写好,摆好缩进在写代码块逻辑运算忘记加. 每个逻辑运算两端都有 ex: 4>3 .and. 5>4循环变量的表达式不要写成i=1:4 习惯了别的语言有时候容易错二进制的读写加了*,写成open(2,),注意二进制读写只用写open(2),没有数组大小不能使用变量,但是可以用parameter后的变量待~
五. 数组
重中之重
1.什么是数组
是一组具有同一类型的变量组成占据一段连续的储存单元数组中包含每个数据称为数组元素,通过下标访问每个元素
2.数组的定义
定义格式:
定义一维数组:
datatype name(size)
!datatype可以是任意类型
integer array1(5) =>声明了一个长度为5,类型为实型的数组array1
integer a(6),b(8) =>同时定义两个数组
定义多维数组:
integer array2(3,5)
=>size是3*5,就是一个三行五列的数组
特殊定义:
integer array3(6:10)=>定义了一个一维数组,但是下标是从6到10
3. 数组引用
下标引用 ——用(下标)来引用
一维:
integer a(5) =>定义了一个一维有五个元素的数组
a(5)=6 =>6赋值给数组a的第五个元素
二维:
integer b(6,8) =>定义了一个6*8的二维数组
b(2,1)=6 => 6赋值给b数组的第二行第一列元素
切片引用——用:可以表示一段数组
a(5:8)
=>表示a中5到8的数组切片
b(2:4,6:8)
=>表示二维数组中的切片
c(1:10:2)
=>用三元表达式(起始下标,终止下标,步长)取出不连续的切片,取出c(1),c(3)...c(9)
4.数组的储存结构
二维数组定义时先行后列 real a(2,4)=>定义了两行四列的数组逻辑结构:就是几行几列和平时一样储存结构:按列顺序存储
5.数组的输入输出
使用do循环输入输出
!通过二重循环遍历二维数组进行读写
integer a(10,4)
do i=1,10
do j=1,4
read(*,*)a(i,j)
enddo
enddo
do i=1,10
do j=1,4
write(*,*)a(i,j)
enddo
enddo
使用隐式循环读写 隐式循环:列循环,do循环是行循环,配合使用可以快速读取行列,在读取气象文件时很好用
一维数组,只有一行,用隐式循环就可以解决
program main
integer a(5)
read(*,*)(a(i),i=1,5)
write(*,*)(a(i),i=1,5)
pause
end
二维数组,每一行用do循环包裹,内部用隐式循环
program main
integer a(5,3)
do i=1,5
read(*,*)(a(i,j),j=1,3)
write(*,*)(a(i,j),j=1,3)
enddo
pause
end
6.给数组赋初始值
使用data赋值
real a(5),b(4,4)
data a /1,2,3,4,5/
!可以用*表示重复的内容
data a /5*2/ =>把五个2赋给了数组中元素
data b /1,5,9,13,2,6,10,14,3,7,11,15,4,8,12,16/ =>注意多维数组的顺序为列顺序
上述b的实际矩阵如下
用隐式循环进行循环,循环的下标从后面的/数据/中取
program main
integer a(5)
data (a(i),i=2,4)/2,5,6/
write(*,*) a
pause
end
声明时直接赋值
integer::a(5)=(/1,2,3,4,5/)
!如果给每一个都赋一样的值,那可以简化
integer ::a(5)=5
使用data赋值的时候,可以存在空值,会有默认值进行填充,但是在声明时直接赋值是不能有空值的
7. 数组运算
相同维数数组之间的加减乘除是各自相同位置的元素的加减乘除,返回一个一样大的数组,这里的除法和乘法不是线代里的矩阵乘法除法数组倒序操作 a(1:10)=a(10:1:-1)数组切片的修改 a(3:5)=(/2,3,4/)
8.数组的常用内置函数
all(a>5) =>判断a中元素是否全部大于5,返回值为布尔值
all(a>b) =>判断a中元素是不是每一个都比b中元素大,返回值为布尔值
/////////////////////////////////////////////////////////////
maxloc(a) =>返回最大值的坐标
maxcal(a) =>返回最大值
minloc(a)
minval(a)
!如果是一维数组就返回只有一个元素的数组,用只有一个元素的数组接收,多维数组就返回对应坐标的一个数组
ex:program main
integer a(3,2)
data a /1,2,3,4,5,6/
write(*,*) maxloc(a)
pause
end
!返回的是(3,2)
ex: program main
integer :: a(5)=(/1,2,3,4,5/)
integer idex(1)
idex=maxloc(a)
write(*,*)idex(1)
pause
end
对于一维数组,返回值也是一个数组,只有一个元素,但不能用整型去接收
//////////////////////////////////////////////////////////////////////
sum(a) =>对整个a求和
sum(a(1:)) =>对第一行
sum(a(:3)) =>对第三列
9. where
使用where和endwhere包裹的部分可以对数组进行筛选,并将筛选结果可以赋值给另一个相同维度的数组,会将对应满足条件保存到对应下标下,其他地方由默认值填充和if的多分支语句一样,where也可以有多分支在where(判断条件)就可以进行筛选
! 多分支where
program main
integer ::a(5) = (/ 1,2,3,4,5 /)
integer ::b(5)
where(a>3)
!write(*,*)a
b = a
elsewhere(a==2)
b = 10
elsewhere
b = -1
endwhere
write(*,*)a
write(*,*)b
pause
end
10. 可变长数组
有时候在一开始写代码的时候并不知道实际需要的数组有多长,定义少了不够用,定义多了又浪费可以用allocatable关键字定义一个不知道长的数组知道长度应该为多少后,用**allocate()**来配置内存空间,才真正将数组造出来
!可变长数组
program main
integer stuNum
! 声明可变大小一维数组
integer, allocatable :: a(:)
write(*,*)"有多少个学生"
read(*,*)stuNum
! stuNUm已经有值之后,来配置内存空间,达到后定义数组长度的效果
allocate(a(stuNum))
forall(i=1:stuNUm)
a(i)=i
endforall
write(*,*)a
pause
end
11. 改变当前数组的长度
通过**deallocate(数组)**可以释放当前数组所占用的内存释放之后再allocate就可以分配新的长度了
deallocate(a)
allocate(a(20))
六. 文件
重点,考试必考 主要是两种格式文件的操作:txt文本文件(有格式文件),二进制文件(无格式文件)
1.读文件
open关键字 看着很麻烦,但其实很多都有默认值,可以缺省
Unit:文件编号,在程序内部用来指定操作的是哪个文件,unit可以不写”unit“,直接写序号
open(20,file='E:\Grads\h4JJ-ave-pre.txt')
!这个文件就是编号20文件,之后使用的时候都需要加上这个
write(20,*)'hello,world'
!往20号文件中写
file:文件的绝对路径 绝对路径就是路径加完整的文件名,这里就是’E:\Grads\h4JJ-ave-pre.txt‘ form:表示文件是二进制的还是文本文件,有两个选项,binary和 formatted,但是默认是formatted,只有在读二进制文件时才需要加上form=‘binary’recl: 直接读取文件的时候需要填写recl项,指定文件中记录的长度
open(20,file='E:\Grads\h3\hgt-jj-79-10.grd',form='binary')
!注意:form=后面的binary或者formatte要用的单引号括起来
2.要记住的两个读写方式
读写操作分为三种:
文本文件顺序读写二进制文件顺序读写二进制文件直接读写 三个中前两个用的最多,必须记住读写操作的形式
1.文本文件顺序读写
open(unit= ,file=' ')
read(unit= ,*)
write(unit= ,*)
close(unit= )
2.二进制文件顺序读取
open(unit= ,file='',form='binary')
=>二进制文件的form就不是默认的formatted,需要手动写成binary
read(unit= )
write(unit= )
=> 注意!注意!注意! 二进制文件不用加* 二进制文件不用加* 二进制文件不用加*
close(unit= )
3.二进制文件直接读取
open(unit=, file='',form='binary',access='direct',recl=4) =>recl指定了每一条记录的长度
read(unit= ,rec=第几条记录,一个整数)
write(unit= ,rec=第几条记录,一个整数)
close(unit= )
3.文件的读写
读写操作每次读一行,使用隐式循环配合可以将文件内容读出来放到数组中写操作也是每次对应一行,write(unit,*)a,b,c,d 这一行就有a,b,c,d四个数在写入时可以加上格式化输出让输出结果统一write(unit= ,fmt=“(F2.5)”)
program main
real,parameter::year=20
real data1(year,2)
open(1,file='E:\1989-2008.txt')
open(1,file='E:\1989-2008-2.txt')
do i=1,year
read(1,*)(data1(i,j),j=1,2)
end do
do i=1,year
write(2,*)2,5,6
enddo
close(1)
end
7.子程序
子程序可以理解成别的编程语言的方法或者叫函数,fortran中有两种子程序
subroutine:就叫子程序function:函数
1.什么时候需要子程序
当代码中出现某个重复功能或重复使用某一段代码的时候,可以使用子程序——把一些需要重复使用的代码封装起来,通过传参的方式得到结果好处在于:解耦合,提高代码的复用,减少多余的代码,是程序设计的重要思想需要把功能抽象出来,提供一个接口供调用的地方访问即可
2. 函数子程序function
函数的思想在各个编程语言中更加通用,我本人也更喜欢使用函数
函数骨架
!主程序
program main
external f1 =>声明f1是个函数
real f1 =>也要声明函数的返回值类型
f1() =>调用函数
end
!函数
real function f1(a,b) =>函数名为f1,返回值类型是个实型的函数,返回值的名字就叫f1,需要a,b作为参数
!函数体
real a,b =>如果主函数没有声明传来的变量类型,比如直接传了一个fun(3),需要声明传来参数的类型,如果传来的类型明确则不用在此声明
f1=a+b =>最后要给返回值赋值
return =>标志函数结束了
end
///////////////////////////////////////////////////////////
实例代码
program main
external add
real add
write(*,*)add(2,3) =>返回5.0
pause
end
real function add(a,b)
integer a,b
add=a+b
return
end
3. 子例行程序subroutine
骨架
program main
external s1 =>声明s1是个子程序
call s1(4,6) =>要用call 关键字调用
end
subroutine s1(a,b)
integer a,b =>如果主函数没有声明传来的变量类型,比如直接传了一个fun(3),需要声明传来参数的类型,如果传来的类型明确则不用在此声明
!子函数体
end
!示例代码
program main
integer ::a=2,b=3
external add2
call add2(a,b)
pause
end
subroutine add2(a,b)
integer a,b
write(*,*)a+b
a=a+3
b=b+4
end
4. 传参的注意点
1. fortran是地址传参
4.fortran中传参是地址传参 向传参传过去的是地址而不是值,传过去的参数在函数/子程序中修改了,主函数中的变量值也会随之改变,必须注意这一点!
program main
external fun
integer ::a,x=17,b,fun
a=fun(x)/fun(x)
b=fun(x)-fun(x)
write(*,*)a,b
pause
end
integer function fun(x)
integer x
x=x/2
=>传来的x在此处的改动会改变主函数中的x的值,第一次进来时候x=17,第一次出去时x=8,当主程序a在计算分母的时候
再次调用的时候x=8,之后以此类推
fun=x*x
end
输出:4 3
当传过去的时候是一个表达式的时候,子程序中修改的是表达式整体的量,而不是表达式中各个变量的值
program main
real::a=3.0,b=5.0,c=4.0
call subt(b-a,c) =>1. 传过去的第一个参数是b-a这个整体
write(*,*)b-a,c => 3. 这里的b-a是用原函数中的b-a重新计算,5-2=3.0
pause
end
subroutine subt(x,y)
real x,y
x=x+2 => 2. 修改的是b-a这个整体,原函数的b,a并没有被修改
y=y+1
end
2. 数组参数
如果传递的参数是一个数组,传过去的也是数组的起始下标因此接收的时候就会有如下情况:
从数组的某个位置将数组传过去,接收的也从这个位置出发整个数组传过去,传过去的就是第一个的下标,在接收的时候要指定接收多少长度
! 数组传参
program main
integer :: a(5) = (/ 1,2,3,4,5 /)
external sub1, sub2
call sub1(a(2))
call sub2(a)
pause
end
subroutine sub1(a)
integer :: a(3)
write(*,*)a
end
subroutine sub2(a)
integer a(5)
write(*,*)a
end
八. 一个综合例子
program main
external rel =>声明rel函数
parameter(nx=144,ny=73,nz=17,nt=32) =>声明了宏变量经纬度,层数,时间
real data0(year,2),data1_temp(year),data1(year)
real data2(nx,ny,nz,nt),res(nx,ny,nz,1)
!两种文件的打开
open(1,file='E:\Grads\h3\ISM-onset-1979-2010.txt')
open(2,file='E:\Grads\h3\hgt-jj-79-10.grd',form='binary')
read(1,*) =>第一行是表头,需要跳过
do i=1,nt
read(1,*) (data0(i,j),j=1,2) => do和隐式循环配合读取数据
read(2)(((data2(i,j,k,n),i=1,nx),j=1,ny),k=1,nz)
enddo
data1=data0(:,2) =>进行切片操作
do i=1,nx
do j=1,ny
do k=1,nz
res(i,j,k,1)=rel(data1,data2(i,j,k,:)) =>循环调用相关系数函数
enddo
enddo
enddo
open(3,file='E:\Grads\h3\cor.ISM.JJ-hgt.79-10.grd',form='binary')
write(3)res
pause
end
real function rel(x,y)
!计算相关系数函数,传入两个相同维数的数组
integer,parameter::year=32
real x(year),y(year),x_aver,y_aver,m1(year),m2(year),m3(year)
!x向量,y向量,x平均数,y平均数,公式的分子,公式的两个分母
x_aver=sum(x)/year
y_aver=sum(y)/year
do i=1,year
m1(i)=(x(i)-x_aver)*(y(i)-y_aver)
m2(i)=(x(i)-x_aver)**2
m3(i)=(y(i)-y_aver)**2
end do
rel=sum(m1)/sqrt(sum(m2)*sum(m3))
return
end