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

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 0;
    }

    // 如果没有达到终止条件,则将本次查找结果1统计在内,
    // 并在新的开始位置pos + 1开始下一次查找,实现函数的递归调用
    return 1 + CountChar(pos + 1,c);
}

int main()
{
    // 字符串
    char str[] = "Thought is a seed";
    char c = ‘h‘; // 目标字符
    // 调用CountChar()函数进行统计
    int nCount = CountChar(str,c);
    // 输出结果
    cout<<"字符\‘"<<c<<"\‘在\""<<str<<"\"中出现了"
         <<nCount<<"次"<<endl;

    return 0;
}

在执行的过程中,当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 = 0; // 记录字符出现次数
    // 在字符串中查找字符,并对结果进行判断
    // 如果strchr()返回nullptr,则表示查找完毕,循环结束
    while(nullptr != (str = strchr(str,c)))
    {
          ++nTotal; // 将找到的字符统计在内
        ++str; // 字符串往后移动,开始下一次循环
    }

    return nTotal;
}

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

时间: 2024-11-09 04:47:55

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

(转载)你好,C++(27)在一个函数内部调用它自己本身 5.1.5 函数的递归调用

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

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

你好,C++(26)如何与函数内部进行数据交换?5.1.3 函数参数的传递 5.1.3  函数参数的传递 我们知道,函数是用来完成某个功能的相对独立的一段代码.函数在完成这个功能的时候,往往需要外部数据的支持,这时就需要在调用这个函数时向它传递所需要的数据它才能完成这个功能获得结果.例如,当调用一个加法函数时,需要向它传递两个数作为加数和被加数,然后在它内部才能对这两个数进行计算获得加和结果.在定义一个函数的时候,如果这个函数需要跟外部进行数据交换,就需要在函数定义中加入形式参数表,以确定函数的

浅谈递归调用的个人领悟

从大一开始学c,就不是挺理解递归的,最近突然有所体会: 递归调用中递归调用的函数可以把它想象成为一个树的结点,在函数中调用自身就是一个分支,直到出口条件时就是这棵树的叶子结点.叶子的值便是出口返回的值.最后从叶子结点按照你所调用的方法向上返回值,最终结束递归调用.

什么是递归调用

递归调用是一种特殊的嵌套调用,是某个函数调用自己或者是调用其他函数后再次调用自己的,只要函数之间互相调用能产生循环的则一定是递归调用,递归调用一种解决方案,一种是逻辑思想,将一个大工作分为逐渐减小的小工作,比如说一个和尚要搬50块石头,他想,只要先搬走49块,那剩下的一块就能搬完了,然后考虑那49块,只要先搬走48块,那剩下的一块就能搬完了,递归是一种思想,只不过在程序中,就是依靠函数嵌套这个特性来实现了 递归举例: C语言中的递归 计算阶乘的代码 long fact(long n) { if(

方法的创建、重载及递归调用

-----------siwuxie095 1.方法的定义 方法就是一段可重复调用的代码段 定义格式: 「方法的返回值类型为 void 时,不需要返回值,小括号 () 里可以有参数」 2.方法的重载: 方法名称相同,但是参数的类型和个数不同(即参数可辨), 通过传递参数的个数和类型不同来完成不同的功能 调用时系统自动匹配 3.方法的递归调用 递归调用是一种特殊的调用形式,就是方法自己调自己 常用于遍历(如:文件夹等) 如:从 1 加到 100 代码: package com.siwuxie095

[java基础]递归调用

递归调用:通过调用或间接调用程序自身 递归调用最重要的一点是,一定要有个头,要是没有头,一直调用下去,就成了死循环了. 代码示例: /** * 递归调用代码示例<br> * 说明:一个方法,自己直接或间接的调用自己.<br> * @author 冲出地球 * */ public class Recursion { /** * 示例程序:阶乘<br> * 一个数的阶乘,就是从1一直乘到那个数<br> * 示例:2! = 1*2 5! = 1*2*3*4*5&l

js 函数内部创建的setTimeout调用自身函数

js 函数内部创建的计时器setTimeout调用自身函数,实际上就变成了setInterval,操作不当的话会导致计时器不断在创建 在本函数内部清除计时器的时候带上 return : 例如: <script type="text/javascript">     var t=3,timer;     function sleep(){         if(t<=0){             clearTimeout(timer);             ale

关于函数内部调用全局变量的问题

这个问题遇到了两次,两次都在这里跌倒,还好浩哥及时提醒主要原因如下: $db和$dbsel都是由外部引入的sql类的所以在函数内部使用时必须要用global声明,因为函数内部使用的变量都是通过函数的参数进行传递的,在这里没有穿$db和$dbsel. 还有一个问题:就是关于生成二维码的问题: 以前一直用qrcode 类进行自己生成,不知道还可以直接调用网上的免费接口 打开百度->搜索二维码API->点击进去就能看到她的参数个数以及传递格式  这里要注意一下的是 携带的链接地址如果太长的话,要进行

javascript——函数内部属性

1 <script type="text/javascript"> 2 //在函数内部有两个特殊的属性:arguments 和 this.arguments是一个类数组对象,包含传入的所有参数, 3 //但是这个对象还有一个名叫callee的属性,该属性是一个指针,指向拥有这个arguments对象的函数. 4 //请看经典的阶乘函数例子: 5 function Factorial(num) { 6 if (num <= 1) { 7 return 1; 8 } el