图5.7两层函数嵌套调用示意图函数的嵌套调用即一个函数在被调用的过程中又调用了另外的一个函数。函数fac和powers的返回值类型均被设计为double型,其主要目的是为了避免n!x:0.5//0.5从键盘输入的数据0.50 powers of e=1.648721//程序执行结果......
2023-11-20
一个函数直接或间接地调用自己,称为函数的递归调用。函数的递归调用可以看成是一种特殊的函数嵌套调用,它与一般嵌套调用相比较有两个不同的特点:一是递归调用中每次嵌套调用的函数都是该函数本身;二是递归调用不会无限制进行下去,即这种特殊的自己对自己的嵌套调用总会在某种条件下结束。
递归调用在执行时,每一次都意味着本次的函数体并没有执行完毕。所以函数递归调用的实现必须依靠系统提供一个特殊部件(堆栈)存放未完成的操作,以保证当递归调用结束回溯时不会丢失任何应该执行而没有执行的操作。计算机系统的堆栈是一段先进后出(FILO)的存储区域,系统在递归调用时将在递归过程中应该执行而未执行的操作依次从栈底开始存放,当递归结束回溯时再依存放时相反的顺序将它们从堆栈中取出来执行,在压栈和出栈操作中,系统使用堆栈指针指示出应该存入和取出数据的位置。为了理解函数递归调用的特性,参照例5.9的程序讨论函数递归调用的执行过程,为了讨论方便为程序加上行号。
【例5.9】 函数递归调用示例(使用递归调的方法反向输出字符串)。
上面程序执行时,若输入数据为字符串:abc#,则函数reverse的第一次调用时,在程序的第14行读入字符'a'到字符变量ch中,由于ch=='#'的条件不满足,所以程序应该执行由18~21行构成的复合语句。但在执行由18~21行构成的复合语句时,第19行程序要执行递归调用语句reverse();,因而第20行所示的C语句putchar(ch);就是在该次函数调用过程中应该执行而没有执行的语句,系统将该语句相关的代码压入堆栈保存起来,如图5.8(b)所示,其中的top表示堆栈指针(下同)。
在函数reverse第二次被调用时(注意此时第一次函数调用并未结束),读入字符数据'b'到字符变量ch中,同样由于ch=='#'的条件不满足,程序应该执行由18~21行构成的复合语句。在第19行要执行递归调用语句reverse();,因而第20行所示的C语句putchar(ch);仍然是应该执行而没有执行的语句,系统将执行该语句的代码压入堆栈保存起来,如图5.8(c)所示。同样在函数reverse第三次被调用时,由于ch=='#'的条件不满足仍然会执行上面相似的过程,执行情况如图5.8(d)所示。
图5.8 递归调用时系统堆栈数据的变化示意图
当第四次调用函数reverse时,读入字符变量ch的字符为'#',此时第15行中的条件ch=='#'满足,程序执行第16行输出字符数据'#'。输入字符数据'#'后,如果静态的看待上面显示的例5.9程序源代码,程序执行应该结束了(注意此时最容易出现错误)。但注意函数reverse的第一次、第二次和第三次调用均未完成,所以程序应该将保存在堆栈中应该执行而未执行的程序代码取出执行。由于堆栈先进后出的特性,所以取出并执行堆栈中代码的顺序与其压入堆栈的顺序相反,在例5.9中首先取出并执行的是函数reverse第三次调用时压入的代码,然后是第二次的代码,最后是第一次的代码,这种回溯的过程一直到取出堆栈中压入的所有代码为止,回溯过程中堆栈的变化情况如图5.8(e)至图5.8(g)所示。所以程序执行时输入数据为字符串:abc#,则输出数据为字符串:#cba。
在对例5.9程序的分析中,由于分析了系统堆栈的行为,过程显得比较复杂。但在对递归函数调用进行理解或者在阅读分析含有递归调用函数的C程序时,没有必要过分地追求系统堆栈变化的细节,只要掌握在函数递归的过程中需要将应该执行而未执行的程序代码依次保留下来,当递归结束程序回溯时将保留下来的程序代码用相反的次序依次执行一遍即可。
【例5.10】 编写程序使用递归方式求n!。
图5.9 函数递归调用过程示意图
上面程序运行时首先输入欲求阶乘的整数(本例中以整数5为例),如图5.9所示,程序在运行时将求fac(5)分解为求5*fac(4)、将求fac(4)分解为求4*fac(3)、将求fac(3)分解为求3*fac(2)、将求fac(2)分解为求2*fac(1)、得到fac(1)等于1,然后程序进入递归回溯过程,由fac(1)等于1求得fac(2)等于2、由fac(2)等于2求得fac(3)等于6、由fac(3)等于6求得fac(4)等于24、由fac(4)等于24求得fac(5)等于120,函数递归调用结束。
递归函数的调用,使得程序的执行常常有非常复杂的流程,但在实际设计递归函数时,我们可以忽略系统的具体执行过程,将重点放在分析递归公式和递归终止条件上面,只要算法和递推公式正确,结论一定是正确的,不必陷入分析执行的复杂流程中。
递归的实质是一种简化复杂问题求解的方法,它将问题逐步简化直至趋于已知条件。在简化的过程中必须保证问题的性质不发生变化,即在简化的过程中必须保证两点:一是问题简化后具有同样的形式;二是问题简化后必须趋于比原问题简单一些。具体使用递归技术时,必须能够将问题简化分解为递归方程(问题的形式)和递归结束条件(最简单的解)两个部分。如例5.10求n的阶乘问题,分解出的递归方程为:n*(n-1)!;递归结束条件是:当n<=1时,阶乘值为1。
在程序结构上,总是用分支结构来描述递归过程,递归函数的一般结构如下:
if递归结束条件成立
return已知结果;
else
将问题转化为同性质的较简单子问题;(www.chuimin.cn)
以递归方式求解子问题(递归方程);
【例5.11】 汉诺塔(Tower of Hanoi)问题。有A、B、C三根杆,最左边杆上自下而上、由大到小顺序呈塔形串有64个金盘。现要把左边A杆上的金盘借助C杆全部移到中间B杆上,条件是一次只能移动一个盘,且任何时候都不允许大盘压在小盘的上面。
问题分析:通过计算可以得出,64个盘的移动次数为:264-1=18,466,744,073,709,511,615次。采用递归方法求解该问题时,可以将问题movetower(n,one,three,two)分解为以下3个子任务:
①以B杆过渡,将上面的1到n-1号盘从A移动C,记为:
movetower(n-1,one,three,two);
②将A杆的第n号盘移动到B杆;
③以A杆过渡,将1到n-1号盘从C移动B,记为:
movetower(n-1,three,two,one);
子任务②只需要移动一次就可以实现,子任务①和③与原问题相比,除3个杆位发生变化以外,对应的盘子数也减少了一个,这是向着问题解决的方面发展了一步。反复进行上述类似的分解过程,以同样的方式移动n-1个圆盘,n-2个圆盘……递归将终止于最后移动一个盘子。
设计一个递归函数movetower(n,one,two,three)来实现把n个圆盘从one轴借助three轴移动到two轴,参数one,two,three表示A、B、C3个杆的位置。
程序执行时,输入需要移动的金盘个数,通过程序执行输出移动金盘的方案。
有关C语言程序设计基础(第3版)的文章
图5.7两层函数嵌套调用示意图函数的嵌套调用即一个函数在被调用的过程中又调用了另外的一个函数。函数fac和powers的返回值类型均被设计为double型,其主要目的是为了避免n!x:0.5//0.5从键盘输入的数据0.50 powers of e=1.648721//程序执行结果......
2023-11-20
在C语言中,以函数首部声明函数的形式称为函数原型。使用函数原型是C语言的一个重要特征,主要作用是利用它在程序的编译阶段对调用函数的合法性进行全面检查。......
2023-11-18
C语言中所有函数的定义都是平行的,也就是说,不能在函数定义中再定义其他函数。但是C语言允许在函数定义中再调用其他函数,这就是函数的嵌套调用。本例可编写两个函数,一个是用来计算平方值的函数f1(),另一个是用来计算阶乘值的函数f2()。图5.4函数的嵌套调用函数的嵌套调用使程序形成了一种自顶向下树型结构,如图5.5所示。一个C语言程序通常不是由少数几个大函数组成,而是由许许多多小函数组成。......
2023-11-18
针对不同的实际应用,数据排序方法有很多种。本节介绍两种常用排序方法的基本思想和实现方法,帮助读者初步理解排序方法的计算机解决思路。②不考虑已排好序的数据,将剩下的数据作为待排序列。编写程序实现冒泡排序算法,对随机生成的10个3位整数按升序进行排序并输出。......
2023-11-20
在C 语言程序中,是通过对函数的调用来执行函数体,其过程与子程序调用相似。在调用函数时,大多数情况下主调函数和被调函数之间有数据传递。图5.2程序运行结果函数调用在程序中起一个表达式或者语句的作用。在调用函数时,有3 种不同的方式。这就要求该函数必须是有返回值的。getchar 函数调用作为表达式使用,即赋值表达式的右操作。......
2023-10-21
C语言把括号、赋值、强制类型转换等都作为运算符处理,灵活使用各种运算符可以实现在其他高级语言中难以实现的运算,功能强大。另外,C语言还把括号、下标、强制类型转换、取地址等都作为运算符处理,并通过与算数运算符的组合达到不同的目的,从而使程序更加简洁明了。C语言具有超强的可移植性。总之,C语言在运算符方面是比较容易让人混淆的。......
2023-11-18
表1.4scanf()附加说明字符注意:“*”符:用以表示该输入项读入后不赋予相应的变量,即跳过该输入值。例1.4输入输出数据类型控制。现将改动程序如下:则运行结果为:input a long integer12345678901234567890当输入数据改为长整型后,输入输出数据相同。......
2023-11-18
例如,有如下所示的C语句序列:则结构体指针变量p1指向结构体数组元素a[2],其关系如图10.2所示。此时应该注意到被指针变量p1指向的结构体数组元素本身是不能作为整体操作的,所以*p1也不能作为整体操作。......
2023-11-20
相关推荐