fortran语言教程第7章Word文档格式.docx
《fortran语言教程第7章Word文档格式.docx》由会员分享,可在线阅读,更多相关《fortran语言教程第7章Word文档格式.docx(43页珍藏版)》请在冰豆网上搜索。
5CONTINUE程序段2
S=FA(M)/(FA(N)*FA(M-N))
源程序(I)中,加粗的3段完全相同,我们可以将其提炼出来,独立写成一个程序单位,构成一个子程序单元,写成源程序(II)的形式,这样的程序就形成了主程序和子程序的多部分、多模块的格局。
一般而言,模块是指可以独立存盘,并且可以分别编译的源程序文件,是一种构成FORTRAN程序的独立成分。
即是说子程序与主程序可以分别编辑存盘、编译,此时即形成所谓的多模块程序。
程序模块化的优点是:
(1)体现了算法和功能上的模块化,符合人们解决复杂问题的一般思路,也为结构化程序设计提供了有力支持。
(2)使复杂的软件开发工作可以化整为零,便于多人分工协作,减少开发人员之间的相互干扰与重复劳动,从而有利于缩短开发周期,节省开发费用,提高软件质量。
(3)可以设计一块,调试一块,设计完成,调试也随之完成,能够方便有效地防止数据之间的相互干扰,增加整个系统的稳定性与可靠性。
(4)结构灵活便于组装,条理清晰便于理解与维护。
其优越性随着程序规模的扩大而愈加明显。
7.2FORTRAN语言的三种子程序形式
7.2.1函数子程序
1)函数子程序定义的一般形式:
类型说明符FUNCTION函数名[([形参表])]
……
[函数名=表达式]
END
或者:
FUNCTION函数名[([形参表])]
类型说明符函数名
2)对函数子程序的说明
函数子程序名应该是标识符。
函数子程序名一方面在程序中惟一标识一个函数子程序,因此它不能与本程序单位以及引用它的程序单位中任何其他名字相同;
另一方面函数子程序名也用来返回计算的结果,因此它是有类型的。
函数子程序名的类型也就是函数子程序返回值的类型。
其类型说明可以采用上面的两种方式进行显式说明,否则它也将遵循隐含类型说明规则。
函数子程序的返回值由函数子程序名带出,因此在子程序单元中一般会给函数子程序名赋值。
但是在子程序单元中也可以不给函数子程序名赋值,当然也可以对函数子程序名进行多次赋值。
函数名后的一对括号中的形参表给出了函数子程序的所有形式参数,它们是函数子程序的自变量。
形式参数(简称形参或虚参)可以是变量名、数组名和子程序名等。
形参表中可以有零到多个形参,当没有形参时,括号可以省略;
当有多个形参时,形参之间用逗号分隔,并且在它所在的函数子程序中的命名必须是惟一的。
形参也是有类型的,所以应该在函数子程序中对形参进行说明,其说明方式与简单变量的说明相同,并且同样的遵循隐含类型说明规则。
由于可以将函数子程序单独放在一个文件中独立编译,以便于任何其他程序单位的引用,所以实际应用中将它与内部函数对应,称其为外部函数。
3)函数子程序的引用
只能通过表达式实现对函数子程序的(引)调用。
调用时采取实参(即实在参数,代表确定的、具体的值)代替形参的方式,这与内部函数、语句函数的引用方法相同。
引用函数子程序时,实参的类型、个数必须与形参的一致。
当引用无形参的函数子程序时,函数子程序名后的一对括号不能省略。
函数子程序可以嵌套调用,即一个子程序可以调用另一个子程序,也可以递归调用,递归调用是指子程序直接或间接调用自己。
【例7.1】设计一个函数子程序GCD(M,N),求M,N的最大公约数,并调用函数子程序GCD(M,N)求75,35,215三个数的最大公约数。
programmain
integergcd!
说明函数子程序名是整型
print*,gcd(gcd(75,35),215)!
嵌套调用函数子程序求3个数的最大公约数
end
integerfunctiongcd(m,n)!
定义求2个数的最大公约数的函数子程序
k=min(m,n)
doi=1,k
if(mod(m,i)==0.and.mod(n,i)==0)gcd=i
enddo
【例7.2】输入X,计算Y。
其中sh是双曲正弦函数,已知:
READ(*,*)X
Y=SH(1+SH(X))/(SH(2+X)+SH(3*X))
WRITE(*,100)X,Y
100FORMAT(1X,'
X='
F6.2,'
Y='
F8.3)
END
FUNCTIONSH(T)
SH=(EXP(T)-EXP(-T))/2
4)函数子程序的特点
通过使用函数子程序,可以知道函数子程序特别适合计算类型的问题,对函数调用后期望得到一个结果数据的问题均可以方便地定义函数子程序。
7.2.2子例行程序
函数子程序可以很好的完成数值计算功能,但在实际应用中有很多的功能模块只要求执行某种操作而不需要带出返回值。
此时就可以采用FORTRAN提供的另一种模块化程序结构——子例行程序。
1)子例行程序定义的一般形式:
SUBROUTINE函数名[([形参表])]
……
子例行程序的定义与函数子程序的定义类似,要注意的是这二者的区别:
(1)子例行程序的名称不用来返回函数的处理结果,因此是没有类型的。
所以既不能定义子例行程序名的类型,也不能在子例行程序中给子例行程序名赋值;
(2)子例行程序必须以SUBROUTINE语句开头,以END语句结尾。
能用函数子程序实现的功能,都能用子例行程序来实现,反之亦然。
因此在程序设计时,要对具体的问题进行具体分析,以便确定选用哪种方式。
一般来说:
如果要得到一个函数值,并且该函数值在调用程序中还要参与运算,则使用函数子程序比较合适;
如果不需要返回值或需要返回多个值,则使用子例行程序比较合适。
但要注意的是由于不能给子例行程序名赋值,所以在将函数子程序转换为子例行程序时,应该增加一个变量用来带回在函数子程序中由函数名带出的子程序处理结果。
2)子例行程序的调用
子例行程序的调用也是采用实参代替形参的方式。
但与函数子程序的引用方法不同的是,子例行程序的调用需要一个专门的语句。
其基本形式:
CALL子程序名[([形参名])]
当定义的子例行程序没有参数时,调用时可以不带上括号。
这一点与函数子程序不同。
将【例7.1】改写成子例行子程序:
programmain
integergys1,gys2
callgcd(75,35,gys1)!
调用子例行程序得到2个数的最大公约数保存在gys1中
callgcd(gys1,215,gys2)!
调用子例行程序求3个数的最大公约数
print*,gys2
subroutinegcd(m,n,gys)
integergys
if(mod(m,i)==0.and.mod(n,i)==0)gys=i
将【例7.2】改写成子例行程序来实现。
READ(*,*)X
CALLSH(X,Y1)
CALLSH(1+Y1,Y2)
CALLSH(2*X,Y3)
CALLSH(3*X,Y4)
Y=Y2/(Y3+Y4)
WRITE(*,100)X,Y
SUBROUTINESH(T,Y)
Y=(EXP(T)-EXP(-T))/2
7.2.3数据块子程序
数据块子程序是非执行程序单元,因而在其中不能出现任何可执行语句,也不能被别的任何程序调用。
它是专门用来给有名公用区中的项目赋初值的子程序。
具体内容将在7.3.3介绍。
7.3程序间的数据传递
在调用子程序时,调用程序与被调用程序之间一般需要传递数据。
数据传递有实参与虚参之间的虚实结合、通过COMMON语句实现数据的共享等方式。
7.3.1实参和虚参之间的数据传递
虚实结合是常见的一种数据传递方式。
在子程序被调用之前,所有形参既没有具体的存储地址,也没有具体的值,它实质上是实参的位置标志符,只是用来在形式上表示子程序中的自变量个数、类型及其在子程序中的处理规则。
当子程序被调用时,实参就取代形参完成形参与实参的结合(简称为形实结合、虚实结合或参数传递),然后执行子程序。
参数传递的方式如下:
调用程序单元CALLSUB(A,M1,‘TEST’)
↓↓↓
定义的子程序SUBROUTINESUB(X,N,CH)
这种虚实结合与代数中函数的概念类似。
形参是在定义子程序时出现在子程序名后面括号中的标识符,它可以是变量名、数组名(但不能是数组元素名)、子程序名等;
而与其对应的实参则是在调用过程时传送给子程序的常量、变量名、数组名、数组元素、表达式、内部函数名、子程序名等。
FORTRAN语言中的虚实结合一般采用按地址方式进行,即调用程序中的实参与子程序中的形参在子程序运行过程中共用同一个存储地址。
特别值得注意的是:
在子程序中,是按照形参的数据类型及格式去“理解”和使用对应实参内存单元中的数据,因而虚实结合遵循的是实参与形参的参数个数相同、按照位置一一对应进行传递,对应位置上数据类型相符的原则。
1)变量作为形参
当形参是变量时,调用程序中对应的实参可以是同一类型的变量、常量、表达式或数组元素。
当实参是变量或数组元素,则虚实结合时形参与实参使用同一个存储单元,因此在子程序中修改形参的值,也就相当于改变了调用程序中实参的值。
有时也将这种参数传递方式称为双向传递。
若对应的实参是常量或表达式,则虚实结合就只能理解为数据由调用程序传入子程序的单向传递过程,这时无论在子程序中如何修改形参的值也不会改变对应的实参的值。
因此如果想要保证实参的值不变,可以把实参变量用一对圆括号扩起来,变为一个表达式;
反之如果想要实现数据的双向传递,就应该在调用子程序之前将常量或表达式的值赋给变量,而在实参表出现相应的变量名。
2)数组作为形参
不论数组的逻辑结构如何,在存储器中都被转换成一维结构来连续存放,因此作为形参或实参的数组之间都将按照该一维结构建立对应关系。
当形参是数组名时,调用程序中对应的实参必须是同一类型的数组名或数组元素名,下面分别加以讨论:
(1)实参为数组名
当实参是数组名时,实参数组与形参数组按照地址结合,实参数组与形参数组都从第一个元素开始逐一对应,但形参数组与实参数组的结构和长度可以不同,只要满足实参数组的长度大于或等于形参数组的长度即可。
例如,有如下的程序段:
INTEGERA(2,3)
CALLSUB1(A)
SUBROUTINESUB1(B)
INTEGERB(-1:
3)
当在主程序中执行调用语句时,实参数组即与形参数组按如下关系结合:
A(1,1)A(2,1)A(1,2)A(2,2)A(1,3)A(2,3)
B(-1)B(0)B
(1)B
(2)B(3)
其中,实参是一个二维数组,有6个整型元素,第一个元素为A(1,1),而形参是一个一维整型数组,其第一个元素为B(-1)。
由此可以看到:
用数组名作实参时,都从第一个元素开始进行地址匹配,后面的元素逐个对应使用相应的存储单元。
(2)实参为数组元素
与数组名作为实参类似,当实参是数组元素时,实参数组与形参数组也是按照地址来结合的。
此时形参数组的元素与作为实参的给定数组元素及其之后的数组元素进行逐个对应。
此时也不要求形参数组与实参数组的结构和长度相同,只要满足从作为实参的数组元素开始到数组结尾的实参的个数大于或等于形参数组所定义出的形参的个数即可。
例如,有如下的语句:
CALLSUB3(A(2,1))
SUBROUTINESUB3(B)
当在主程序中执行调用语句时,实参即与形参按如下关系结合:
在此例中,实参是二维整型数组中的一个元素,而形参是一个一维整型数组,则虚实结合就从形参的第一个元素B(-1)与作为实参的数组元素A(2,1)开始进行地址匹配,后面的元素逐个对应使用相应的存储单元。
如果主程序中语句如下:
CALLSUB3(A(1,2))
则在主程序中执行CALL语句后,实参与形参的结合关系变为:
A(1,1)A(2,1)A(1,2)A(2,2)A(1,3)A(2,3)
在这里虚实结合是从形参的第一个元素B(-1)与作为实参的数组元素A(1,2)开始进行地址匹配,后面的元素逐个对应使用相应的存储单元。
结果形参数组元素B(3)没有对应的实参元素,虚实结合过程中就出现了错误。
但这种错误很隐蔽,一般不容易发现。
因此必须明确数组的存储结构,并且一定要保证实参中从作为实参的数组元素开始到数组结尾的元素个数大于或等于形参数组的元素个数。
综合比较前面数组名与数组元素作为实参的情况,可以得出如下结论:
1形参数组与实参数组的结构可以不同,但虚实结合时都是按照一维存储结构进行地址匹配。
②当实参为数组名时,是从实参数组的第一个元素与形参数组的第一个元素开始依次对应结合;
当实参为数组元素时,则是从该数组元素开始的实参数组元素与形参数组的所有元素逐个对应结合,而不仅仅是将作为实参的那一个数组元素传递给形参。
③从开始结合的实参数组元素到实参数组的最后一个元素之间的元素个数一定要大于或等于形参数组的元素个数。
3)可调数组作形参
前面所用到的数组,其元素个数都是固定不变的。
除此之外,在FORTRAN中允许在子程序中使用可调数组。
可调数组是指其大小在子程序被调用之前并不确定,而是在调用过程中由传递过来的实参来确定。
在主程序中定义数组时,数组下标的上下界必须是整型常量,而不能是变量或表达式。
但在子程序中,允许使用整型变量来定义数组中各维下标的上下界,而整型变量的值应该通过参数传递由调用程序传入。
这样即可在子程序中定义出可调数组。
使用可调数组的优点是可以通过同一个子程序来处理不同结构的数组,从而大大提高子程序的通用性。
例如,有如下的一段程序:
INTEGERA(2,4)
……
L=5
CALLSUB5(A,L)
N=8
CALLSUB5(A,N)
SUBROUTINESUB5(B,M)
INTEGERB(M)
END
子程序SUB5中的B是一个一维可调数组,数组元素个数(或称为数组的长度)由整型形参M给出,当主程序执行到调用语句时,形参M的值就由主程序的实参L或N传过来,从而确定可调数组的长度。
其中的虚实结合关系如下:
由L确定的虚实结合关系:
A(1,1)A(2,1)A(1,2)A(2,2)A(1,3)A(2,3)A(1,4)A(2,4)
B
(1)B
(2)B(3)B(4)B(5)
由N确定的虚实结合关系:
B
(1)B
(2)B(3)B(4)B(5)B(6)B(7)B(8)
使用可调数组时除需要遵守数组使用的语法规定外,还必须注意以下要求:
①可调数组名必须作为形参;
②可调数组的每一维上下界必须是整型变量,而且必须在调用时能够通过参数传递获得确定的值;
③使用可调数组时,一般要使传送给可调数组的各维下标的上下界与实参数组的一致。
4)过程名作为形参
过程名(即函数子程序名或子例行程序名)也可以作为形参出现在形参表中,FORTRAN编译器完全能够根据某个形参在子程序中出现时的上下文关系来确定它是函数子程序名、子例行程序名或其他标识符的名称。
因为若此形参在子程序中使用时,其后面跟有一对圆括号,而又没有相应的数组名定义,则此形参一定是函数子程序名;
而若此形参在子程序中使用时,出现在子程序中的CALL语句中,则此形参一定是子例行程序名。
在形参表中的子程序名,只是一个虚设的名称,并不代表程序中实际存在如此名称的一个函数子程序或子例行程序,实际的名称要在调用时通过实参向形参的传递来确定。
形参是一个子程序名时,对应的实参应该是同一类型的子程序名。
即当形参是函数子程序名时,对应的实参应该是函数子程序名或内部函数名,并且形参函数值与实参函数值应该具有相同的数据类型;
当形参是子例行程序名时,对应的实参应该是子例行程序名。
在调用程序的实参中如果出现外部函数名或子例行程序名时,必须在调用程序中用EXTERNAL语句来说明这些外部函数名或子例行程序名;
在调用程序的实参中如果出现内部函数名,则必须在调用程序中用INTRINSIC语句来说明这些内部函数名。
这里的EXTERNAL语句和INTRINSIC语句都是说明语句,用来说明本程序单位中作为实参的子程序名或内部函数名,而不是用来说明作为形参的子程序名。
子程序名的虚实结合使FORTRAN程序可以方便地使用许多通用子程序(特别是内部函数),也使程序按照模块划分为独立的子程序分别编写、编译、调试成为可能,为软件的工程化组织和实施提供了技术上的保证。
示例:
INTRINSIC语句对内部函数的说明。
INTRINSICSIN,COS
CALLSUB(SIN,1.0,A)
CALLSUB(COS,2.0,B)
WRITE(*,*)A,B
WRITE(*,*)SIN(1.0),COS(2.0)
SUBROUTINESUB(F,X,R)
R=F(X)
【例7.4】梯形法求积分
根据例5.21中的积分方法,其计算公式为:
=
h=
将梯形积分算法单独设计成一个子例行程序,被积函数单独设计成函数子程序,通过虚实结合来实现被积函数的传递。
程序如下:
FUNCTIONFUNC(X)
FUNC=1/(1+X)
SUBROUTINETRINT(F,A,B,N,S)
H=(B-A)/N
S=H*(F(A)+F(B))/2.0
DO10I=1,N-1
S=S+H*F(A+I*H)
EXTERNALFUNC
READ(*,*)N
CALLTRINT(FUNC,0.0,1.0,N,R)
WRITE(*,*)N,R
在此程序中采用的是指定积分区间的区间等分数N来求积分,实际上也可以不指定积分的区间等分数N,即“变步长”法:
由程序自动地改变N的值,使N=2,4,6,8,…直到满足指定的求积分精度。
上面的子例行程序的五个形参中后四个是变量,第一个形参(F)是函数子程序名。
因为在子例行程序中的表达式中出现了F(A)和(B),在FORTRAN中这样的形式要么是代表数组元素,要么代表函数调用。
而在子例行程序中并没有说明F是数组,由此可以确定F是实型子程序名。
因此在主程序的说明部分中将与形参F对应的实参“FUNC”用“EXTERNALFUNC”语句加以说明。
5)星号(*)作为形参
当形参是星号(*)时,与之对应的实参应该是一个冠有星号(*)的语句标号。
例如:
设计一个子例行程序,当参数C为加号(+)时,计算并打印A+B的值,C为减号(—)时,计算并打印A-B的值。
character*1c
read*,a,b,c!
输入2个运算数和一个标志数
callf(a,b,c,*10,*20,s)!
调用子程序
10print*,a,'
+'
b,'
='
s
goto30
20print*,a,'
-'
30end
subroutinef(a,b,c,*,*,s)
character*1,c
selectcase(c)
case('
)
s=a+b
return1!
返回到第一个带有*号的语句(即10号)
s=a-b
return2!
返回到第二个带有*号的语句(即20号)
endselect
6)变量的作用域
FORTRAN为每个变量在内存中分配一个存储区域(根据类型决定字节数),一旦某变量完成了自己的使命,FORTRAN将收回该变量占据的存储区域,该变量将变成无定义。
到底FORTRAN什么时候为变量分配存储区域,又什么时候收回存储区域呢?
这就是变量的作用域问题。
(1)变量存储区域的分配与收回
一个程序在投入运行时,机器将为该程序中的所有变量分配存储单元,当该程序退出运行时,机器将收回这个程序所占用的全部存储单元。
(2)变量作用域
一个变量通常只在本程序单元中起作用,离开建立该变量的程序单元,变量就被收回,这种作用域的局限性,使用户在设计程序时,只需要考虑在本程序单元中的变量是否互相干扰,而无需要考虑与其他程序单元之间的变量干扰问题,简化了程序的调试工作。
在程序调用时,调用程序单元的实参是通过与被调用程序单元的形参的结合来实现的,并不是调用程序单元中的变量直接在被调用程序单元中有定义。
下面举例来说明变量作用域的问题。
subroutinef(a,b,c)
integerx,y
x=5!
子程序F中有X,Y
y=8
c=a+b
print*,'
x,yinsub'
x,y
u=5
v=8
x=3!
主程序MAIN中有X,Y
y=2
callf(u,v,s)
x,yinmain'
print*,u,v,s
从程序执行结果可以看到,尽管在2个程序单元中都有X,Y,但它们是互不