1、第六章 函数与过程,第一节 函数 第二节 过程 第三节 递推算法 第四节 递 归,前面我们曾经学习了程序设计中的三种基本控制结构(顺序、分支、循环)。用它们可以组成任何程序。但在应用中,还经常用到子程序结构。通常,在程序设计中,我们会发现一些程序段在程序的不同地方反复出现,此时可以将这些程序段作为相对独立的整体,用一个标识符给它起一个名字,凡是程序中出现该程序段的地方,只要简单地写上其标识符即可。这样的程序段,我们称之为子程序。子程序的使用不仅缩短了程序,节省了内存空间及减少了程序的编译时间,而且有利于结构化程序设计。因为一个复杂的问题总可将其分解成若干个子问题来解决,如果子问题依然很复杂,还
2、可以将它继续分解,直到每个子问题都是一个具有独立任务的模块。这样编制的程序结构清晰,逻辑关系明确,无论是编写、阅读、调试还是修改,都会带来极大的好处。 在一个程序中可以只有主程序而没有子程序(本章以前都是如此),但不能没有主程序,也就是说不能单独执行子程序。 Pascal中子程序有两种形式:函数和过程。,第一节 函数,在此之前,我们曾经介绍并使用了Pascal提供的各种标准函数,如Abs(),Sqr()等等,这些系统提供的函数为我们编写程序提供了很大的方便。比如:求sin(1)+ sin(2)+sin(100)的值。但这些函数只是常用的基本函数,编程时经常需要自定义一些函数。 我们来看看下面一
3、个例子:求:1!2!3!10!?如果要编写程序,我们看到求阶乘的操作要执行10次,只不过每次所求的数不同。我们想:不至于编写10遍求阶乘的程序吧。我们希望有一个求阶乘的函数,假设为js(x),那么我们就可以这样求这道题了。,例6.1 Program ex6_1; var i:integer;sum:longint; BEGINsum:=0;for i:=1 to 10 dosum:=sum+js(i);writeln(sum=,sum); END.现在的问题是:Free PASCAL没提供js(x)这样一个标准函数,这个程序是通不过的。如果是PASCAL的标准函数,我们可以直接调用,如trun
4、c(x),ln(x),sqrt(x)而PASCAL提供给我们的可供直接调用的标准函数不多。没关系,我们编写自己的函数!,函数的定义,在Pascal中,函数也遵循先说明后使用的规则,在程序中,函数的说明放在调用该函数的程序(主程序或其它子程序)的说明部分。函数的结构与主程序的结构很相似。 函数定义的一般格式: Function () : ; /函数首部 ; begin / 以下是函数体; end;,说明: 函数由首部与函数体两部分组成。 函数首部以关键字Function开头,表示子程序是一个函数。 函数名是用户自定义的标识符。 函数的类型也就是函数返回值的类型,所求得的函数值通过函数名传回调用它
5、的程序。可见,函数的作用一般是为了求得一个值。 形式参数简称形参,形参即函数的自变量。自变量的初值来源于函数调用。在函数中,形参一般格式如下: 变量名表1:类型标识符1;变量名表2:类型标识符2; ;变量名表n:类型标识符n。 可见形参表相当于变量说明,对函数自变量进行说明。 当缺省形参表(当然要同时省去一对括号)时,称为无参函数。 函数体与程序体基本相似,由说明部分和执行部分组成。 函数体中的说明部分用来对本函数使用的标号、常量、类型、变量、子程序加以说明,这些量只在本函数内有效,有效范围是局部的。 函数体的执行部分由begin开头,end结束,中间有若干用分号隔开的语句,只是end后应跟分
6、号,不能像程序那样用句号“.“。 在函数体的执行部分,至少应该给函数名赋一次值,以使在函数执行结束后把函数值带回调用程序,新版本支持用exit(返回值)命令带出返回值。,编写一个阶乘的函数,我们给此函数取一个名字js。 Function js(n:integer):longint; Var i:integer;s:longint; begins:=1;for i:=1 to n do s:=s*i;js:=s; end;在本例中,函数名叫js,只有一个integer型的自变量n,函数js属longint型。在本函数中,要用到两个变量i,s,在var后已加以说明。在函数体中,是一个求阶乘的语句,
7、但有一点要注意:虽然n的阶乘的值在s中,但最后必须将此值赋给函数js,此时js不带任何参数。在任何函数中,最后都要把最终结果赋给函数名,因为该函数的结果是靠函数名返回的。在这里,函数的参数n是一个接口参数,说得更明确点是入口参数。如果我们调用函数:js(3),那么在程序里所有有n的地方,n被替代成3来计算。在这里,3就被称为实参。又如:sqrt(4),ln(5),这里4,5叫实参。而ln(x),sqrt(x)中的x,y叫形参。,函数的调用,我们可以在任何与函数值类型兼容的表达式中调用函数,或者说,函数调用只能出现在允许表达式出现的地方,或作为表达式的一个因子。函数调用方式与标准函数的调用方式相
8、同。 函数调用的一般格式:函数名或函数名(实在参数表) 说明: 实在参数简称实参。实参的个数必须与函数说明中形参的个数一致,实参的类型与形参的类型应当一一对应。 调用函数时,一般的,实参必须有确定的值。 函数调用的步骤为:计算实参的值,“赋给“对应的形参; 我们对函数进行了定义,在后面的程序中都可以象标准函数那样直接调用自定义函数了。我们对例6.1用自定义函数进行编写程序。,例6.2 求1!+2!+10!的值。 程序如下: Program ex6_2; var i:integer;sum:longint;Function js(n:integer):longint; var i:integer
9、;s:longint; begins:=1;for i:=1 to n dos:=s*i;js:=s; end;BEGINsum:=0;for i:=1 to 10 dosum:=sum+js(i);writeln(sum=,sum); END.,函数的应用举例,例6.4 利用前面定义的阶乘函数,求5!,9!。程序如下:Program ex6_4;Var a1,a2:longint;Function js(n:integer):longint;Var i:integer;s:longint;begins:=1;for i:=1 to n dos:=s*i;js:=s;end;BEGINa1:=
10、js(5);a2:=js(9);writeln(5!=,a1, ,9!=,a2);END.,在这个程序中,在主程序的BEGIN之前,我们对函数进行了一次说明,在后面的程序中都可以象标准函数那样直接调用自定义函数了。在Function语句中,用的是形参n,在主程序调用中,调用函数是用的实参,如:js(5);程序执行到这儿会自动将5代入前面的Function函数中,用5 取代所有的n,最终将结果赋值给js。所以在a1中一定是5!,a2中是9!。另外,函数不能单独使用,一定要结合主程序才能运行。主程序的变量a1,a2叫全程变量,它们除了主程序外,还可以在函数中出现;在函数说明中用到的变量i,s则是局
11、部变量,只能在函数部分使用,一旦出了函数则失去意义;别外要注意:全程变量和局部变量尽量不要同名。,例6.5 任意输入10组三角形的三边,求其面积。 我们可以定义一个已知三角形三边求其面积的函数,设为area(a1,a2,a3)。 程序如下: Program ex6_5; Var a,b,c,s:real;i:integer; Function area(a1,a2,a3:real):real; var s1,d:real; begind:=(a1+a2+a3)/2;s1:=Sqrt(d*(d-a1)*(d-a2)*(d-a3);area:=s1; end; BEGINfor i:=1 to 1
12、0 dobeginwriteln(input a,b,c);readln(a,b,c);if (a+b=c) or (a+c=b) or(b+c=a)then writeln(data error!)else writeln(s=,area(a,b,c);end; END.,在函数说明中,如果形参的个数不止一个,那么在程序中调用函数的实参个数一定要与形参的个数一致,第一个实参对应第一个形参,第二个实参对应第二个形参次序不能对调。,例6.6 定义一个函数CHECK(N,D),让它返回一个布尔值。如果数字D在整数N的某位中出现则送回TRUE,否则送回FALSE。 例如:CHECK(325719,3
13、)TRUE;CHECK(77829,1)FALSE; Program ex6_6; VAR a,b:integer; Function heck(n,d:integer):boolean; var f:boolean; e:integer; beginf:=false;while (n0) and (not f) dobegine:=n mod 10;n:=n div 10;if e=d then f:=true;end;check:=f; end; BEGINwriteln(input n,d);read(a,b);writeln(check(a,b); END.,例6.7 计算如图多边形的
14、面积。 从图中可以看出,五边形的面积是三个三角形面积之和。,b1,b2,b3,b4,b5,b6,b7,程序如下: Program ex6_7; Var b1,b2,b3,b4,b5,b6,b7,s:real; Function area(a,b,c:real):real; Var P:real; BeginP:=(a+b+c)/2;Area:=sqrt(p*(p-a)*(p-b)*(p-c); End; BEGIN 主程序Write(please input b1,b2,b3,b4,b5,b6,b7:);Readln(b1,b2,b3,b4,b5,b6,b7);S:=area(b1,b5,b6
15、)+area(b2,b6,b7)+area(b3,b4,b7); 三次调用函数areaWriteln(s=,s:10:3); END.,函数课堂练习1.编程找出由键盘任意输入二个整数中的最大数。 2.编程找出由键盘任意输入三个整数中的最大数。 3.求从键盘任意输入两个自然数的最大约数。 4.求从键盘任意输入三个自然数的最大约数。 5.求从键盘任意输入两个自然数的最小公倍数。 6.用函数求1+2+3+n的和(n=100)。,【上机练习6.1】,1.编程求5!+7!+9!+11!的值。 2.编程求C RK = K!/(R!(K-R)!)(K R 0) 3.求正整数2和100之间的完全数。 完全数:
16、因子之和等于它本身的自然数,如6=1+2+3 ; 4.如果一个自然数是素数,且它的数字位置经过对换后仍为素数,则称为绝对素数,例如13。试求出所有二位绝对素数 5.编写程序计算表达式:Y = x2 + SH(x),SH(x)是双曲正弦函数 【提示】在Fp中没有SH()函数,需要由用户自已定义,由数学知识知:SH(x)=( e x e x)/2。,第二节 过程,在Pascal中,自定义过程与自定义函数一样,都需要先定义后调用。函数一般用于求值,而过程一般实现某些操作,两者的本质区别就是在于:函数有一个值返回,主程序中调用时要有一个相应类型的变量来接收这个值,而过程只是完成某些操作,没有返回值,调
17、用时可以当成完成某项功能的一条命令。,过程的说明,过程说明的一般格式为: procedure (); /过程首部 ; begin /以下是过程体end;,说明: 过程首部以关键字procedure开头。 过程名是用户自定义的标识符,只用来标识一个过程,不能代表任何数据,因此不能说明“过程的类型“。 形参表缺省(当然要同时省去一对括号)时,称为无参过程。 形参表的一般格式形式如下:var 变量名1:类型;var 变量名n:类型。其中带var的称为变量形参,不带var的称为形参。在函数中,一般都 是形参,很少用变量形参(但可以使用)。例如,下列形参表中:(x,y : real;n : intege
18、r;var w : real;var k : integer;b : real)x、y、n、b为形参,而w、k为变量形参。调用过程时,通过形参给过程提供原始数据,通过变量形参将值带回调用程序。因此,可以说,形参是过程的输入参数,变量形参是过程的输出参数。有关变量形参,这在后面内容具体叙述。 过程体与程序、函数体类似。与函数体不同的是:函数体的执行部分至少有一个语句给函数名赋值,而过程体的执行部分不能给过程名赋值,因为过程名不能代表任何数据。 过程体的说明部分可以定义只在本过程有效的标号、常量、类型、变量、子程序等。,过程的调用,过程调用是通过一条独立的过程调用语句来实现的,它与函数调用完全不同
19、。过程调用与调用标准过程(如write,read等)的方式相同。调用的一般格式为: 过程名 或 过程名(实在参数表) 说明: 实参的个数、类型必须与形参一一对应。 对应于形参的实参可以是表达式,对应于变量形参的实参只能是变量。 过程调用的步骤为:计算实参的值;将值或变量的“地址“传送给对应的形参;执行过程体;返回调用处。,过程与函数有下列主要区别:,标识符不同。函数的标识符为FUNCTION,过程为:PROCEDURE。 函数在定义时一定要进行函数的类型说明,过程则不进行过程的类型说明。 函数有类型,最终要将函数值传送给函数名;过程无类型,不能给过程名赋值。 函数中一般不用变量形参,用函数名直
20、接返回函数值;而过程如有返回值,则必须用变量形参返回。 调用方式不同。函数的调用出现在表达式中,而过程调用是一个独立的语句。 函数通常是为了求一个函数值,而过程可以得到若干个运算结果,也可用来完成一系列的数据处理,或用来完成与计算无关的各种操作;,例6.8 输出以下一个图形 : * 【分析】我们前面学习可用的二重循环打印出上图形, 现我们设置一个过程打印出N个连续的“*“号。 程序如下: Program ex6_8; var i : integer; Procedure print(n : integer); /该过程打印出连续n 个星号,并换行 var j : integer; beginf
21、or j : =1 to n do write(*);writeln; end; BEGINfor i : =1 to 6 doprint(i); /调用过程,第i行打印i个连续星号 END.,例6.9 使用无参过程,输出由“”组成的矩阵的过程,该矩阵四行五列。Procedure print;var i,j:integer;beginfor i:=1 to 4 dobeginfor j:=1 to 5 dowrite(*);writeln;end;end;该过程就没有参数,直接执行打印一个固定矩阵的任务,而且也没返回值。,例6.10 定义一个求N!的过程。Procedure js(n:inte
22、ger);var s:longint;i:integer;begins:=1;for i:=1 to n dos:=s*i;writeln(n,!=,s);end.在该过程中,它的值的返回形式和函数不一样:函数是由函数名返回,而过程不是由过程名返回的;在过程的首部不用对过程的类型进行说明。,例6.11 定义过程fa求N!。 Program ex6_11 ; var x:integer ; t:real ; Procedure fa(n:integer); var i:integer; begint:=1;for i:=2 to n dot:=t*i; end; BEGINread(x) ;fa
23、(x);writeln( x:5 , t:8) ; END.这里通过全程变量T,将过程中计算结果传递到T变量中。Fa(x)仅仅作为程序中的一条命令被执行。,变量形参,在过程定义的语句中,有个参数表,在参数表中,除了前面我们已用的形参,还有变量形参。变量形参的作用是:它可以作为过程的出口参数。我们可以把过程中求出的结果用变量形参输出到过程外,在过程外面可以调用该参数,因此,该参数是全局变量。其格式上的区别是在变量形参前加上var即可。那么我们现在来求1!2!3!10!?,例6.12 求1!+2!+10!的值。 程序如下: Program ex6_12 ; Var j:integer; s,m:l
24、ongint; Procedure js(n:integer;var m:longint); Var i:integer; beginm:=1;for i:=1 to n dom:=m*i; end; BEGINs:=0;for j:=1 to 10 dobeginjs(j,m);s:=s+m;end;writeln(s=,s); END.,在本例中。我们看到,过程js中用到了变量形参m,在过程中定义为longint类型;而在主程序的变量说明中也得对变量形参m说明为同种类型longint。于是,在过程中和主程序中都可以用该变量了。,形参与变量形参,(1)形参:在函数或过程定义中,没有加VAR
25、说明的参数,在调用函数或过程时,调用程序将实参的值直接传递给形参,起着赋值作用。 (2)变量形参:在函数或过程定义中,加有VAR 说明的参数,在调用函数或过程时,调用程序将实参的变量地址传递给变量形参,因此当过程或函数处理中,改变变量形参的值,则实参的变量值也随之改变。(共享同一个存储单元),例6.13 下列程序中的参数传递 程序如下: Program ex6_13 ; var x,n:integer; procedure chan(x:integer;var y:integer); beginx:=x+5;y:=y+5;writeln(x=,x,y=,y); /语句 end; BEGIN /
26、主程序x:=10; n:=10;writeln(x=,x,n=,n); /语句chan(x,n);writeln(x=,x,n=,n); /语句 END. 运行结果: x=10 n=10 /语句的结果 x=15 y=15 /语句的结果 x=10 n=15 /语句的结果,过程chan中定义了形参x和变量形参y。在调用过程时,形参x接受实参的值10,然后将它加5,但是形参值的改变并不影响主程序中实参的值(数值传递),所以返回主程序后,输出实参x的值仍为10,可见,实参x和形参x是两个不同的变量。y为变量形参,对于变量形参的操作实际上就是对相应实参n的操作(共享存储地址)。y的初值为10,调用过程后
27、,值为15,返回主程序时,值15被带回主程序,故n也为15。,形参和变量形参的区别:1、形参应该强调的是:形参和对应的实参必须一一对应,包括个数和类型。实参和形参之间数据传递是单向的,只能由实参传送给形参,相当赋值运算。一个特殊情况是,当形参是实型变量名时,对应的实参可以是整型表达式。形参作为子程序的局部量,当控制返回程序后,形参的存储单元释放。2、变量形参必须在形参前加关键字var。应该注意的是:与变量形参对应的实参只能是变量名,而不能是表达式。与变量形参对应的实参可以根据需要决定是否事先有值。变量形参与对应的实参的类型必须完全相同。对变量形参,运行时不另外开辟存储单元,而是与对应的实参使用
28、相同的存储单元。也就是说,调用子程序时,是将实参的地址传送给对应的变量形参。当控制返回到调用程序后,变量形参的存储单元不释放,但变量形参本身无定义,即不得再使用。到底是使用形参还是变量形参,应慎重考虑。形参需要另开辟存储空间,而变量形参会带来一些副作用。一般在函数中使用形参,而在过程中才使用变量形参,但也有例外。,例6.14 写出下列两个程序的运行结果。Program ex6_14_1; Program ex6_14_2;var a,b : integer; var a,b : integer;procedure swap(x,y : integer); procedure swap(var
29、x,y : integer) ;var t : integer; var t : integer;begin begint : =x; x : =y; y : =t; t : =x; x : =y; y : =t;end; end;Begin Begina : =1; b : =2; a : =1; b : =2;writeln(a : 3,b : 3); writeln(a : 3,b : 3);swap(a,b); swap(a,b);writeln(a : 3,b : 3); writeln(a : 3,b : 3);End. End. 【分析】 这两个程序唯一的区别是ex6_14_1中
30、将x,y作为形参,而 ex6_14_2中将x,y作为变量形参,因此在ex6_14_2中对x,y的修改实际上是对调用该过程时与它们对应的变量a,b的修改,故最后,a,b的值为2,1。而ex6_14_1中调用swap过程时,只是将a,b的值传递给x,y,之后在过程中的操作与a,b无关。ex6_14_1的运行结果为: ex6_14_2的运行结果为:1 2 1 21 2 2 1,全程变量、局部变量及它们的作用域,全程变量:主程序中被说明的变量。局部变量:在过程或函数中被说明的变量。在程序中,局部变量、全程变量进行存取的适用范围是不一样的,即作用域不一样。局部变量的作用域是它们所在的子程序。因形式参数也
31、只在子程序中有效,因此也属于局部变量。对于局部变量的作用域可以这样理解:当局部变量所在子程序被调用时,局部变量才被分配有效的存储单元;当返回调用程序时,局部变量所占的存储单元就被释放。全程变量的作用域分为两种情况:在全程变量和局部变量不同名时,其作用域是整个程序。在全程变量和局部变量同名时,局部变量屏蔽了全程变量。,例6.15 变量作用范围: Program ex6_15; var x,y : integer; Procedure doit; var x : integer; beginx := 2;writeln(x,y); end; BEGINx := 1; y := 2;writeln(
32、x,y);doit;writeln(x,y); END. 【分析】 程序中x,y是全局变量,但在过程doit中也定义局部变量x,在这种相冲突的情况下,在doit过程中的变量x是过程中所定义的局部变量。 运行结果:1 22 21 2,过程与函数的综合应用,例6.16 任意输入一个整数,将它变成字符串输出。如:输入数34567,打印出字符“34567”。要求用过程的方法实现。 程序如下: Program exp7_16 ; var i,k:integer;s:string; Procedure n_c(n:integer;var s:string); Var l:integer; beginl:=
33、abs(n);s:=;repeats:=char(l mod 10) +ord(0)+s;l:=l div 10;until l=0;if n 0 then s:=-+s; end;,BEGINfor i:=1 to 10 dobeginwriteln(input k);readln(k);n_c(k,s);writeln(the string is:,s);end;END.,例6.17 编程输入十进制整数N(N:-3276732767),请输出它对应的二进制、八进制、十六进制数。 【分析】这是一道进行数制转换的问题,将十进制整数转换成R进制的数,算法是:除R取余,再将余数倒过来写出即是R进制
34、的数。本例是要求把一个十进制数同时转换成二进制、八进制、十六进制数。因此可以设计一个过程同进处理这三种数的进制转换。 程序如下: Program ex6_17; var n : integer; Procedure TurnData(n,a : integer); var x : array116 of integer;i,j,k,h: integer; beginwriteln(n,turn into,a, : );if n0 then write(-); /负数的话,先输出负号再开始转j : =abs(n);k : =0; /用于统计转成a进制数后的总位数repeatk : =k+1; i
35、 : =j mod a;j : =j div a;xk : =iuntil j=0;,for h : =k downto 1 doif xh10 the write(xh)else write(chr(55+xh); /A的序号是65,依次类推writeln; end; BEGINreadln(n);TurnData(n,2); /n转成2进制数TurnData(n,8); /n转成8进制数TurnData(n,16); /n转成16进制数 END.这里的过程TurnData中的参数不需要把什么值返回给主程序,因此设为形参即可。,例6.18 对6到60的偶数验证哥德巴赫猜想:不小于6的偶数可分解成两个素数之和。 【分析】用布尔型函数prime(x)判断x是否是素数,若是, 函数值为真,否则,函数值为假。算法如下所示。 1 t : =6 2 while t60 do 3 t1 : =1; 4 repeat 5 t1 : =t1+2; /找下一个素数a 6 until prime(t1)and prime(t-t1); /直到a,b都是素数 7 writeln(i,=,t1,+,t-t1); 8 t : =t+2; 9 endwhile,