自学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之递归理解 的相关文章

  • 使用递归对数字求和

    我刚刚研究了递归的概念 我想尝试一个简单的例子 在下面的代码中 我尝试获取数字 1 2 3 4 5 并使用递归将它们加在一起 我预计结果是 15 但我的代码返回 16 我究竟做错了什么 Code static void Main strin
  • 有人可以解释一下这段代码吗?排列代码[关闭]

    这个问题不太可能对任何未来的访客有帮助 它只与一个较小的地理区域 一个特定的时间点或一个非常狭窄的情况相关 通常不适用于全世界的互联网受众 为了帮助使这个问题更广泛地适用 访问帮助中心 help reopen questions 我正在做一
  • 使用 F# 进行循环与递归

    这里的示例代码解决了一个项目欧拉问题 从数字 1 开始 按顺时针方向向右移动 方向 5 x 5 螺旋形成如下 21 22 23 24 25 20 7 8 9 10 19 6 1 2 11 18 5 4 3 12 17 16 15 14 13
  • 使用 apply 函数重写循环

    我有以下 3 个函数 我想使其更快 我认为应用函数是最好的方法 但我从未使用过应用函数 所以我不知道该怎么做 任何类型的提示 想法和代码片段将不胜感激 n T dt 是全局参数 par 是参数向量 函数 1 是创建 m 1 n 矩阵的函数
  • Elixir 中的递归和匿名函数

    我正在尝试定义一个匿名函数来执行点积 我可以将其编码为私有函数 没有任何问题 但我正在努力解决匿名函数语法 我知道我可以以不同的方式实现这一点 但我试图了解如何使用模式匹配和递归来定义匿名函数 这是我当前的实现 dot fn i input
  • 使用递归求数组的最小值?

    好吧 所以我一直在尝试用 Java 来理解递归 我可以完成简单的任务 例如求和 反转等 但我一直在努力做这个练习 我试图使用递归找到数组中的最小数字 但始终得到 0 0 的答案 我对递归的理解是 我需要增加一个元素 然后提供一个结束递归的基
  • 使用记忆化与不使用记忆化的递归

    我在学校做的作业是用递归计算加泰罗尼亚数 第一个没有记忆 def catalan rec n res 0 if n 0 return 1 else for i in range n res catalan rec i catalan rec
  • 使用PHP通过FTP递归扫描目录和子目录

    我正在尝试创建目录中所有文件 及其大小 的列表 包括子目录中的所有内容 这些文件位于远程服务器上 所以我的脚本通过 FTP 连接 然后使用以下命令运行递归函数ftp chdir浏览每个目录 如果有其他方法可以做到这一点 我愿意接受建议 fl
  • Python递归数据读取

    如果你曾经玩过 我的世界 下面的内容将会更有意义 由于你们中的许多人还没有 我将尽力解释它 我正在尝试编写一个递归函数 它可以找到从 Minecraft 食谱的平面文件中制作任何 Minecraft 物品的步骤 这个实在是让我难住了 平面文
  • F#、FParsec 和递归调用流解析器(第二次)

    感谢您的回复我的第一篇文章 https stackoverflow com questions 26853718 f fparsec and calling a stream parser recursively and 我的第二篇文章 h
  • 具有特定深度的 TypeScript 递归类型

    TypeScript 允许您编写递归类型 但无法深入了解代码在较低级别 即深度 中如何变化 例如 下面的代码在所有级别上都具有相同类型的签名 并且我们必须在每个级别手动检查是否存在sub财产 type Recurse foo string
  • Java hibernate/jpa 如何创建自相关的动态通用实体

    我想使用 JPA hibernate 创建动态和通用的超类 它将针对每个层次结构模型进行扩展 例如 角色 页面 目录 部门 权限 树 我想使用递归和java反射来创建这个对象动态树 它应该看起来像这样 该实体应该引用自身实体 我希望它是完全
  • 计算 python 字典/数组数据结构的非空尾叶 - 递归算法?

    我正在寻找一个函数来查找一种复杂字典 数组结构的所有非空端点 我认为因为我不知道嵌套数组的数量或它们的位置 所以它必须是递归的 而我只是还没有完全理解这种思维方式 所以对于嵌套字典 x top middle nested value nes
  • 生成总和为 N 的所有数字排列

    我正在编写一个程序来创建所有数字 起初 我尝试使用分区函数对数字进行分区 然后对每个数字集进行排列 但是我认为这行不通 最好的方法是递归排列 同时对数字求和 这超出了我的能力范围 抱歉 如果这听起来真的很愚蠢 但我真的不知道 Example
  • 嵌套列表递归python的序列

    给定一些数字 n 我想生成一个大小为 n 的列表 其中以下示例显示列表中的第 n 个元素应该如何 对于 n 0 返回 对于 n 1 返回 对于 n 2 返回 对于 n 3 返回 基本上 它采用先前的列表并将它们附加到新列表中 我尝试过以下方
  • 如何停止 CTE 中的递归?

    我有一个数据库表 如下所示 ID PredecessorID Data 43b1e103 d8c6 40f9 b031 e5d9ef18a739 null 55f6951b 5ed3 46c8 9ad5 64e496cb521a 43b1e
  • 递归和大O

    我最近正在完成一项涉及递归和大 O 表示法的计算机科学作业 我相信我很好地理解了这一点 虽然当然不是完美的 但是有一个问题给我带来了最多的问题 奇怪的是 一看就知道是作业上最简单的一个 使用大哦符号提供最佳增长率来解决以下递归问题 T 1
  • 递归方法比交互式方法慢 10 倍 [关闭]

    这个问题不太可能对任何未来的访客有帮助 它只与一个较小的地理区域 一个特定的时间点或一个非常狭窄的情况相关 通常不适用于全世界的互联网受众 为了帮助使这个问题更广泛地适用 访问帮助中心 help reopen questions 代码已尽可
  • 迭代函数可以调用自身吗?

    当观看下面的 MIT 6 001 课程视频时 讲师在 28 00 将此算法标记为迭代 但是 在 30 27 他说这个算法和实际的 递归 算法都是递归的 该函数正在使用基本情况调用自身 那么这次迭代情况如何 private int itera
  • 需要使用 pyparsing 制作递归解析器的帮助

    我正在尝试使用 python pyparsing 进行解析 我在制作递归解析器时陷入困境 让我解释一下问题 我想要计算元素的笛卡尔积 语法是 cross elements element 我用更具体的方式 cross a c1 or cro

随机推荐

  • 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结构的函