自学C之递归理解

2023-11-12

一、 理解概念

  C语言允许一个函数调用自身,这种过程被称为递归(Recursion)。程序使用递归处理特殊的问题,如阶乘、 Ackermann函数,反序等等。实际上,如果不考虑运行时内存的开消,任何使用赋值语句、if-else和while结构的函 数都可以用递归方式重写。

  “递归”可以跟据字面去理解,“递”就是一级一级递进,“递”可以想象成通过一段台阶走下地下室,“归”则是归来 的意思,就像通个了“递”这些阶级到了地下室之后,又延着所走的台阶返回原点。

你还可以将“递归”想象成“我肚里面有一个我”,如果我肚里有一个我,那么肚里的我肚里还有一个我,这样一路我 下去,我我我我,将会无穷无尽,因而递归需要一个终止的条件,就假设世界上只能同时存在5个我,这样,“我肚里面 有我”就一共只有五个肚,五个我,最后第五个我的肚里不能再有我了。

  “我肚里有我”,这就相当函数里面有本函数,函数处理事务,就相当于我吃饭, 当我吃饭时,我里面的我也要吃 饭,但所有的我入口都是最外面的一张嘴,于是,我吃饭,饭到了肚子里被肚子里的我吃了我下去的饭,肚子里的我的 肚子里的我再把他的饭吃了,这样的情况,只有把最里面的我的肚子喂饱了,外面的我才可以吃到饭。程序的函数也是 一样,它必须要等里面的函数处理完了问题,外面的函数才可以着手处理问题。就好比去地下室拿东西,你必须走完所 有阶梯才到达地下室,然后才可以返回。

  总之,我们记着,只要是递归,就必须要等里面的我(函数)完成了任务,才轮到外面一级的我(函数)做任务。

二、 递归的基本原理

   1. 每一级的函数调用都有自已的变量。

   2. 每一次函数调用都会有一次返回。

   3. 递归函数中,位于递归调用前的语句和各级被调函数具有相同的执行顺序。

   4. 递归函数中,位于递归调用后的语句和各级被调函数的顺序相反。

   5.虽然每一级都有自己的变量,但是函数代码并不会复制。函数代码是一系列的计算机指令,而函数调用就是从头执行这个指令集的一条命令。

   6.递归函数中必须包含可以终止递归的语句。

三、原理验证

#include <stdio.h>
void up_and_down(int);
int main(void)
{   
    up_and_down(1);
    return 0;
}
void up_and_down(int n)
{
    printf("Level %d: n location %p\n", n, &n);
    if(n < 3) 
        up_and_down(n+1);
    printf("Level %d: n location %p\n", n, &n);
}

编译后执行的结果:


Level 1: n location 0x7fffffffe30c 从level1、level2、level3中可以看出n有三个不同的地址,即是说经过三次递归调用,各有不同的n变量。

Level 2: n location 0x7fffffffe2ec 下面的3级level,可以看出3次递归调用返回了三次,验证了每次调用都有返回。

Level 3: n location 0x7fffffffe2cc 第一和第二个printf用来验证递归调用前的函数执行顺序,验证了它和递归调用有相同的执行顺序。

Level 3: n location 0x7fffffffe2cc 第三和第四个printf则说明了递归后的函数是以反序方式来执行的

Level 2: n location 0x7fffffffe2ec

Level 1: n location 0x7fffffffe30c


我们再用GDB跟踪程序:


先用gdb 加载程序,键入start运行后停在main入口处

Temporary breakpoint 2, main () at recur.c:6
6 up_and_down(1);

stepi跟踪程序进入调用函数

(gdb) stepi
0x0000000000400589 6 up_and_down(1);

用backtrace查看内存运行栈,看出函数已压入栈中

(gdb) bt
#0 up_and_down (n=0) at recur.c:11
#1 0x000000000040058e in main () at recur.c:6

往下执行到n=3时,栈中已压入三个函数在main函数之上,从中可以看出,每一次函数调用,就需要向栈中压入数据,因为每次调用都要压栈,所以每次调用的函数的变量都是独立的,又所以每一次调用都会有返回。

(gdb) bt
#0 up_and_down (n=3) at recur.c:13
#1 0x00000000004005d7 in up_and_down (n=2) at recur.c:15
#2 0x00000000004005d7 in up_and_down (n=1) at recur.c:15
#3 0x000000000040058e in main () at recur.c:6

因为栈的特性,后进入栈的函数获得先返回的机会,所以位于递归调用后的语句和各级被调函数的顺序相
反,当n>时函数开始返回,首先返回的是n=3时的函数。
跟上面比较, up_and_down(n=3)的函数已经返回

(gdb) bt
#0 up_and_down (n=2) at recur.c:17
#1 0x00000000004005d7 in up_and_down (n=1) at recur.c:15
#2 0x000000000040058e in main () at recur.c:6

往下执行直到main返回,程序结束。

(gdb) c
Continuing.
Level 2: n location 0x7fffffffe2ec
Level 1: n location 0x7fffffffe30c
[Inferior 1 (process 6893) exited normally]

用GDB调试可以看出,递归如果层数太多,超出了栈的容量就会造成程序死机,这就是递归的缺点;同时,函数调用每次都要压栈处理,递归层数过多就会影响程序的运行速度。

四、递归处理反序

返回值需要反序排列时,递归能有效地精简代码

例如:十进制转换成二进制,用除二取余,倒序排列
意思是:将一个十进制数除以二,得到的商再除以二,依此类推直到商等于一或零时为止,倒取将除得的余数,即换算为二进制数的结果。 例如把52换算成二进制数,计算结果如图:
除二取余
52除以2得到的余数依次为:0、0、1、0、1、1,倒序排列,所以52对应的二进制数就是110100。

用C语言可以这样编码:

#include <stdio.h>

void to_binary(int);

int main(void)
{
    int number;
    printf("Enter a number or 'q' to exit:\n");
    while (scanf("%d", &number) == 1)
    {
        printf("Binary equivalent:");
        to_binary(number);
        putchar('\n');
        printf("Enter a number or 'q' to exit:\n");
    }
    printf("Done!\n");

    return 0;

}

void to_binary(int n) {
    int r;
    r = n%2;
    if (n >= 2)
        to_binary(n/2);
    putchar('0' + r);
    return;
}

上面用递归的方法比用循环的方法要简单很多,你可以用循环的方法重编上面的函数去对比。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

自学C之递归理解 的相关文章

  • 归并排序中的递归:两次递归调用

    private void mergesort int low int high line 1 if low lt high line 2 int middle low high 2 line 3 mergesort low middle l
  • 如何在 javascript 正则表达式中匹配平衡分隔符?

    我原以为这个问题是不可能的 据我所知 Javascript 的正则表达式既没有递归插值 也没有漂亮的 NET 平衡组功能 但问题就在那里 如问题 12 所示正则表达式 alf nu http regex alf nu 匹配平衡对 lt an
  • PHP递归遍历对象树[关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 如何高效生成总和在指定范围内的所有组合(在所有深度)

    假设您有一组值 1 1 1 12 12 16 如何生成总和在预定义范围内的所有可能组合 不重复 min max 例如 这里是 所有深度的 范围在13 and 17 1 12 1 1 12 1 1 1 12 16 1 16 这假设具有相同值的
  • 包装一个采用 std::function 的函数,以便传递采用更多参数的函数

    Problem 我有一个函数double subs std function
  • Javascript 中的深平面多维数组[重复]

    这个问题在这里已经有答案了 我想编写一个可以深度展平给定数组的函数 例如 deepFlatten deepFlatten 1 2 3 1 2 3 deepFlatten 1 2 3 a b c 1 2 3 1 2 3 a b c 1 2 3
  • Fortran 递归分段错误

    我必须设计并实现一个 Fortran 例程来确定方格上簇的大小 并且递归地编写子例程似乎非常方便 然而 每当我的晶格大小超过某个值 大约 200 边 时 子例程就会始终出现段错误 这是我的集群检测例程 RECURSIVE SUBROUTIN
  • 如何在 Twig 中渲染树

    我想渲染一棵深度不确定的树 孩子的孩子的孩子等 我需要递归地循环遍历数组 我怎样才能在 Twig 中做到这一点 我玩过domi27的想法 https stackoverflow com questions 8326482 how to re
  • 变量的多个值介于 0 和数字序言之间

    所以我一直在尝试自学序言 我认为我进展顺利 然而 我有点坚持我正在尝试的这一种方法 toN N A A 等于 0 到 N 1 之间的整数值 按升序生成 所以 toN 5 A 将是 A 0 A 1 A 2 A 3 A 4 我对序言还很陌生 所
  • 如何在 Alloy 中构建递归谓词/函数

    我试图在 Alloy 中生成两组类 例如重构之前的类 重构应用程序后的应用程序和类 假设在第一组中我们有以下类 ALeft gt BLeft gt CLeft Class1 Class2 gt Class3 gt Class4 这意味着 A
  • 生成尾调用操作码

    出于好奇 我尝试使用 C 生成尾部调用操作码 斐波那契数很简单 所以我的 C 示例如下所示 private static void Main string args Console WriteLine Fib int MaxValue 0
  • F# 中的递归对象?

    这段 F 代码 let rec reformat new EventHandler fun gt b TextChanged RemoveHandler reformat b gt ScrollParser rewrite contents
  • Haskell:处理死锁的自引用列表

    GHC 允许永久阻止以下内容是否有任何有用的理由 list 1 tail list 看起来列表迭代器 生成器有点复杂 我们应该能够做一些更有用的事情 Return error Infinitely blocking list Return
  • 如何在分形绘图递归函数中创建延迟

    我正在玩一个分形绘图递归函数 遇到了雄辩的 JavaScript https eloquentjavascript net 我想为每个分支的绘制设置一个延迟 以便在我修改此函数及其参数时可视化分支 递归调用的流程 我用过的方式setTime
  • java正则表达式查找第一个和最后一个字符

    我正在编写一个程序来递归地确定字符串是否为回文 我决定尝试使用正则表达式 但我在理解语法时遇到了一些困难 我需要做的是比较第一个和最后一个字符 看看它们是否相同 我该怎么做呢 Thanks 编辑 我发现这很有帮助 AirSource Ltd
  • 为什么《破解编码面试》这个例子的时间复杂度是O(k c^k)?

    该问题来自 破解编码面试 第 6 版 问题 V1 11 以下代码打印长度为 k 的所有字符串 其中字符 是按排序顺序排列的 它通过生成所有长度的字符串来做到这一点 k 然后检查每个是否已排序 什么是运行时间 package QVI 11 P
  • 如何使用append/3在prolog中递归构建列表?

    我需要了解一些事实的价值 这部分似乎正在发挥作用 fact1 A Val1 fact2 B Val2 A B 但是一旦我尝试附加这些值 Val1 Val2 通过使用append 3谓词到列表 OutList 我只得到一个可能的解决方案 而不
  • 正则表达式引擎如何解析具有递归子模式的正则表达式?

    此正则表达式匹配回文 1 2 我无法理解它是如何工作的 递归何时结束 以及正则表达式何时从递归子模式中断并转到 part Thanks 编辑 抱歉我没有解释 2 and 1 1 指第一个子模式 对其自身 2 反向引用第二个子模式的匹配 即
  • 获取嵌套数组 JS 中对象的所有父对象

    我在使用 vuejs 的项目上遇到问题 我有一个像这样的嵌套对象数组 Data data id 1 parent id null title First folder children id 3 parent id 1 title Firs
  • 我应该对算法使用递归还是记忆化?

    如果我可以选择使用递归或记忆来解决问题 我应该使用哪一个 换句话说 如果它们都是可行的解决方案 因为它们提供了正确的输出并且可以在我正在使用的代码中合理地表达 那么我什么时候会使用其中一个而不是另一个 它们并不相互排斥 您可以同时使用它们

随机推荐

  • Android databinding的接入使用与详解(一)

    一 介绍 DataBinding 是Google Android组件框架 管理view和data之间进行绑定 DataBinding主要管理数个布局文件 这样我们就不用去实例化layout的view 直接通过DataBindingUitl来
  • 软件测试须知基于PostMan的接口自动化测试

    临近年底 公司任务也不是很多 趁这个机会老大让我研究了一下PostMan的脚本自动化测试 作为一个前端开发 说实话 对于PostMan的操作 仅仅限于新建请求 gt 填写url地址和参数 gt send发送 然后看看返回值而已 事实上 Po
  • Python读取CSV文件,数值精度丢失

    Excel保存为csv以后 大数值的列 会把转换为科学计数法 而且后边几位都会被转为0 搞了很多方法 最后直接安装 openpyxl 组件 和 pandas 读取Excel文件就行了 data pd read excel C work 20
  • Search:Vscode 如何自定义Python代码高亮

    参考 白月黑羽的python教程网站 不太习惯Dark主题 然而又没有找到合适的Light Theme IDLE Shell的高亮倒是不错 可是VScode 的插件里没搜到 上网搜索 感谢B站up白月黑羽的分享 最终使用 Atom One
  • Unity3d FPS游戏之武器切换

    U3D武器系统切枪 多种武器切换教程 多种武器切换教程 我们要通过鼠标来实现切枪效果 我们要有几种思路 1 通过值来索引对应武器数组下标的值 然后生成 在切换武器的时候 先销毁当前的武器 在生成新武器 2 直接先全部生成 状态全部不激活 通
  • 144项大神级ppt制作技术

    1 两幅图片同时动作 PowerPoint的动画效果比较多 但图片只能一幅一幅地动作 如果你有两幅图片要一左一右或一上一下地向中间同时动作 可就麻烦了 其实办法还是有的 先安置好两幅图片的位置 选中它们 将之组合起来 成为 一张图片 接下来
  • ubuntu虚拟机变成只读文件系统的解决方法

    因非正常关机可能导致文件系统变为只读 1 先在windows系统里给硬盘 或虚拟机文件夹 去掉只读权限 2 可选项 搜索框输入cmd 以管理员身份运行 依次执行下面的命令 diskpart list disk 列出磁盘 select dis
  • python没基础能自学吗-需要自学python吗?大概多久能学会?

    以下内容针对有编程经历的同学 没有编程经历的建议跟着视频学习 我这准备了一套基础的python视频资料 需要的同学可以私信我 对于python这块有任何不懂的问题可以随时来问我 我对于学习方法 系统学习规划 提高学习效率这些知道一点 希望可
  • 【华为OD】

    华为OD试题注意事项 使用合适的编程语言 在华为OD机试中多数情况下使用C 或Java 按照题目要求进行编码 仔细阅读题目描述并理解要求 在编码前可以进行伪代码编写或画流程图有助于理解和排除逻辑错误 注意代码的规范性 注重代码的可读性和可维
  • nrm ls *(星号)不见了,修改了cls文件也没有用

    问题描述 在使用nrm切换源的时候 发现执行nrm ls命令后 原本的 号不见了 这样很难一眼看出当前使用的npm源 npm https registry npmjs org yarn https registry yarnpkg com
  • 电子元器件符号+实物图+命名规则(太全了,绝对收藏)

    电子电路中常用的器件包括 电阻器 含电位器 电容器 电感器 变压器 二极管 三极管 光电器件 电声器件 显示器件 晶闸管 可控硅 场效应晶体管 IGBT MOSFET 继电器与干簧管 开关 保险丝 晶振 连接器 各种传感器等 下面一起来看看
  • 兼莱宝分享:适合上班族空闲时间做的三种靠谱副业?

    只讲干货 不来虚的 作为一个有着4年自由写作经验的人 做过的兼职非常多 什么给人家做海报 写商业文案 做民宿体验师等等 都有尝试过 每一份工作的收入都是不一样的 今天只分享3种靠谱的副业 都是我自己做过并且有收益的副业 一 自媒体写作 很多
  • 每日学习:Idea和Eclipse中的一些常用快捷键

    1 删除光标所在行代码 idea快捷键 Ctrl X eclipse快捷键 Ctrl D 2 复制光标所在行代码 或者鼠标选中的代码 idea快捷键 Ctrl D eclipse快捷键 Ctrl Alt 上下键 3 切换代码大小写 idea
  • Nacos 中下线服务时报错:The Raft Group [naming_instance_metadata] did not find the Leader node;解决

    问题描述 因为某些特殊原因需要把nacos迁移到另一个版本的nacos 我迁的是nacos2 0 2版本 迁移完成后 Nacos注册中心有一个微服务有多台实例的时候 点击一个实例下线操作 报错 caused errCode 500 errM
  • easyExcel文件上传与下载

    目录 1 导入POM依赖 2 模板文件 3 实体类 4 前端页面 5 模板文件上传 Controller 6 文件下载 Controller 7 导出效果 1 导入POM依赖
  • linux常用命令总结

    find查找命令 find 位置 name 搜索的相关内容 eg find name aaa 查看当前位置以aaa开头的文件 查找文件得内容 grep r 关键字 路径 例如 grep r test data reports grep 查找
  • 如果代码已关联git仓库,但是想将代码提交到新的仓库,应该如何做?

    如果你已经将代码关联到了一个 Git 仓库 但是希望将代码提交到另一个远程仓库 可以按照以下步骤操作 打开命令行终端并导航到你的本地代码仓库 确保你当前在正确的分支上 你可以通过运行 git branch 命令来查看当前所在分支 如果需要切
  • 快速解决Android中的selinux权限问题

    关于selinux的详细资料 请查阅http blog csdn net innost article details 19299937 在Android开发的过程中 遇到关于selinux相关的东西 当时还一下子看不懂 现在好像有点眉目了
  • UI特效应用Mask剪裁

    公司的特效做UI特效的时候 总喜欢一些奇奇怪怪的shader 做滚动窗口的时候需要用Mask把多余位置遮住 如果里面有特效的话会像这样透出 修改shader 的代码 使其支持支持stencil 可以实现mask遮盖 加入下面的两段代码 St
  • 自学C之递归理解

    一 理解概念 C语言允许一个函数调用自身 这种过程被称为递归 Recursion 程序使用递归处理特殊的问题 如阶乘 Ackermann函数 反序等等 实际上 如果不考虑运行时内存的开消 任何使用赋值语句 if else和while结构的函