《第3章 模块化程序设计—例程和模块.ppt》由会员分享,可在线阅读,更多相关《第3章 模块化程序设计—例程和模块.ppt(41页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、第3章 模块化程序设计例程和模块,“模块”直接映射为例程:子程序和函数 内部例程和外部例程 程序构造单元:主程序(包含有内部例程)、模块(包含有内部例程)、外部例程(单独的,包含有内部例程),第一节 内部例程,一 内部函数 1. 构造形式 实例牛顿法求解方程:方程的一般形式为 设根的一个近似值为 , 新的近似值 为: 其中: 是 的一阶导数,通过叠代的方法获得近似解 ,满足 。 假设 f(x)=x3+x-3, 那么f(x)=3x2+1;根的初值设为 2;叠代控制条件为f(x)10-6 ,或者叠代步数不超过20;用F(x)代表f(x),用DF(x)代表f(x)。,例 3-1 牛顿法解方程 PRO
2、GRAM Newton IMPLICIT NONE INTEGER : Its =0 !叠代数 INTEGER : MaxIts =20 !最大叠代数 LOGICAL : Converged =.false. !是否收敛 REAL : Eps = 1e -6 !叠代精度 REAL : X =2 !根的初值 DO WHILE (.NOT. Converged .AND. ItsMaxIts) X = XF(X)/DF(X) PRINT*,X,F(X) Its = Its +1 Converged = ABS(F(X)=Eps END DO IF (Converged) THEN PRINT*,
3、Newton converged ELSE PRINT*, Newton diverged END IF,CONTAINS FUNCTION F(X) REAL F, X F = X * 3 + X 3 END FUNCTION F FUNCTION DF(X) REAL DF, X DF = 3 * X * 2 + 1 END FUNCTION DF END PROGRAM Newton,内部函数位于主程序的 CONTAINS 关键字和END 语句之间, 构造形式如下: FUNCTION 函数名( 参数列表 ) 声明语句 执行语句 END FUNCTION 函数名 其中, 为可选部分。 IM
4、PLICIT NONE,强制类型声明,其作用域为整个程序单元,所以无需在内部函数中再重写此语句。 尽管可以在程序单元头部声明内部函数的函数名,参数及其有关变量,但从数据安全性考虑,还是应该在内部函数中予以声明。 若在内部函数中声明了和全局变量同名的局部变量,全局变量被屏蔽,内部函数引用的是局部变量,这种现象称为同名覆盖。,2. 全局变量和局部变量 例3-2 内部函数采用全局变量 PROGRAM Factorial IMPLICIT NONE INTEGER I DO I =1, 10 PRINT*, I, Fact(I) END DO CONTAINS FUNCTION Fact(N) INT
5、EGER Fact, N, Temp Temp = 1 DO I = 2, N Temp = I * Temp END DO Fact = Temp END FUNCTION Fact END PROGRAM Factorial,这是一个求N的阶乘的实例,程序输出结果: 1 1 3 6 5 120 7 5040 9 362880 11 39916800 13 1932053504 15 2004310016 17 -288522240 19 109641728,上述程序的执行过程是: I 是一个全局变量,当内部函数 Fact 第一次被引用时,I 1,该值当传递给虚参 N,相同的 I 在函数循环
6、体中被赋初值2,此时 2N,不执行DO循环(Fact=1),当函数Fact 返回到主程序打印时,I 的值为2;下一次引用时,I 在主程序 DO 循环中增至3,依次类推 正确方式是在内部函数中重新声明 I为局部变量。例如:INTEGER I 在内部例程中声明所有变量应成为一条编程规则,以消除全局变量带来的负面影响。 共享数据,声明变量模块。,3. 函数返回值 Fortran中,表达式(函数)值一是通过函数名赋值来实现, 例如: F= X*3+X-3 此时赋值号左边的函数名不能带参数表。 另一是通过RESULT 子句来实现,例如: FUNCTION F(X) RESULT(R) REAL R,X
7、R = X*3+X-3 END FUNCTION F 其中,R代表函数结果,其数据类型代表函数类型;赋值号左边不再是函数名F ,而是函数结果 R , 相应的函数类型声明,也由声明函数名 F 改为声明函数结果 R 。,4. 语句函数 Fortran 90 也支持Fortran 77 提供的语句函数,不推荐。 例3-3语句函数的使用 PROGRAM Statement_Function IMPLICIT NONE REAL f,x,y !函数名f、形参x、实参y均需声明类型 f(x) = x*2/SQRT(1.0 + 2.0*x + x*2) !定义语句函数 DO WRITE(*,(A),ADVA
8、NCE = NO) 输入 x 的值: READ*,y IF(INT(y) = 0) EXIT !0终止循环 PRINT*,f(,y,)=,f(y) END DO END PROGRAM Statement_Function,语句函数的形式为: 函数名(参数1,参数2,)=函数表达式 语句函数在形式上和数学上的函数表达式完全一样,语句函数使用时应注意以下几点: (1) 语句函数先定义后使用,且只能用一条语句来定义; (2) 定义语句应放在声明部分,且放在语句函数相关的类型声明之后; (3) 参数列表可以为空(此时,函数实际上是一常量表达式),但函数名后边的一对括号,无论在定义还是引用时都不能省略
9、。,二 内部子程序 内部子程序和内部函数主要差别在于: (1)没有返回值和子程序名关联,因此无需声明子程序类型; (2)通过CALL语句调用子程序; (3)在例程原型(头)和 END 语句(尾)中,使用关键字SUBROUTINE; (4)若子程序参数表为空,子程序名后的一对括号可以省略; (5)函数通过函数名返回一个值,子程序通过函数参数可以返回多个值。,例3-4 内部子程序用来实现2个数据的返回(交换) PROGRAM Exchange IMPLICIT NONE REAL : A =1, B=5 CALL Swop(A, B) PRINT*, A, B CONTAINS SUBROUTIN
10、E Swop(X,Y) REAL Temp, X, Y Temp = X X = Y Y = Temp END SUBROUTINE END PROGRAM,程序说明: Fortran 参数传递,缺省为引用传递(地址传递); 子程序调用时,实参A、B的值被传递给参数 X、Y,改变后的形参值被返回调用程序,实现两个数据的交换; 子程序中的Temp 为局部变量,在主程序中不可访问; 如果希望参数以数值方式传递,这样形参的改变不影响实参,为此Fortran 90 提供了INTENT 属性。 内部子程序的构造形式: SUBROUTINE 子程序名(参数表) 声明语句 执行语句 END SUBROUTI
11、NE 子程序名 ,主程序构造形式: PROGRAM 程序名 声明语句 执行语句 CONTAINS 内部例程 END PROGRAM 程序名 注意事项: (1)一个完整的程序有且只有一个主程序; (2)主程序只有 END 语句是必须的,其他都是可选的; (3)若含有内部例程,必须出现关键字CONTAINS; (4)可以有多个内部例程,但内部例程不能再含有自己的内部 例程,不允许内部例程的嵌套; (5)END 语句若出现程序名,其前面的 PROGRAM 关键字不能少。,第二节 主程序,构造形式: SUBROUTINE 子程序名(参数表) 声明语句 执行语句 CONTAINS 内部例程 END SU
12、BROUTINE 子程序名 或者 FUNCTION 函数名( 参数表 ) 声明语句 执行语句 CONTAINS 内部例程 END FUNCTION 函数名 外部例程和内部例程的比较: (1)外部例程是单独的外部文件,除头、尾外,形式上与主程序是相同的; (2)外部例程可以含有内部例程,而内部例程不能再含有内部例程; (3)END语句中的关键字 FUNCTION/ SUBROUTINE,在外部例程中是可选的,但在内部例程中是必须的。,第三节 外部例程,例 3-5 采用外部例程实现 Swop(交换) PROGRAM Exchange IMPLICIT NONE EXTERNAL Swop !声明例
13、程Swop 为外部的 REAL : A =1, B=5 CALL Swop(A, B) PRINT*, A, B END PROGRAM Exchange SUBROUTINE Swop(X, Y) REAL Temp, X, Y Temp = X X = Y Y = Temp END SUBROUTINE Swop 若外部程序名与系统的标准例程名相同,编辑器会优先引用标准例程,而不是用户定义的外部例程;为避免出现这种情况,可在调用例程的声明部分,添加外部例程声明语句(EXTERNAL),这应该成为一个编程惯例。,例程接口:包括例程名、参数个数及各自的数据类型等信息。分为:显式接口(对标准例程
14、、内部例程和模块例程)和隐式接口(对外部例程)。 Fortran 90提供接口块,以向调用程序明确外部例程的接口信息。 接口块构造形式: INTERFACE 接口体 END INTERFACE 接口体由外部例程头、参数声明和外部例程尾构成。 参数名可以和外部例程定义用的参数名不同,也可以相同(将外部例程定义的参数声明部分直接拷贝)。,第四节 接口块,例3-6 在主程序中添加接口块,实现Swop外部例程 PROGRAM Exchange IMPLICIT NONE INTERFACE SUBROUTINE Swop(X,Y) REAL Temp,X,Y END SUBROUTINE Swop E
15、ND INTERFACE REAL : A =1, B=5 CALL Swop(A,B) PRINT*, A , B END PROGRAM Exchange 在程序中不需要用EXTERNAL 语句声明外部例程。,必须使用接口块的情况: (1)外部例程具有可选参数; (2)例程用来定义操作符重载; (3)外部函数返回数组或变长字符串; (4)外部例程具有假定形状数组,指针或目标参数; (5)例程作参数; (6)例程重载。 除了操作符和例程重载外,其他的只要将外部例程转化为模块例程,就可以免去提供接口块的麻烦,模块在Fortran 90中具有十分重要的作用。,1 构造形式 Fortran 90
16、有三种程序单元:主程序、外部例程和模块,模块主要是用来在程序单元之间共享数据和操作例程。其构造形式如下: MODULE 模块名 声明语句 CONTAINS 模块例程 END MODULE 模块名 ,第五节 模块,例3-7 模块使用 MODULE MyUtils REAL, PARAMETER : PI =3.1415927 CONTAINS SUBROUTINE Swop(X, Y) REAL Temp , X , Y Temp = X X = Y Y = Temp END SUBROUTINE Swop END MODULE MyUtils !模块和主程序在同一文件中,模块在主程序之前 PR
17、OGRAM Main USE MyUtils !位于IMPLICIT NONE、声明语句之前 IMPLICIT NONE REAL : A = PI, B = PI*2 CALL Swop(A, B) PRINT*, A , B END PROGRAM Main,其中:例程 Swop被转换为模块例程,主程序通过引用(USE)模块,使用 其中的模块例程及数据。,模块例程作为模块的内部例程,其形式和主程序,外部例程包含的内部例程是一样的,只不过模块例程还可以包含自己的内部例程。模块、主程序、模块例程、内部例程和外部例程的关系如下: 其中: Mod-sub 指模块例程,Int-subs指内部例程,E
18、xt-sub 指外部例程。,Module,Mod-sub,Int-subs,Ext-sub,Int-subs,Main program,Int-subs,Ext-sub,Int-subs,Module,Fortran 90 中,数据块程序单元被模块程序单元替代; 模块不仅可供主程序和外部例程引用,还可供其他模块引用; 主程序、模块、外部例程之间的关系如下图所示:,Program A1,Use module A,Module A,Contains,Module subroutine of funotion,Subroutine B,External subroutine,Call B,Conta
19、ins,End Program A1,Internal subroutine of funotion,主程序、模块、外部例程之间的关系,2 USE语句 模块的引用,一般形式为: USE 模块名 模块的引用,其他形式为: (1)不方便直接使用模块实体名,可在程序中重新命名,引用形式为: USE 模块名,重命名列表(新实体名 =原实体名) 例如:模块YourMod有一个例程或者变量YourPlonk,被重新命名为MyPlonk: USE YourMod, MyPlonk =YourPlonk (2)程序只使用模块中的部分实体,采用如下的引用形式: USE YourMod,ONLY :X,Y 这表示
20、只使用模块 YourMod 中的 X 和 Y,冒号后边的实体可被重命名。 (3)假如要同时引用多个模块,每个模块都要使用单独的 USE语句,并使 USE 语句出现在 IMPLICIT NONE 之前,模块的先后次序无关紧要。,3 PUBLIC 和 PRIVATE 属性 信息隐蔽: PRIVATE,将模块内部使用的实体隐藏,实体访问只限于模块内; 缺省访问属性: PUBLIC,外部程序只使用公共部分的实体。例如: REAL,PRIVATE : X 将实型变量 X 规定为私有的,只能在模块内访问。 PRIVATE X,Swop 同时规定变量 X,例程Swop 为私有的。 PRIVATE PUBLI
21、C Swop 不带实体列表,除Swop外,其余的模块实体都是私有的。,第六节 例程参数,形参(虚参):相对于实参而言 例程在定义时,列表中的参数只是特定数据类型的占位符, 系统不会为他们分配存储单元,称为形参或者虚参; 例程被调用或引用时,列表中的参数被分配一定的存储单 元,并接收外部实参传递进来的值; 例程执行完毕,例程中的参数所占有的存储空间被系统自动 释放,空间中保存的参数值也随之消失。 例程中的局部变量具有同样的性质: 例程执行时“生存”,例程不执行时“消亡”; 如果声明变量具有SAVE 属性,局部变量就是静态变量, 从例程中被调用到程序结束,一直保存有特定的值。,1 参数传递 实参和
22、虚参之间的数据传递(1)引用传递;(2)值传递。 Fortran 中参数(包括数组参数)通常是以引用方式传递,即地址传递; 在实参为常量和表达式的情况下,参数是以值方式传递; 若将变量实参括起来,该实参被转化为表达式,表达式以值方式传递。 例如: CALL Sub(A),B) 其中,(A)是表达式,以值方式传递。 * 引用传递就是将实参的内存地址传递给虚参,例程中虚参的变化会反映到实参中;值传递就是将实参值的拷贝传递给虚参,虚参的变化不会影响实参。* 为确保参数按用户的意愿进行传递,Fortran 90提供了INTENT 属性,例如: SUBROUTINE SUB (X,Y,Z) REAL,I
23、NTENT(IN) : X ! 向例程传入数据,拥有该属性的虚参不允许改变 REAL,INTENT(OUT) : Y ! 向例程传出数据,对应的实参必须是一变量 REAL,INTENT(INOUT) : Z ! 传出/传进数据,对应的实参必须是一个变量 END SUBROUTINE SUB,2 参数类型匹配 PROGRAM Main IMPLICIT NONE EXTERNAL Sub INTEGER : X=1 !对应实参X声明为整型 CALL Sub(X) PRINT* ,X=,X END PROGRAM SUBROUTINE Sub(A) IMPLICIT NONE REAL,INTEN
24、T(INOUT): A !虚参A声明实型,对应实参为X A=A+1 END SUBROUTINE 例3-8中,参数类型不匹配,出错!,3 可选参数 参数列表可以很长,但并不是所有的参数都需要传递,这种 情况下,可以规定部分或全部参数具有可选(OPTIONAL)属 性。 假如参数列表既有必选参数又有可选参数,那么所有的必选 参数必须放在可选参数之前,先必选,后可选。 例如一个外部例程Sub 有6个参数,2个必选,4个是可选,调 用程序中的接口块形式如下: INTERFACE SUBROUTINE Sub( DumU,DumV,DumW,DumX,DumY,DumZ ) REAL DumU,Dum
25、V,DumW,DumX,DumY,DumZ OPTIONAL DumW,DumX,DumY,DumZ END SUBROUTINE END INTERFACE,调用语句 CALL Sub(A,B) !1 CALL Sub(A,B,C,D) !2 CALL Sub(A,B,DumX=D,DumY=E,DumZ=F) !3 对!1,只有必选参数DumU,DumV被传递; 对!2,2个必选参数DumU,DumV 和前面的2个可选参数DumW,DumX被传递; 对!3,2个必选参数DumU,DumV和后边的3个可选参数DumX,DumY,DumZ被传递。 对!1,!2,没有特别说明,参数传递按照列表顺
26、序; 对!3,可选参数可以不按声明的列表次序进行调用;若不按列表次序调用,可选的虚参名必须被引用(需要提供关键字参数列表)。一旦某个可选参数使用了关键字,其后面的可选参数都使用关键字。例如: CALL Sub(A,B,DumX=D,E,F)是错误的 外部例程使用可选参数,需要在调用程序中建立其接口块。,例3-9 可选参数的使用 MODULE Mod IMPLICIT NONE CONTAINS REAL FUNCTION Func(X,A,B,C) ! 计算FUNC(X)=A*X2+B*X+C ! A,B,C 不传入,值为 0 REAL, INTENT(IN) : X ! X值一定要传入 RE
27、AL,OPTIONAL,INTENT(IN) : A,B,C ! A,B,C可以不传入 REAL RA, RB, RC RA=0.0;RB=0.0;RC=0.0 ! 几个简单的赋值语句放在一行 IF ( PRESENT(A) ) RA = A ! 检查可选参数是否存在 IF ( PRESENT(B) ) RB = B IF ( PRESENT(C) ) RC = C Func = RA*X*2 + RB*X + RC END FUNCTION END MODULE PROGRAM Main USE Mod IMPLICIT NONE PRINT*, Func(2.0, C=1.0) ! F(2
28、)=0*22+0*2+1 = 1 PRINT*, Func(2.0, B=1.0, A=2.0) ! F(2)=2*22+1*2+0 = 10 END PROGRAM,例程重载:指不同参数列表的例程被赋予相同的名字,例程调用时,编译器会依据所传递的实参类型,按实参和虚参类型匹配的原则,调用或引用相关的例程。例如子程序 Swop(X,Y),其功能是交换2个数的值。其中的2个参数,可以是实数,也可以是整数。 例3-10 MODULE Mod IMPLICIT NONE INTERFACE Swop !Swop 是调用时使用的例程名 MODULE PROCEDURE SwopReal, SwopIn
29、tegers END INTERFACE,第七节 例程重载,!实型数据交换 CONTAINS SUBROUTINE SwopReals(X,Y) REAL, INTENT(INOUT) : X,Y REAL Temp Temp = X X = Y Y = Temp END SUBROUTINE !整型数据交换 SUBROUTINE SwopIntegers(X,Y) INTEGER, INTENT(INOUT) : X, Y INTEGER, Temp Temp = X X = Y Y = Temp END SUBROUTINE END MODULE !主程序 PROGRAM Main USE
30、 Mod IMPLICIT NONE REAL : A=1.0, B=2.0 INTEGER : I=1,J=2 CALL Swop(A, B) CALL Swop(I, J) PRINT*,A=,A,B=,B PRINT*,I=,I,J=,J END PROGRAM,建立重载例程接口块,以调用的例程名命名接口块: INTERFACE Swop MODULE PROCEDURE SwopReals,SwopIntegers END INTERFACE 体的例程作为外部例程,调用程序过程中建立的接口块为: INTERFACE Swop !相比一般的例程接口块,多了接口名Swop SUBROUTI
31、NE SwopReals(X,Y) REAL,INTENT(INOUT) : X,Y END SUBROUTINE SUBROUTINE SwopIntegers(X,Y) INTEGER,INTENT(INOUT) : X,Y END SUBROUTINE END INTERFACE,递归例程:在一个例程体内出现直接或间接调用例程自身的语句,称该例程为递归例程。递归例程的典型例子是计算n! Fortran 90 支持递归,但必须添加RECURSIVE 关键字,在例程是函数的情况下,还须添加RESULT 子句; 在递归函数体内引用函数自身时,用的是函数名(Factorial),赋值给函数时,用
32、的是结果名(Fact) 。,第八节 递归例程,例3-11 递归函数求n! PROGRAM Main IMPLICIT NONE INTEGER I DO I=1, 10 PRINT*,I,!=, Factorial(I) END DO CONTAINS RECURSIVE FUNCTION Factorial(N) RESULT(Fact) INTEGER Fact, N IF(N = 1)THEN Fact = 1 ELSE Fact = N * Factorial( N-1 ) !引用的是函数名Factorial !赋值采用的是Fact END IF END FUCTION END PRO
33、GRAM,递归算法简单直观,容易编写程序,但递归算法执行效率低,原因是递归包含递推和回归2个过程,系统分别要进行进栈和出栈操作。例如求5!, 递推过程: 5! = 54! 4! = 43! 3! = 32! 2! = 21! 1! = 1 回归过程: 2! = 21! = 2 3! = 32! = 6 4! = 43! = 24 5! = 54! = 120 所以递归程序设计除了要找出递归表达式外,还要确定递归的终止条件(如:1!=1),无限递归没有任何意义;在递归子程序下,同样须添加RECURSIVE 关键字。,PROGRAM Main IMPLICIT NONE INTEGER F, I
34、DO I = 1, 10 CALL Factorial( F, I ) PRINT*,I,!,F END DO CONTAINS RECURSIVE SUBROUTINE Factorial( F, N ) INTEGER F, N IF (N = 1) THEN F = 1 ELSE CALL Factorial( F,N-1) F = N * F !该递归子程序的关键,将赋值语句 F = N * F 置于递归调用语句之后 END IF END SUBROUTINE END PROGRAM,小 结 (1)模块化程序的设计思想:将大的程序分解为若干个功能单一的例程。例程可以是包含在程序单元中的
35、内部例程,也可是作为独立程序单元使用的外部例程。Fortran90程序单元包括主程序、外部例程和模块。其中,主程序和外部例程包含的内部例程不能再包含内部例程,而模块中的模块例程允许包含其内部例程,即例程嵌套。 (2)若外部接口信息较简单,可以通过EXTERNAL关键字将例程声明为外部的,以防止调用程序使用和外部例程同名的标准例程;若外部接口信息复杂或某些特殊情况下,必须建立接口块,使编译器可以产生正确的调用。编译器自动为标准例程、内部例程和模块例程提供显式接口。 (3)模块通常含有全局变量和通用例程,专供其他程序单元使用。在通过USE语句引用模块时,可只使用其中的部分实体,还可为模块实体重命名
36、。模块中实体的缺省访问属性为PUBLIC,PRIVATE属性则将实体访问属性限定在模块内。,(4)实参和虚参间的数据传递必须类型匹配。实参变量以引用方式传递,常量和表达式则以值方式传递,编程中应通过INTENT属性明确规定参数的传递方式。Fortran 90 允许声明可选参数,允许以关键字形式使用可选参数。 具有可选参数的外部例程,使用时需在调用程序中建立其接口块。 (5)重载例程通过有名接口块来规定,重载的具体例程可以是模块例程,也可以是 外部例程,但要求规定不同的接口体。 (6) Fortran 90支持递归,允许例程直接或间接调用自身。但前提是声明例程时要添加RECURSIVE 关键字,在函数例程情况下,还要添加RESULT子句。,