《FORTRAN90程序设计教程 第8章 子程序.ppt》由会员分享,可在线阅读,更多相关《FORTRAN90程序设计教程 第8章 子程序.ppt(121页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、第八章第八章子程序子程序8.1语句函数语句函数8.2函数子程序函数子程序8.3子例行程序子例行程序8.4程序单元之间的数据传递程序单元之间的数据传递8.5递归调用递归调用8.6数据共用存储单元与数据块子程序数据共用存储单元与数据块子程序8.7内部子程序内部子程序8.8模块模块8.9程序举例程序举例子程序是构造大型程序的有效工具,一个实用程序(不管是系统程序还是应用程序),一般都含有多个子程序。FORTRAN90中的子程序按子程序完成的功能划分有子例行程序、函数子程序、数据块子程序等,按是否定义在某个特定的程序单元内部来划分有程序单元子程序、模块子程序以及内部(INTERNAL)子程序等,这些通
2、称为子程序,子程序不能独立运行,它们和一种称为主程序(就是本章前读者已经熟知的程序结构)的程序单元一起组成一个实用程序。一个程序可以不含子程序,但不能缺少主程序。本章讨论各种子程序的结构、功能以及子程序与主程序或子程序之间的数据交互作用。语句函数不具备子程序的一般书写特征,但其作用与子程序相同,也一并放在本章讨论。通过本章的学习,读者应能熟练地选择并设计恰当的子程序形式来构造自己的程序,从而提高程序设计能力。8.1语句函数语句函数在第二章中,读者使用过了诸如SIN(X)、SQRT(X)这些内在(INTRINSIC)函数。也有教科书称这一类函数为内部(INTERNAL)函数,但为了区别另外一种内
3、部子程序结构,本章称由FORTRAN编译器提供的函数为内在函数。这些内在函数是在程序设计过程中使用频率很高,并且一般实现比较复杂的函数。用户在使用内在函数时,并不需要对函数的实现过程进行描述,只需按照FORTRAN90手册要求使用即可,FORTRAN90编译器“认识”这些内在函数并能正确完成函数所规定的功能。但是,在实际设计过程中,用户还会遇到大量的并未包含在内在函数中的其他函数,计算这些函数时,就不能象内在函数那样仅仅使用函数名来使用这些函数,而必须在适当的地方以FORTRAN90能“理解”的形式向FORTRAN90编译器说明这些非内在的函数的计算过程与参数类型,当函数的计算过程简单到可以用
4、一个语句定义清楚时,这样的定义语句就称为语句函数。例8.1设多项式函数设计一个程序,计算f(1)、f(10)、f(12)、f(-5)的值。程序如PROGRAMEXAM1A:PROGRAMEXAM1AX=1FX=5*X*3-2*X*2+7*X+6WRITE(*,*)f(,X,)=,FXX=10FX=5*X*3-2*X*2+7*X+6WRITE(*,*)f(,X,)=,FXX=12FX=5*X*3-2*X*2+7*X+6WRITE(*,*)f(,X,)=,FXX=-1FX=5*X*3-2*X*2+7*X+6WRITE(*,*)f(,X,)=,FXEND显然,程序EXAM1A不简练,把一个相同的函数
5、表达式重复了多遍,如果能定义一个函数f(x),然后分别使用1、10等参数来调用f(x),将会有效的简化程序量,这就是本节要讨论的内容。811语句函数的定义语句函数的定义如前所述,由于FORTRAN90的编译器不“认识”一般的函数f(x),不知道怎样计算f(x)的函数值,因而,在使用(称为函数调用)函数时,必须向FORTRAN90编译器说明该函数的计算方法,这种说明过程称为函数定义。语句函数定义的一般格式是:1.语句函数名在语句函数定义格式中,f称为函数名,函数名的组成规则与变量名相同。如果没有在语句函数定义语句前用类型语句说明该函数名的数据类型,则该语句函数的函数值的类型按其函数名遵守IN隐含
6、规则。如f函数隐含为实型函数,而nf则隐含为整形函数。F(X,Y)=X*2+Y*2!定义了一个实型函数FINTEGERF(X,Y)=X*2+Y*2!定义了一个整型函数FNF(X,Y)=X*2+Y*2!也同样定义了一个整型函数F语句函数不能与同一个程序单元中的变量同名。2.语句函数的虚参在语句函数定义语句中的函数参数称为虚参,他们本身是没有值的,只有在函数调用时才用实际参数(称为实参)代替。实参或是常数、或是一个有确定值的变量、或是一个可以计算值的表达式。虚参在形式上与普通变量相同,一个语句函数中的虚参不能同名。不同语句函数中的虚参可以同名,虚参也可以和程序中的变量同名。当没有语句对虚参的数据类
7、型进行说明时,虚参的类型遵守IN隐含规则;当使用了类型说明语句对虚参类型进行说明后,这种说明对于虚参以及与虚参同名的变量同时有效。INTEGERZF(X)=3*X*2+5G(Y)=3*Y*2+5H(Z)=3*Z*2+5在上述程序中,函数F和G本质上是一个函数,因为对于任意的实参T,F(T)和G(T)总是相同的,但函数F、G和H有点不同,其虚参Z被说明为整型。当虚参个数多于一个时,虚参间用逗号分隔,当没有虚参时(这样的语句函数没有使用价值),函数名后的括弧也不能省略。3.语句函数表达式语句函数表达式说明函数与参数的对应关系,在函数表达式中,可以包含常量、变量、函数虚参(虚参必须包含在表达式中)、
8、FORTRAN90内在函数、数组以及前面已经说明了的语句函数。4.关于语句函数的进一步说明(1)只有当函数关系简单,可以用一个语句描述函数与参数的对应关系时,才能使用语句函数。(2)语句函数是非执行语句,语句函数的定义语句要放在一个程序单位的所有其他说明语句之,并放在所有可执行语句之前。(3)语句函数只有在本程序单元中才有意义。也就是说,不能使用其他程序单元中的语句函数。(4)语句函数中的虚参必须是变量,不能是常量、数组元素和内在函数等。(5)语句函数是有类型的,因而语句函数表达式的类型一定要和其函数名的类型相容812语句函数的调用语句函数的调用语句函数一经定义,就可以在同一个程序单元中调用它
9、,调用的形式和内在函数完全相同。函数调用要注意以下两个问题:1调用时可以使用常量、变量、内在函数以及它们的表达式作为实参代替对应的虚参位置,但要保证实参与虚参具有完全相同的类型,并且实参是可以计算值的(即调用前实参中包含的变量全部已经赋值。)2实参和虚参个数相同。例8.2用函数语句的方法设计例8.1PROGRAMEXAM1BINTEGERXF(X)=5*X*3-2*X*2+7*X+6WRITE(*,*)F(2),F(10),F(12),F(-2)END8.2函数子程序函数子程序语句函数由于要求在一个语句中完成函数的定义,因而它只能解决一些较简单的问题,当函数关系比较复杂,用一个语句无法定义时,
10、语句函数就无能为力了,这时需要用到函数子程序。821函数子程序的定义函数子程序的定义函数子程序是以保留字FUNCTION开头,并以保留字END结束的一个程序段,该程序段可以独立存储为一个文件,也可以和调用它的程序单元合并存储为一个程序文件。函数子程序的定义格式是:类型说明FUNCTION函数名(虚参表)函数体END函数名的命名方法与变量名相同,虚参可以是简单变量和数组变量,但不能是常数、数组元素、表达式)例8.3编程,求:分析:上述三个数列的通项不同,求和范围也不同,用一个程序段难以同时计算三个数列的和,并且,因为无法用一个语句函数完成数列的求和计算,所以也无法使用语句函数来简化程序设计。下面
11、用函数子程序来完成这个问题。程序如下:FUNCTIONSM(M,N,L)SM=0DOI=M,NIF(L0)THENSM=SM+I*LELSESM=SM+(1.0*I)*L!进行实型运算ENDIFENDDOENDFUNCTIONSMPROGRAMEXAM2!开始主程序单元定义WRITE(*,*)S1=,SM(1,100,2)!调用函数子程序SM完成S1的计算WRITE(*,*)S2=,SM(100,140,3)WRITE(*,*)S3=,SM(20,50,-1)END程序运行结果如下:S1=338350.000000 S2=7.291440E+07 S3=9.514656E-01对程序EXAM2
12、作如下说明:程序的第一部分是函数子程序的定义部分,如前所述,它可以单独存储为一个程序文件。从保留字PROGRAM开始至END是主程序部分,一个程序总是从主程序开始运行的,主程序的第24个语句都要打印函数SM的值,这时调用函数定义部分,根据函数的定义描述计算函数的值并予以打印。三个打印语句分别调用了三次函数子程序(即执行了三次函数子程序),从这里我们可以看到:采用函数子程序设计程序时并没有提高程序的执行效率,但却可以有效的提高程序的设计效率。当函数子程序的定义过程和主程序放到一个程序文件中时,存储顺序是任意的,函数子程序可以放到主程序之前(如EXAM2),也可以放到主程序之后,读者应该清楚,程序
13、总是从主程序开始执行。函数定义部分应注意如下问题:1函数值的类型可以在函数定义时予以说明,下面的两种说明方法是等效的:(1)INTEGERFUNCTIONF1(X1,X2.XN)函数体END(2)FUNCTIONF1(X1,X2.XN)INTEGERF1函数体END都说明F1是一个整型函数,当未使用这种显示的类型说明时(如例8.3),函数值的类型遵守IN隐含规则。2函数不能有同名虚参。虚参的类型可以在函数体中进行说明方法,当未对虚参类型进行说明时,虚参类型遵守IN隐含规则。3函数定义部分中一定要有一个语句将函数值赋值给函数名(如例8.3中的第5句和第7句)。这种赋值语句的格式是:函数名=表达式
14、注意不要在函数名后带上括弧,写成:函数名(虚参表)=表达式822函数子程序的调用函数子程序的调用定义函数子程序的目的是为了调用。不仅主程序可以调用一个函数子程序,函数子程序也可以调用其它的函数子程序,甚至于还可以调用本身(递归调用)。调用程序称为主调程序单元,而被调用的函数子程序称为被调程序单元。调用一个函数子程序的方法和调用内在函数和语句函数的方法基本相同:1调用时应该用实参代替函数子程序定义部分的虚参,实参和虚参的类型要相同。和语句函数一样,实参可以是常量、变量、表达式等。2调用程序单位中的变量不能与函数子程序同名。函数值的类型由函数定义程序单元决定,与调用程序单元无关。3当函数名的类型不
15、满足IN隐含规则时,在调用程序单元中要对函数名的类型给出说明(如例8.4中的主程序EXAM3的第2句。)4不能调用一个没有定义的函数子程序(这一点和内在函数是不同的。)例8.4用函数子程序的方法设计一个程序,求50100内的所有素数及其和。分析:设计一个函数子程序PRIME(N),函数PRIME的值定义如下:主程序的任务是应用PRIME函数子程序在50100之间使用枚举法求出那些使PRIME函数值为1的自然数并求这些数的和。函数子程序如下:FUNCTIONPRIME(N)INTEGERPRIME*定义PRIME是整型函数PRIME=0DOI=2,N-1IF(MOD(N,I)=0)GOTO10*
16、参数N有因子I,非素数,退出循环ENDDOPRIME=1*参数N无任何因子,函数值为110END主程序如下:PROGRAMEXAM3INTEGERPRIME*说明要调用的函数PRIME为整型DOI=50,100IF(PRIME(I)=1)THENS=S+IWRITE(*,*)IENDIFENDDOWRITE(*,*)S=,SEND运行结果如下:.83 89 97 S=732.000000例 8.5 当一个数各个数位的立方和等于这个数本身时,称这样的数为水仙花数(如153=1*3+5*3+3*3,所以,153是一个水仙花数,)编程:求100999之间的水仙花数。分析:设计一个函数子程序NUM(N
17、,I),当I=100时,函数NUM返回N的百位上的数;当I=10时,NUM返回N的十位上的数,I=1时,NUM返回N的个位上的数。主程序中调用NUM函数在100999之间找出所有水仙花数。程序如下:PROGRAMFLOWERDOI=100,999IF(NUM(I,100)*3+NUM(I,10)*3+NUM(I,1)*3=I)THENWRITE(*,*)IENDIFENDDOENDFUNCTIONNUM(N,I)SELECTCASE(I)CASE(100)NUM=N/100!用N百位上的数赋值给函数名CASE(10)NUM=MOD(N/10,10)!用N十位上的数赋值给函数名CASE(1)NU
18、M=MOD(N,10)!用N个位上的数赋值给函数名ENDSELECTEND程序运行结果如下:153 370 371 407除了函数子程序外,还有一种子例行子程序,函数子程序和子例行子程序都是一种独立的程序单元,二者的差别是:函数子程序的名字代表一个值,因而是有类型的,而子例行程序的名字不代表一个值,因而其名字没有类型问题。从使用上来说,二者是可以相互替代的。831子例行子程序的定义子例行子程序的定义子例行子程序是由保留字SUBROUTINE开头,到保留字END结束的一个程序段。其定义格式是:SUBROUTINE子程序名(虚参表)子例行程序体END子程序的命名方法与变量相同。虚参由变量、数组名(
19、不能是数组元素,常数、表达式)充当,当虚参多于一个时,各虚参间用逗号分隔,当没有虚参时,子例行程序名后的一对括弧可以省略。子例行程序的设计方法和函数子程序相同,但因为其名字没有值,所以不能有对子例行程序的名字赋值的语句。83子例行程序子例行程序832子例行子程序的调用子例行子程序的调用子例行程序的调用格式是:CALL子例行程序名(实参表)其实参的类型与函数子程序相同。和函数子程序的调用不同的是,子例行子程序的调用是一个独立的语句。子例行程序调用的其它事项与函数子程序的调用相同。下面通过两个实例来讨论函数子程序与子例行程序的相同点与不同点。例8.6用子例行程序的方法完成例8.3分析:例8.3中定
20、义了一个函数SM(M,N,L),我们将该函数子程序修改为一个子例行程序SM(S,M,N,L),虚参M,N,L的含义与例8.3相同,虚参S用来存储结果和(用来替代函数名SM)。程序如下:SUBROUTINESM(S,M,N,L)DOI=M,NIF(L0)THENS=S+I*LELSES=S+(1.0*I)*LENDIFENDDOENDPROGRAMEXAM5CALLSM(S1,1,100,2)CALLSM(S2,100,140,3)CALLSM(S3,20,50,-1)WRITE(*,*)S1=,S1WRITE(*,*)S2=,S2WRITE(*,*)S3=,S3END运行结果如下:S1=338
21、350.000000 S2=7.291440E+07 S3=9.514656E-01例8.7用随机方法生成一个包含20个元素的数组,对该数组按升序排序打印。数据排序的问题读者在第七章已经很熟悉了,下面直接给出程序:PROGRAMEXAM5PARAMETER(N=20)DIMENSIONA(N)INTEGERAA(1)=17该语句及随后的三个语句生成数组ADOI=2,20A(I)=MOD(19*A(I-1),1024)ENDDODOI=1,N-1二重循环完成对A的排序DOJ=I+1,NCALLSWAP(A(I),A(J)ENDDOENDDOWRITE(100,200)(A(I),I=1,N)20
22、0FORMAT(2(2X,10(I5,X)/)END子例行程序SWAP(M,N)的功能是,若MN,就交换M、N的值,程序段如下:SUBROUTINESWAP(X,Y)INTEGERX,Y,TIF(XY)THENT=XX=YY=TENDIFEND程序执行结果是:3 17 57 59 115 137 139 169 305 321 323 537 545 555 593 675 891 979 987 1017从上面两个例题可以看出:1子例行程序和调用它的主程序的存放顺序是无关紧要的,既可以将子程序存放在前,也可以将主程序存放在前,还可以作两个程序文件分别存放。2子例行程序和函数子程序在使用上可以
23、相互替代。但是,当要求一段子程序有返回值时,以选择函数子程序比较方便(如例8.6),当子程序没有返回值时(如例8.7),则选择子例行程序较为方便例8.8设计一个自例行程序程序,求任意矩阵的转置矩阵。设计一个子例行程序TRAN(A,B,M,N)完成将矩阵A转置后放矩阵B,M、N分别是矩阵A、B的行数和列数。主程序如下:PROGRAMEXAM6PARAMETER(M=3)PARAMETER(N=4)DIMENSIONA(M,N),B(N,M)INTEGERA,BWRITE(*,*)pleaseinputa3x4MatraREAD(*,200)(A(I,J),J=1,N),I=1,M)CALLTRA
24、N(A,B,M,N)WRITE(*,300)(B(I,J),J=1,M),I=1,N)200FORMAT(4I4)300FORMAT(3I5)END子例行程序如下SUBROUTINETRAN(A,B,M,N)INTEGERA(M,N),B(N,M),M,NDOI=1,MDOJ=1,NB(J,I)=A(I,J)ENDDOENDDOEND运行情况:pleaseinputa3x4Matra 1 2 3 4 5 6 7 8 9 10 11 12显示结果如下:1 5 9 2 6 10 3 7 11 4 8 12841简单变量作为虚参简单变量作为虚参这是一种最常见的情况,例8.4,8.5都是这种情况,在这
25、种情况下,根据子程序调用时实参的不同类型,又可以进一步分为两种情况:1简单变量或数组元素作为实参FUNCTIONSUB(A,B)PROGRAMTTEMX=3.Y=-5S=S+SUB(X,Y).ENDEND不同程序单元之间的数据传送方法有参数的虚实结合、建立公用区及通过数据文件等三种方式实现。本节先讨论参数的虚实结合方法,8.7节讨论公用区的概念与用法,第十一章讨论文件方法。图8.1A、X,B、Y共用存储单元8.4程序单元之间的数据传递程序单元之间的数据传递此种情况下FORTRAN90系统将实参与虚参安排同一个存储单元,对虚参的任何改变都作用在对应的实参上,因而调用一个子程序(包括函数子程序和子
26、例行程序)时,实参的值有可能改变。如图8.1所示:2常量或表达式作为实参当用常量或表达式作为实参时,FORTRAN90编译器首先计算表达式的值(如果实参为表达式时。)然后将该值赋值给对应的虚参。此种情况下,子程序中不能改变与常量(或表达式)对应的虚参的值,否则结果难以预料。842数组名作为虚参数组名作为虚参当虚参是数组名时,对应的实参可以是与虚参类型相同的数组名或数组元素,并且实参与虚参共用一片连续的存储单元。1虚参为数值型或逻辑型数据(此时的实参当然也是对应类型)(1)实参为数组名时,FORTRAN90编译器将实参数组的第一个元素的存储地址传送给子程序,并将其作为对应虚参数组的第一个元素的存
27、储地址,从而使两者共用一片存储单元。(2)当实参是一个数组元素时,FORTRAN90编译器将该元素的存储地址传送给子程序,并将其作为对应虚参数组的第一个元素的存储地址,从而使虚参数组与实参自该元素以后的元素共用一片存储单元。在实参与虚参数组之间传送数据时,不要求两者的行列数相同,甚至于实参元素的个数可以多于虚参(这种情况下,多余的实参不参与子程序中的运算。)读者应该牢记:FORTRAN90是按列为主存放多维数组的,实参和虚参间的元素按存储顺序对应。如:PROGRAMTTSTFUNCTIONSUB2(X)DIMENSIONA(3,4)DIMENSIONX(2,6)DOI=1,3S=0DOJ=1,
28、4DOI=1,2A(I,J)=I+JDOJ=1,6ENDDOIF(MOD(I+J,3)=0)THENENDDOS=S+X(I,J)WRITE(*,*)SUB2(A)ENDIFENDENDDO在程序TTST的倒数第二句调用了函数子程序SUB2,虚参X是一个2x6的矩阵,而实参A是一个3x4的矩阵,两者的元素对应关系是:A(1,1)X(1,1),A(2,1)X(2,1),A(3,1)X(1,2)A(1,2)X(2,2),A(2,2)X(1,3),A(3,2)X(2,3)A(1,3)X(1,4),A(2,3)X(2,4),A(3,3)X(1,5)A(1,4)X(2,5),A(2,4)X(1,6),A
29、(3,4)X(2,6)在函数子程序SUB2中,满足求和条件的元素分别是:X(2,1),X(1,2),X(2,4),X(1,5)这些元素对应的实参元素是:A(2,1),A(3,1),A(2,3),A(3,3)故所求的函数值为:3+4+5+6=182虚参为字符型数据当虚参为字符型数据时(当然对应的实参也为字符型数据),实参和虚参不是按照数组元素的顺序对应,而是按照字符位置一一对应,读者只要了解FORTRAN90中字符型数据的存储规则,并将实参与虚参的存储顺序派出来,就不难确定实参元素与虚参元素的对应关系,这里不再详细说明。3可调数组请读者重新考察例8.8中的子例行程序TRAN(A,B,M,N),在
30、子程序的说明中将A、B说明为A、B分别是一个M*N的矩阵,注意在子程序中并没有具体规定M、N的值,这样的数组称为可调数组,可调数组的引入提高了子程序的适应性,大大提高了编程效率。应注意可调数组只能作为虚参使用,不能在主程序中使用可调数组,也不能在子程序的其它地方使用可调数组。843子程序名作为虚参子程序名作为虚参子程序的虚参不仅可以是前面所述的各种数据类型,还可以是一个子程序名。例8.9设有三个连续函数:分析:用Simpson方法求函数的积分时要计算被积函数的函数值,因为三个被积函数不同,求其函数值的方法也就不同,要设计一个统一的函数子程序,必须设计一个虚参函数,并用被积函数作为实参来调用该函
31、数子程序。用Simpson方法求函数定积分的函数子程序如下:首先定义三个被积函数F(X)、G(X)、H(X)的函数子程序:FUNCTIONF(X)!被积函数F(X)F=SIN(3*X)+COS(X)ENDFUNCTIONG(X)!被积函数G(X)G=5*X*3+2*X-10ENDFUNCTIONH(X)!被积函数H(X)H=1/(1+X*2)END再建立SIMPSON求积分函数子程序FUNCTIONSIMPSON(F,A,B)H=(B-A)/2C=(A+B)/2SIMPSON=H*(F(A)+4*F(C)+F(B)/3ENDPROGRAMSIMPSON_PROEXTERNALF,G,H!三个被
32、积函数都非FORTRAN90内在函数REALI1,I2,I3!故定义三个函数名F、G、H为!计算F(X)的积分!外部函数(EXTERN属性)I1=SIMPSON(F,0,2*3.1416)I2=SIMPSON(G,0,10.0)I3=SIMPSON(H,0,1.0)WRITE(*,*)I1=,I1WRITE(*,*)I2=,I2WRITE(*,*)I3=,I3END程序的执行结果是:I1=-2.094445 I2=12500.000000 I3=7.833334E-01关于实参函数名的说明:1EXTERNAL和INTRINSIC调用虚参中有程序名的子程序(包括函数子程序和子例行程序)时,我们要
33、在虚参的位置代之以一个实际存在的子程序名作为实参,实参子程序如果是FORTRAN90的内在函数,则在调用程序段中要用保留字INTRINSIC对该程序名作出说明,如果实参子程序是自己设计(或调用他人的程序库)的,则必须用保留字EXTERNAL对实参程序名作出说明(如例8.9),这种对函数名属性的说明就和对变量的类型进行说明一样,说明语句必须放在该程序段的所有可执行语句前。必须强调:只需对实参的属性进行说明,至于虚参,因为其只是一个并不存在的形式子程序,并不具有INTRINSIC属性或EXTERNAL属性,是无法也无需说明其属性的。2内在函数的专用名和通用名FORTRAN90的内在函数有通用名和专
34、用名之分(部分函数的通用名和专用名一致),用FORTRAN90的内在函数作为实参时,只能使用这些函数的专用名而不能使用其通用名。844星号星号(*)作为虚参作为虚参星号(*)也可以作为虚参,与星号(*)虚参对应的实参是一个冠有星号(*)的语句标号。如:PROGRAMTESTSUBROUTINEF(S,*,*).IF(条件1)THEN.RETURN1!return与1之间有空格REACLLF(A,*100,*200)ELSE*100.GOTO300RETURN2*200.ENDIF.300ENDEND例8.10设计一个子例行程序,当参数C为加号(+)时,计算并打印A+B的值,C为减号(-)时,计
35、算并打印A-B的值。SUBROUTINEF(A,B,C,*,*,S)CHARACTER*1C!定义参数C为单个字符变量SELECTCASE(C)CASE(+)S=A+BRETURN1!返回到第一个冠有*号的语句标号CASE(-)S=A-BRETURN2!返回到第二个冠有*号的语句标号ENDSELECTENDPROGRAMTESTCHARACTER*1CWRITE(*,*)PLEASEINPUT2NUMBERANDAOPERATIONSIGNREAD(*,*)A,B,CCALLF(A,B,C,*10,*20,S)10WRITE(*,*)A,+,B,=,S!C参数为加号返回到此GOTO3020WR
36、ITE(*,*)A,-,B,=,S!C参数为减号返回到此30END应该指出,象上面这样的分支问题,可在子程序单元F或调用程序单元TEST中用分支程序很容易地实现,这里采用的子程序中根据分支条件而返回到不同的出口的方法,违背了一个程序单元应具有单一出口的结果化原则,是不应提倡的,读者在程序设计的实践中应尽量避免这种用法。845变量的作用域变量的作用域变量是为了完成一个计算任务而设置的一些数据存储单元。FORTRAN90为每一个变量在内存区域中建立一个物理的连续存储区,一旦某个变量完成其使命,FORTRAN90将释放该变量所占据的物理存储区,该变量变得无定义。程序设计人员不能使用已经被FORTRA
37、N90释放的变量。到底FORTRAN90何时为变量建立物理的存储区,又何时释放这些存储区呢?下面就来讨论这些问题。1变量存储区的分配与释放一个程序(是一个完整的程序而非组成程序的各程序单元)在投入运行时,系统为这个程序的全部变量一次性分配存储单元,当这个程序退出运行时,系统收回这个程序所占据的全部存储单元。一个程序中的全部变量被同时建立存储单元。一般来说,一个程序的变量单元也将被同时释放,除非我们声明保留某个变量的存储单元。2变量作用域一个变量通常只在本程序单元(这里是程序单元而非整个程序)中起作用,离开建立该变量的程序单元,变量就失去了定义,这种作用域的局限性,使用户在设计程序时,只需考虑在
38、本程序单元中的变量只否有相互干扰,而无需考虑与其它程序单元之间的变量干扰问题,简化了程序的调试工作。在程序调用时,调用程序单元的实参是通过与被调用程序单元的虚参的结合来实现的,并不是调用程序单元中的变量直接在被调用程序单元中有定义。为了帮助读者更清楚的了解这一点,下面举一个说明变量作用域的例子,请读者仔细分析程序、程序的说明以及程序结果,以弄清变量的作用域这一个重要问题。例8.11讨论变量的作用域SUBROUTINEF(A,B,C)INTEGERX,YX=5!子程序S中有X、YY=8C=A+BWRITE(*,*)X、YINSUB,X,YENDPROGRAMEXAM8U=5V=8X=3!主程序中
39、也有X、YY=2CALLF(U,V,S)WRITE(*,*)X、YINMAIN,X,YWRITE(*,*)U,V,SEND程序的执行结果是:X、Y IN SUB 5 8 X、Y IN MAIN 3.000000 2.0000005.0000008.00000013.000000从程序执行结果可以看到,尽管在C程序单元EXAM8和程序单元VERY中都有变量X、Y,但其值是不相同的,说明它们是不相干的变量,只是同名而已。3子程序中变量的存储属性变量有数据类型,同时还有存储属性,数据类型决定变量的运算特征,而存储属性决定一个变量所占用的存储单元在什么时候被释放,FPS中,变量的几种常见的存储属性有:
40、(1)SAVE属性:当声明变量的子程序执行完毕后,具有SAVE属性的变量的存储状态依然保留,在下一次调用该子程序时,这些变量保持上一次调用返回时的值。(2)STATIC属性:具有该类属性的变量在整个程序(不是声明该变量的子程序)执行期中一直保留在存储单元中,这是变量的默认存储属性(3)AUTOMATIC属 性:当 声 明 变 量 的 子 程 序 执 行 完 毕 后,具 有AUTOMATIC属性的变量的存储单元被系统回收。变量的存储属性的说明方法是:存储属性变量名表这里的存储属性是上述三种属性之一。如:AUTOMATICA,B,C!定义实型变量A、B、C具有AUTOMATIC存储属性INTEGE
41、RSAVEX,Y!定义变量X、Y为整型,SAVE存储属性例8.12说明变量存储属性的作用。PROGRAMDATAS_TESTDOI=1,10WRITE(*,*)I,F()ENDDOENDFUNCTIONF()STATICN,S!定义N、S为STATIC属性N=N+1S=S+NF=SEND程序的执行结果是:1 1.000000 2 3.000000 3 6.000000 4 10.000000 5 15.000000 6 21.000000 7 28.000000 8 36.000000 9 45.000000 10 55.000000如果将函数子程序中的N、S变量定义为AUTOMATIC属性,
42、结果与现在完全不同,请读者将修改后的程序上机调试运行,并分析程序结果。递归是一种很有用的数学思想,该种思想使得人们可以使用很简单的方法处理一些无穷概念。在程序设计语言中,所谓递归,就是允许在一个子程序的定义部分直接或间接地调用被定义子程序。FORTRAN语言在FORTRAN90以前的版本都不支持对子程序的递归定义,FORTRAN90增加了递归了这一功能,方便了用户。851递归的概念递归的概念数学上很多常见的概念能很好地说明递归的概念,为了帮助读者理解程序设计中的这一重要思想,下面讨论几个实例:第一个例子:自然数的定义。数学中自然数的定义是用递归的方法给出的,其定义是:(1)1是自然数。(2)如
43、果N是一个自然数,则N+1是一个自然数。在上述定义中,使用了“自然数”这一被定义的概念。第二个例子:阶乘函数的定义。上述定义中,使用(n-1)!来定义n!,其实还有很多这样的例子,如数学的一个分支图论中的图就是一个典型的递归定义的概念。85递归调用递归调用关于递归定义,读者应注意两个方面:第一、递归定义都应该有一个非递归的分支。如第一个例子中明确说明1是一个自然数(非递归定义),第二个例子中明确了1!=1,也不是采用递归的定义。非递归定义分支是正确推导的出发点。第二、在递归定义的递归分支中,被定义的概念和用来作为递归定义的概念在规模上有所不同。如第一个例子中在假设n是一个自然数后,肯定n+1是
44、一个自然数(规模越来月大,)第二个例子中用(n-1)!来定义n!(规模越来越小。)规模地改变是按照递归定义能在有穷步骤内解决问题的保证。如我们根据第一个例子中关于自然数的定义来判断某个数N是否自然数,根据定义,1是一个自然数,于是1+1=2是一个自然数,并且3、4.是自然数,如果N能由1加上有限个1得到,则N是自然数,否则不是。在第二个例子中,我们如果需要计算10!,根据定义的第二部分,得到10!=10*9!,对9!再继续使用定义,连续9次,得到:10!=10*9*8*7.*1852递归函数递归函数递归函数的定义格式是:RECURSIVEFUNCTION函数名(虚参表)RESULT(变量名).
45、调用该函数本身.END与一般函数子程序的定义格式比较,这里在保留字FUNCTION前加上了另一个保留字RECURSIVE,并且后面多了一个RESULT(变量)成分。该变量是FORTRAN90用来存放函数的中间结果变量,其类型应与函数名的类型相同。在递归函数的函数体中,给函数赋值不是赋值给函数名,而是给RESULT后面括号中的变量赋值。在退出函数子程序并返回到其调用程序单元之前,FORTRAN90会将该变量的值自动赋值给函数名。例8.13设计一个计算N!的函数子程序,并用其计算从键盘输入的任意自然数N的阶乘。函数子程序如下:RECURSIVEFUNCTIONFAC(N)RESULT(FAC1)I
46、F(N=1)THENFAC1=1!只能给FAC1赋值ELSEFAC1=N*FAC(N-1)!只能调用FAC函数ENDIFEND主程序如下:PROGRAMFAC_PROWRITE(*,*)PLEASEINPUTANUMBER.READ(*,*)NWRITE(*,*)N,!=,FAC(N)END运行情况是:PLEASE INPUT A NUMBER.5(并键入回车键)5!=120.000000在递归函数子程序中,不能直接给函数名赋值,而只能给RESULT变量(这里是FAC1)赋值,但在递归调用时,则必须使用函数名才可以,这一点请读者务必注意。下面对该程序的执行过程作一些分析:程序从主程序FAC_P
47、RO的第一个语句WRITE开始执行,屏幕上出现“PLEASEINPUTANUMBER.”提示。用户从键盘输入5,EXAM6用参数5调用函数子程序FAC,这是第一次调用,调用的程序单元是FAC_PRO。在函数子程序FAC中,因为实参5不等于1,执行函数体的ELSE块,以参数N-1=4调用FAC,这是第二次调用FAC,调用的程序单元是FAC本身,如此反复五次,到第五次调用FAC函数子程序时,调用参数N=1,执行FAC的IF块,计算出此时的函数值为1,并最终计算出函数值为120。如果在主程序FAC_PRO中,用户给N输入的值是一个负整数,如-5,根据以上的分析,其递归调用过程是:FAC(-5)=-5
48、*FAC(-6)=(-5)*(-6)*FAC(-7)=.因为调用实参永远不等于1,因而每一次的调用都引起一个新的递归调用,这样的递归过程显然没有完结的时候,为避免这种情况,应该在函数子程序FAC中有对N0)THENFAC1=N*FAC(N-1)!只能调用FAC函数ELSEFAC1=0!假定负数的阶乘函数值为0ENDIFEND程序中假定,当实参0时,函数值为0下面再讨论一个读者熟悉的例子。例8.14Fibonacci数列可以用下列函数形式来定义:设计一个函数子程序,计算Fib(n)。不用递归方法,读者也可以很容易的设计出求Fib(n)的函数,但使用递归方法会使程序更自然一些,下面是使用递归方法的
49、函数子程序:RECURSIVEFUNCTIONFIB(N)RESULT(FI)SELECTCASE(N)CASE(:-1)FI=-1!假定负数的函数值为-1CASE(0)FI=0CASE(1)FI=1CASE(2:)FI=FIB(N-2)+FIB(N-1)ENDSELECTEND程序FIB_PRO7用FIB函数子程序计算FIB(1)到FIB(10)的值。PROGRAMFIB_PROWRITE(*,*)(FIB(I),I=1,10)END结果是:1.000000 1.000000 2.000000 3.000000 5.000000 8.000000 13.000000 21.000000 34
50、.000000 55.000000关于递归函数子程序,读者应注意:1函数子程序单元中不应该直接给函数名赋值,而应该给RESULT变量赋值,并且RELUST变量应和函数名的数据类型相同;2函数体一般由两个或两个以上的分支构成,其中至少有一个分支是非递归的(例8.10的IF块分支、例8.11的:-1,0,1三个分支都是非递归的),并且递归分支的参数应朝非递归分支的方向发展。853递归子例行程序递归子例行程序递归子例行程序的定义格式:RECRUSIVESUBROUTINE子例行程序名(虚参表).CALL子例行程序名(实参).END例8.15设设计一个程序,求大于某个数据E的最小的S及对应的n分析:这