5.1.5 函数的递归调用

在函数调用中,通常我们都是在一个函数中调用另外一个函数,以此来完成其中的某部分功能。例如,我们在main()主函数中调用PowerSum()函数来计算两个数的平方和,而在PowerSum()函数中,又调用Power()函数和Add()函数来计算每个数的平方并将两个平方加和起来成为最终的结果。除此之外,在C++中还存在另外一种特殊的函数调用方式,那就是在一个函数内部调用它自己本身,这种方式也被称为函数的递归调用。

函数的递归调用,实际上是实现函数的一种特殊方式。当递归函数被调用的时候,会产生一个自己调用自己的循环,这个循环会不断地递归进行下去,直到最后一次函数调用在特殊条件下,也就是满足了递归的终止条件,不再继续调用自身而是返回某个具体的结果数据。这时,所有调用这个函数的上层函数会依次返回,直到我们最初对这个函数的调用返回,获得其结果数据。虽然函数的递归调用每次调用的都是自己,但是每次递归调用的条件,也即是函数参数,往往有所不同。正是调用条件的变化,才有可能使函数满足终止条件并返回一个具体的结果数据,不再继续递归地调用自身,这也即是递归调用的终点。

函数的递归调用虽然形式上比较复杂,但是它在处理那些可以把一个大问题分解成一个已知的结果与另一个类似的小问题,需要重复多次做相似的事情才能最终解决的问题时,因为函数的递归调用本身所表达的意义就是循环往复地做同一件事情,所以在处理这类问题上有着天然的优势。例如,我们要统计某个字符在目标字符串中出现的次数。通常,我们的思路是用for循环遍历整个字符数组,然后逐个字符地进行匹配统计。而如果采用递归函数的思路来解决这个问题,那么整个统计过程就变为:从目标字符串的开始位置查找这个字符,如果找到,那么字符出现的次数就成了已经找到的这一次加上在剩下的字符串中出现的次数,在程序中我们可以用“1 + CountChar(pos+1, c)”来表示,其中“1”表示已经找到的字符出现一次,而“CountChar(pos+1, c)”则代表了字符在剩下的字符串中出现的次数,加起来刚好就是字符在整个字符串中出现的次数。这里的“CountChar(pos+1, c)”就是在变更开始条件后对CountChar()函数的递归调用,进行第二次查找与统计。第二次查找也会进行类似的查找统计过程,如果找到则会第三次调用CountChar()函数继续向后继续查找统计。这个过程会不断地持续进行下去,直到最后满足递归的终止条件——查找到了字符串的结尾,再也找不到这个字符——为止。在这个过程中,有需要循环往复执行的相同动作——从字符串开始位置查找目标字符;有不同的开始条件——在字符串的不同位置开始查找;有终止条件——在字符串中再也找不到目标字符。有了这三个特征,我们就可以用函数的递归调用更轻松而自然地解决这个问题:

// countchar.cpp 统计一个字符串中某个字符出现的次数
#include <iostream>
#include <cstring> // 引入字符查找函数strchr()所在的头文件 using namespace std; // 用函数的递归调用实现统计字符在字符串中出现的次数
int CountChar(const char* str,const char c)
{
// 从字符串str的开始位置查找字符c
char* pos = strchr(str,c); // 如果strchr()函数的返回值为nullptr,则意味着
// 在字符串中再也找不到目标字符,递归的终止条件得到满足
// 则结束函数的递归调用,直接返回本次的查找结果0
if(nullptr == pos)
{
return ;
} // 如果没有达到终止条件,则将本次查找结果1统计在内,
// 并在新的开始位置pos + 1开始下一次查找,实现函数的递归调用
return + CountChar(pos + ,c);
} int main()
{
// 字符串
char str[] = "Thought is a seed";
char c = 'h'; // 目标字符
// 调用CountChar()函数进行统计
int nCount = CountChar(str,c);
// 输出结果
cout<<"字符\'"<<c<<"\'在\""<<str<<"\"中出现了"
<<nCount<<"次"<<endl; return ;
}

在执行的过程中,当CountChar()在主函数中第一次被调用时,第一个参数str指向的字符串是“Thought is a seed”,这时进入CountChar()函数执行,strchr()函数会在其中找到字符‘h’出现的位置并保存到字符指针pos中,此时尚不满足终止条件(nullprt == pos), 则执行“return 1 + CountChar(pos+1,c)”,将本次查找结果统计在内,并变更递归的开始条件为“pos+1”,让第二次递归调用CountChar()函数时参数str指向的字符串变为“ought is a seed”。在第二次进入CountChar()函数执行时,strchr()函数会找到字符‘h’第二次出现的位置,递归的终止条件依然无法得到满足,则继续将本次查找结果统计在内并修改开始条件,将CountChar()函数的str参数指向“t is a seed”,开始第三次递归调用。在第三次进入CountChar()函数执行时,strchr()函数在剩下的字符串中再也找不到目标字符,递归的终止条件得到满足,函数直接返回本次的查找统计结果0(return 0;),不再继续向下递归调用CountChar()函数,然后逐层向上返回,最终结束整个函数递归调用的过程,得到最终结果2,也就是目标字符在字符串中出现的次数。整个过程如下图5-8所示。

图5-8 CountChar()函数的递归调用过程

函数的递归调用,其实质就是将一个大问题不断地分解成多个相似的小问题,然后通过不断地细分,直到小问题被解决,才最终解决最开始的大问题。例如在这个例子中,我们开始的大问题是统计字符串中的目标字符的个数,然后这个大问题被分解为当前已经找到的目标字符数1和剩余字符串中的目标字符数CountChar(pos+1,c),而我们要计算剩余字符串中的目标字符数,又可以采用同样的策略进一步细分,直至剩余字符串中没有目标字符,无法继续细分为止。从这里我们也可以看到,函数的递归调用实际上是一个循环过程,我们必须确保函数能够达到它的递归终止条件,结束递归。例如,我们这里不断地调整查找的开始位置,让查找到最后再也无法找到目标字符而满足终止条件。否则,函数会无限地递归调用下去,最终形成一个无限循环而永远无法获得结果。这一点是我们在设计递归函数时尤其需要注意的。

函数的递归调用,是通过在一个函数中循环往复地调用它自身来完成的,从本质上讲,函数的递归调用其实是一种特殊形式的循环。所以,我们也可以将一个函数的递归调用改用循环结构来实现。例如,上面的CountChar()函数可以用循环结构改写为:

// 用循环结构实现统计字符在字符串中出现的次数
int CountChar(const char* str,const char c)
{
int nTotal = ; // 记录字符出现次数
// 在字符串中查找字符,并对结果进行判断
// 如果strchr()返回nullptr,则表示查找完毕,循环结束
while(nullptr != (str = strchr(str,c)))
{
++nTotal; // 将找到的字符统计在内
++str; // 字符串往后移动,开始下一次循环
} return nTotal;
}

这里我们不禁要问,既然函数的递归调用可以用循环结构来实现,而函数的递归调用又涉及到函数调用时的那些传递参数保护现场的幕后工作,性能比较低下,那么我们为什么还要使用函数的递归调用而不是直接使用效率更高的循环结构来解决问题呢?这是因为,面对某些特殊问题,我们很难用循环结构来解决。比如,从一个数组中找出连续和值最大的数据序列,如果采用循环结构,我们几乎无从下手,即使最后解决了但性能也是十分低下。而恰好这种问题又可以细分成多个类似的小问题,比如这里我们可以将数组分成左右两部分,那么和值最大的数据序列要么在左边部分,要么在右边部分,要么跨越两个部分。这样,这个问题就细分成了寻找左边部分、右边部分和跨越左右部分的和值最大序列的三个相似的小问题。而这三个小问题又可以进一步细化,直至最后可以轻松解决的最小问题。在这种情况下使用函数的递归调用来解决问题,更加符合我们人类的思考方式,问题解决起来更加容易,同时其性能也会优于循环结构的实现,做到了“又好又快”。解决这类可以不断细分的特殊问题,就是函数递归调用的用武之地。

你好,C++(27)在一个函数内部调用它自己本身 5.1.5 函数的递归调用的更多相关文章

  1. 你好,C++(26)如何与函数内部进行数据交换?5.1.3 函数参数的传递

    5.1.3  函数参数的传递 我们知道,函数是用来完成某个功能的相对独立的一段代码.函数在完成这个功能的时候,往往需要外部数据的支持,这时就需要在调用这个函数时向它传递所需要的数据它才能完成这个功能获 ...

  2. 二项分布。计算binomial(100,50,0.25)将会产生的递归调用次数(算法第四版1.1.27)

    算法第四版35页问题1.1.27,估计用一下代码计算binomial(100,50,0.25)将会产生的递归调用次数: public static double binomial(int n,int ...

  3. 作用域链和函数内部this指向问题以及bind、call、apply方法

    作用域链和函数内部this指向问题以及bind.call.apply方法 作用域链 作用域是相对于变量而言的, 其意义就在与查找变量(确定变量的来处, 变量是否可以访问到, 确定变量在当前位置是否可以 ...

  4. Python第七天 函数 函数参数 函数里的变量 函数返回值 多类型传值 函数递归调用 匿名函数 内置函数

    Python第七天   函数  函数参数   函数里的变量   函数返回值  多类型传值     函数递归调用   匿名函数   内置函数 目录 Pycharm使用技巧(转载) Python第一天   ...

  5. js闭包(函数内部嵌套一个匿名函数:这个匿名函数可将所在函数的局部变量常驻内存)

    js闭包(函数内部嵌套一个匿名函数:这个匿名函数可将所在函数的局部变量常驻内存) 一.总结 1.闭包:就是在一个函数内部嵌套一个匿名函数,这个匿名函数可以访问这个函数的变量. 二.要点 闭包 闭包的相 ...

  6. javascript知识点总结----函数内部属性

    在函数内部,有两个特殊的对象:argumengs和this 1.函数的参数 ECMAScript函数不介意传递进来多少个参数,也不在乎传进来的参数是什么数据类型,也就是说:你定义的函数只接收2个参数, ...

  7. Javascript高级程序设计——函数内部属性与函数属性

    函数内部属性 函数内部有两个特殊的属性arguments和this.其中,arguments是类数组对象,包含传入函数中的所有值,这个arguments还有一个属性:callee,这个属性是一个指针, ...

  8. c++函数内部声明函数,在函数外面实现函数是可以的

    这个具体有什么用我也不大清楚,只知道可以这样 #include <iostream> //#include "header1.h" using namespace st ...

  9. javascript——函数内部属性

    <script type="text/javascript"> //在函数内部有两个特殊的属性:arguments 和 this.arguments是一个类数组对象,包 ...

随机推荐

  1. 也谈 Python 的中文编码处理

    最近业务中需要用 Python 写一些脚本.尽管脚本的交互只是命令行 + 日志输出,但是为了让界面友好些,我还是决定用中文输出日志信息. 很快,我就遇到了异常: UnicodeEncodeError: ...

  2. Mifare S50与Mifare S70

    转自http://blog.sina.com.cn/s/blog_9ed067ad0100zyjx.html Mifare S50和Mifare S70又常被称为Mifare Standard.Mif ...

  3. sql server 2008 case when

    CASE WHEN的两种格式 1.简单Case函数 CASE sex WHEN '1' THEN '男' WHEN '2' THEN '女' ELSE '其他' END 2.Case搜索函数 CASE ...

  4. C获取本地时间的localtime函数

    最近有朋友问如下问题: #include <stdio.h>#include <stdlib.h>#include <iconv.h>#include <ti ...

  5. html5中使用标签支持视频播放

    <!--定义视频--> <!-- <video src="E:/ext-4.2.1.883/learnHtml5/Wildlife.wmv" control ...

  6. C#编译时出现“不安全代码只会在使用 /unsafe 编译的情况下出现”错误的解决

    原因是:在编译的代码里面有不安全类型unsafe方法或类!解决方法:将项目属性页中生成下的“允许不安全代码”复选框打上对勾即可,方法如下:项目属性对话框->生成->允许不安全代码块 选中即 ...

  7. 【转】silverlight 跨域访问

    作者:MIDI  来源:博客园  发布时间:2010-01-01 17:39  阅读:204 次  原文链接   [收藏]    在 Silverlight 使用 WebService .WCF.We ...

  8. Java8 时间 API

    前言 Java8 中最为人津津乐道的新改变恐怕当属函数式 API 的加入.但实际上,Java8 所加入的新功能远不止这个. 本文将基于<Java SE8 for the Really Impat ...

  9. 通过百度获取IP地址对应的经纬度

    /** * 获取指定IP对应的经纬度(为空返回当前机器经纬度) *  * @param ip * @return */ public static String[] getIPXY(String ip ...

  10. Jenkins,Maven及TestNG在自动化测试的应用(转)

    转自:http://qa.blog.163.com/blog/static/190147002201581634549893/ 希望实现的场景:Jenkins中的Job可针对不同浏览器,不同环境,运行 ...