![C++新经典](https://wfqqreader-1252317822.image.myqcloud.com/cover/184/44510184/b_44510184.jpg)
7.3 函数递归调用精彩演绎
7.3.1 函数递归调用的定义
所谓递归,就是指函数的递归调用。函数的递归调用,也是属于一种函数的嵌套调用,只不过这是一种很特殊的函数嵌套调用。为什么特殊呢?因为它是自己调用自己。自己调用自己导致了很多初学的朋友觉得递归调用很难,很不好理解,实际它并没有那么难以理解。
举个递归调用的例子,有如下自定义函数:
![](https://epubservercos.yuewen.com/F329F6/23721607801994206/epubprivate/OEBPS/Images/Figure-p124_85752.jpg?sign=1738947151-xxVb3G49N1xnOh3eVPFAplKipsJ17iCT-0-213940c7f378ecb85fc7bf5f836aec7e)
在main函数中,调用上述的diguifunc函数,代码如下:
![](https://epubservercos.yuewen.com/F329F6/23721607801994206/epubprivate/OEBPS/Images/Figure-p124_85753.jpg?sign=1738947151-NgU6XlnDVnjvHRbu92W3TaVbJ1eMmHZY-0-f906ffa8d8f8e3fa17961693bb7cf2e6)
把程序执行起来,等几秒钟,可以看到,屏幕不断滚动并输出如下内容:
![](https://epubservercos.yuewen.com/F329F6/23721607801994206/epubprivate/OEBPS/Images/Figure-p124_48339.jpg?sign=1738947151-jI1MxB5GoprbxK6fUpb12MnbTf3FAnKI-0-52563ed3d96a87eb372856cd9cb6357a)
继续等待一会儿(几秒)后,有的Visual Studio编译环境版本直接出现程序报错,弹出异常对话框,有的Visual Studio编译环境版本直接退出了整个程序的执行,等等,各种不正常的现象都会发生,但总归就是一句话,程序执行不正常,出现了各种问题。
报错也好,执行崩溃或者程序退出也罢,根本原因是系统的资源(内存)耗尽了,这是因为不断无限次地调用函数自身所导致。很容易想象,调用函数是要占内存的,每多调用一次函数,系统的内存就要多占用一些,当函数调用完成,从函数中返回时,调用该函数时所占用的内存才能被系统释放掉。以图7.2为例,如果是在第3步定义一个变量(局部变量,后面会讲解这个概念),那么这个变量所占用的内存需要到第11步才能被释放,这也说明了,函数嵌套调用的层次越深,所需要占用的系统内存就越大。还是以7.2节内容所举的范例来进一步阐述函数嵌套调用时的内存分配问题,不过这里要改造一下7.2节的代码。
在函数qtfunc1中,额外定义了一个变量tempvar1,在函数qtfunc2中,额外定义了一个变量tempvar2,这两个变量在执行到相应代码时肯定都是要占用内存。代码如下:
![](https://epubservercos.yuewen.com/F329F6/23721607801994206/epubprivate/OEBPS/Images/Figure-p125_85756.jpg?sign=1738947151-zvScdP0EHvWgPmOM3ofzJYRYJZWzfvXx-0-4b6d85a3655607d2e2307bb6b13513f2)
这里可以设置断点并进行单步调试。不难发现,tempvar1的内存需要在qtfunc1函数执行完之前才释放,而tempvar2的内存需要在qtfunc2函数执行完之前才释放,不光是这些局部变量,在函数调用过程中,可能还会存在函数参数需要临时保存,一些函数调用关系(例如qtfunc1调用的qtfunc2,qtfunc2调用的qtfunc3)也要记录,这样函数调用返回的时候才知道返回到哪个函数里。对于函数嵌套调用来讲,只需要记住,系统会给函数调用分配一些内存来保存提到的这些信息(局部变量、函数参数、函数调用关系等),但分配的内存大小是固定和有限的,一旦超过这个内存大小,程序执行就会出现上述崩溃或者异常退出的情况。
本节开头做了一个函数递归调用(自己调用自己)的代码演示,针对这个调用,可以绘制一个比较形象的图看看函数调用关系,如图7.3所示。
![](https://epubservercos.yuewen.com/F329F6/23721607801994206/epubprivate/OEBPS/Images/Figure-P125_48352.jpg?sign=1738947151-aRWs5pZWYDKg32yc2E0zFDRM3K1e9zpx-0-255dd9b1ecac9f3a01ebee1dbccedb8b)
图7.3 函数递归调用关系图(这种调用导致死循环)
这个例子导致函数自己不断地调用自己(递归调用),造成了调用的死循环。所以递归调用这种自己调用自己的方式必须要有一个出口,这个出口也叫作递归结束条件,有了这个递归结束条件,就能够让这种函数调用结束,可以用图7.4做一个形象一点的说明。
![](https://epubservercos.yuewen.com/F329F6/23721607801994206/epubprivate/OEBPS/Images/Figure-P126_48358.jpg?sign=1738947151-RDDCELrTJyDPy9q2N3bcGURVlTplTuAk-0-785e49cda6ba6b7c62bdf6f1ddcb97dc)
图7.4 函数递归调用关系图(增加出口条件代码使递归函数能够执行结束)
总结一下图7.4:递归调用就是一个函数在它的函数体内部调用它自身。执行递归函数将反复调用其自身,每调用一次就进入新的一层,递归函数必须有结束条件(递归调用的出口),从而引出下一个话题:递归调用的出口。