一、过程
1、过程定义由过程首部和过程体组成,其形式如下:
procedure 过程名(形式参数表); { 过程首部 }
局部变量说明;
begin
执行语句;
…… 过程体
end;
例1:定义过程求n!。
procedure jc(n:integer);
var k:integer;
begin { 变量t在主程序中说明 }
t:=1; for k:=2 to n do t:=t*k; { 最后由t将n!值返回调用程序 }
end;
过程首部,以保留字procedure开头。圆括号内为形参表。无参数时,形参表可省略。定义的过程名不能再做其它变量名、数组名、过程名等。
形参有值形参和变量形参。如:procedure sub(x,y:integer; var k:real);
其中,x,y为值形参,k为变量形参。
除函数首部和过程首部的句法略有差别外,函数体和过程体完全相同。函数返回一个函数值,过程则能完成一系列各种操作。函数的调用方式出现在表达式中,而过程调用是一句独立的语句。与函数不同,不能给过程名赋值。
二、变量及其作用域
1.全程变量的作用域
全程变量是指在程序开头的说明部分定义和说明的量。它的作用域分为两种情况:
(1)在全程变量和局部变量不同名时,其作用域是整个程序。
(2)在全程变量和局部变量同名时,全程变量的作用域不包含同名局部变量的作用域。
2.局部变量的作用域
凡是在子程序内部使用的变量,必须在子程序中加入说明。这种在子程序内部说明的变量称为局部变量。局部变量的作用域是其所在的子程序。形式参数也只能在子程序中有效。因此也属于局部变量。局部变量的作用域分为两种情况:
(1)当外层过程的局部变量名和嵌套过程中的局部变量不同名时,外层过程的局部变量作用域包含嵌套过程。
(2)当外层过程的局部变量名和嵌套过程内的局部变量名同名时,外层局部变量名的作用域不包含此过程。
引入局部变量可以节省内存空间,且便于结构化程序设计。在某些程序中,为了使变量间不互相干扰,一般采用局部变量。如变量间需某种联系时,则可选择全程变量或形参。
三、参数的调用
函数调用或过程调用的执行步骤分为以下几步:
实参和形参结合→执行函数或过程体→返回调用处继续执行
函数或过程说明的形参表对函数或过程体直接引用的变量进行说明,详细指明这些参数的类别、数据类型要求和参数的个数。函数或过程被调用时必须为它的每个形参提供一个实参,按参数的位置顺序一一对应,每个实参必须满足对应形参的要求。
形参可分为四类:值形参,变量形参、函数形参和过程形参。实参也可分为四类:值实参,变量实参、函数实参和过程实参。
1.值参数
值参数是形式参数表中前面没有var,后有类型说明的参数。
如:function fac(x:integer):integer; 它类似过程和函数的局部变量,仅为过程和函数的执行提供初值而不影响调用时实际参数的值。在调用过程或函数时,值参数所对应的实际参数必须是表达式。实参必须和形参赋值相容。
var a:integer;
procedure s(b:integer); { b为值形参 }
begin b:=b+5; writeln('b=',b) end;
begin
a:=10; s(a); writeln('a=',a)
end.
运行结果:
b=15 { 过程中输出的值形参b的值 }
a=10 { a为实参,并没有因调用过程而改变其值,值形参b的值的改变并不影响相应实参a的值。}
2.变量参数
形式参数表中前面有var后由类型的参数。如果需要子程序向调用程序返回值时,应采用变量参数。变量参数要求它的实参是和它同一类型的变量。因为在子程序执行时,遇到对相应形参的引用式定值,就是对相应实参的引用式定值,即对形参的任何操作就是对实参本身的操作。
var a:integer;
procedure s(var b:integer); { b为变量形参 }
begin b:=b+5; writeln('b=',b) end;
begin
a:=10; s(a); writeln('a=',a)
end.
运行结果:
b=15 { 过程中输出的变量形参b的值 }
a=15 { b将值返回主程序调用处,对变量形参b的引用或赋值就是对相应实参a的引用或赋值。}
例4:定义过程swap,完成变量 a和b的交换。
程序一
var a,b:integer;
procedure swap;
var t:integer; { 通过全程变量将过程中的值传回主程序 }
begin t:=a; a:=b; b:= t end;
begin
a:=10; b:=20; writeln('a=',a, ' b=',b);
swap; writeln('a=',a, ' b=',b);
end.
程序二
var a,b:integer;
procedure swap(var m,n:integer); { 通过变量参数将过程中的值传回主程序 }
var t:integer;
begin t:=m; m:=n; n:=t end;
begin
a:=10; b:=20; writeln('a=',a, ' b=',b);
swap(a,b); writeln('a=',a, ' b=',b);
end.
程序三
var a,b:integer;
procedure swap(m,n:integer); { 本程序不能完成两个变量值的交换,为什么? }
var t:integer;
begin t:=m; m:=n; n:= t end;
begin
a:=10; b:=20; writeln('a=',a, ' b=',b);
swap(a,b); writeln('a=',a, ' b=',b);
end.
m,n为值形数,它类似局部变量,仅为过程和函数提供初值而不影响调用时实参的值。
例5:设计一个过程,将数组中的元素从大到小排列。
type atype= array[1..10] of integer;
var a: atype; { a 为数组类型 }
n:integr;
procedure sort(var b: atype); { 变量形参b为数组参数 }
var i,j,k:integer;
begin
for i:=1 to 9 do
for j:=i+1 to 10 do
if b([i]<b[j] then begin k:=b[i]; b[i]:=b[j]; b[j]:=k end
end;
begin { 主程序 }
for i:=1 to 10 do read(a[i]);
sort(a); { 过程调用,实参a为数组类型 }
for i:=1 to 10 do write(a[i], ' ')
end.
过程中对变参b的操作就是对实参a的操作。当形参为数组类型时,必须用类型名进行定义。不能写成: procedure sort(var b:array[1..10] of integer);
四、程序的嵌套和超前引用
1、 程序的嵌套
一个过程或函数调用另一个过程或函数,称为过程与函数的嵌套。嵌套是一层套一层的程序结构,从外向内逐层相包。注意:
①内、外层不能相互交叉,内层必须完全嵌套在外层之中;
②一般情况下,过程或函数内部需要使用的变量应在内部进行定义。外层不能访问内层所定义的变量。
2、 超前引用
有时程序在并列的函数或过程需要相互调用,或前面定义过的过程或函数需调用后面定义函数或过程。这就违反了标识符必须先定义后使用的原则。解决这个问题的方法是适当处理后,超前引用。
例6、设有下列函数和过程:
procedure a(m,n:integer);
{ a的过程体 }
procedure b(x:integer);
{ b的过程体 }
function (s:integer):integer;
{ c的函数体 }
如果a调用b和c,而b和c 又调用a,写出这些函数和过程的说明次序。
解:
procedure b(x:integer); forward; { 过程b的首部提前 }
function c(s:integer):integer; forward; { 函数c的首部提前 }
procedure a(m,n:integer);
{ a的过程体 }
procedure b; { 过程b被提前引用后,此处去掉形参表 }
{ b的过程体 }
function c; { 函数c被提前引用后,此处去掉形参表和函数类型 }
{ c的函数体 }
五、递归调用
如果一个过程或函数又直接或间接调用自身,称为递归。
使用递归求解问题,一般可将一个比较大的问题层层转化为与原问题类似的、规模较小的问题进行求解,最终达到对原问题的解决。
递归的关键在于找出递归方程式和递归终止条件。
例7、求n!。
阶乘的算法可以定义成函数:
n*f(n-1) (n>0)
f(n)=
f(n)=1 (n=0)
使用递归定义,把求n!转化为求(n-1)!* n的问题,而求(n-1)!又转化为求(n-2)!的问题,……,最后归结为求0!的问题,由0!=1有一步步求出1!,2!,……,直到求出n!。当n=0时,f(n)=1,是递归结束的条件。
var n,s:integer;
function fac(a:integer):integer;
begin
if a=0 then fac:=1
else fac:=a*fac(a-1);
end;
begin
readln(n);
s:=fac(n);
write(n, '!= ',s)
end.
例8、求裴波那契数列的第n项。
定义: f(n)=f(n-1)+f(n-2); f(0)=0; f(1)=1;
var n:integer;
function f(n:integer):longint;
begin
case n of
0: f:=0; { 递归结束条件 }
1: f:=1;
else f:=f(n-1)+f(n-2) { 递归调用 }
end
end;
begin
write('n:'); readln(n);
writeln(f(n))
end.
例9、倒序输出以“.”结尾的字符串,如输入abcdef@sina., 输出anis@fedcba。
procedure down;
var c:char;
begin
read(c);
if c<>'.' then down else exit;
write(c)
end;
begin
down; writeln
end.
递归算法一般用于解决三类问题:
⑴ 数据的定义形式是按递归定义的。比如阶乘的定义。这类递归问题往往又可转化成递推算法,递归边界作为递推的边界条件。
⑵ 问题解法按递归算法实现的。例如回溯等。
⑶ 数据的结构形式是按递归定义的。如树的遍历, 图的搜索等。
递归算法解题简洁,但其运行效率较低。在递归调用的过程中系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成栈溢出。一个问题的求解如果既可以用递归算法,也可以用递推算法,则往往用递推算法,因为递推的效率比递归高。
<script src="/adfile/Contact.js" type="text/javascript"></script><script src="/adfile/Contact.js" type="text/javascript"></script>