一、子程序嵌套
子程序嵌套是指一个子程序中又定义了一个子程序,要求子程序的定义清晰,结构完整,不可以出现定义的交叉。
例一、源程序
Program ex1:
Var
i,j:integer;
procedure p:
function f(i1:integer):integer;
procedure q(var i2:integer);
begin
i2:=i2*10;
writeln(‘i2=’, i2:3);
end; {q}
begin
q(i1);
f:=2*i1;
end; {f}
begin
j:=f(i);
end; (p)
begin
i:=10;
p;
writeln(‘i=’, i:3, ‘j=’, j:3);
end. {main}
执行过程如下图:
执行结果:i2=100
i=10 j=200
在分析程序的执行过程时,我们应该注意全程变量和局部变量、同名、数值参数和变量参数、程序嵌套几个问题。每执行完一个子程序,程序指针就要返回上一层从调用位置继续执行。程序为什么能够正常返回哪?是因为在进入子程序之前,系统将现场,即当前的程序状态,各种局部数据和下一步要运行的位置保存到栈里,称为进栈。栈是一段内存空间,它的特点是先进先出。当子程序执行完毕,将栈中的值返回,称为出栈,就可以继续准确的运行程序了。
例二、定义一个函数F(N,K)=N!/(K!*(N-K)!)
源程序:
二、子程序递归
递归是嵌套中的一种特殊情况。所谓递归是指说明过程或函数时又调用了自己,其中过程或函数直接调用自己称为直接递归;通过调用其他过程或函数变相地调用自己,称为间接递归。
1、间接递归
程序结构1.
Program exa2;
Procedure p1;
Procedure p2;
Begin
p1;
end;{p2}
begin
p2;
end;{p1}
begin
p1;
end.
主程序中调用了过程p1,过程p1中调用了p2,过程p2中调用了p1,这样过程p1通过调用p2就可以达到变相调用自身的目的。
直接递归 1 N=0
计算N的阶乘,数学式如图F(N)=
N*F(N-1) N>0
下面我们就根据数学式定义说明一个递归函数:
function F(N:integer):real;
begin
if N>0 then F:=F*(N-1)
else F:=1;
end;
通过执行过程我们可以看到调用子程序时与嵌套一样,只是反复调用的是自身,局部变量虽然同名但处在不同的层次上表示的含义是不同的。程序执行状态也要不断的进栈、出栈。递归程序应该 注意有终止条件。
1、递归过程的执行过程
program ex3;
procedure p;
var
ch:char;
begin
read(ch);
if ch<>’.’ Then p;
write(ch);
end;
begin
p;
readln;
end.
运行程序从键盘输入’abc.’,执行过程,如下图示:
输出结果为‘.cba’.
通过此程序可以看出局部变量ch在不同的递归层次上取值是不同的,主要利用栈将现场进栈保护了局部变量的值。
有兴趣的同学可以分析下面程序的结果。
program ex4;
var
ch:char;
procedure p;
begin
read(ch);
if ch<>’.’ Then p;
write(ch);
end;
begin
p;
readln;
end.
运行程序从键盘输入’abc.’,屏幕输出’….’。
通过这个例子可以看出程序递归时使用局部变量和全程变量运行结果不同。
三、递推
利用递归可以使程序结构简明,程序的可读性强,但往往程序的执行效率不高。因为递归是从后向前递归到终止条件,再从前向后反推回去。每次递归都要进栈,这样就占用了很多空间,还要出栈又浪费了时间。所以我们对有些问题采取从前向后推得办法,此种方法称为递推。
例三、求阶乘问题
function f(n:integer):real;
var
i:integer; s:real;
Begin
s:=1;
For i:=1 to n do s:=s*i;
f:=s;
End;
有些问题无法转化为递推,例如利用过程递归的程序。为了提高程序效率,我们可以编写为非递归程序,自己利用数组开辟栈空间,自己控制进栈、进栈内容和出栈。此问题我们将会在以后的回溯算法中提及。