一个函数在它的函数体内调用它自身称为递归调用,这种函数称为递归函数。执行递归函数将反复调用其自身,每调用一次就进入新的一层,当最内层的函数执行完毕后,再一层一层地由里到外退出。

递归函数不是C语言的专利,Java、C#、JavaScript、PHP 等其他编程语言也都支持递归函数。

下面我们通过一个求阶乘的例子,看看递归函数到底是如何运作的。阶乘 n! 的计算公式如下:

根据公式编写如下的代码:


  1. #include <stdio.h>
  2. //求n的阶乘
  3. long factorial(int n) {
  4. if (n == 0 || n == 1) {
  5. return 1;
  6. }
  7. else {
  8. return factorial(n - 1) * n; // 递归调用
  9. }
  10. }
  11. int main() {
  12. int a;
  13. printf("Input a number: ");
  14. scanf("%d", &a);
  15. printf("Factorial(%d) = %ld\n", a, factorial(a));
  16. return 0;
  17. }

运行结果:
Input a number: 5↙
Factorial(5) = 120

factorial() 就是一个典型的递归函数。调用 factorial() 后即进入函数体,只有当 n==0 或 n==1 时函数才会执行结束,否则就一直调用它自身。

由于每次调用的实参为 n-1,即把 n-1 的值赋给形参 n,所以每次递归实参的值都减 1,直到最后 n-1 的值为 1 时再作递归调用,形参 n 的值也为1,递归就终止了,会逐层退出。

要想理解递归函数,重点是理解它是如何逐层进入,又是如何逐层退出的,下面我们以 5! 为例进行讲解。

递归的进入

1) 求 5!,即调用 factorial(5)。当进入 factorial() 函数体后,由于形参 n 的值为 5,不等于 0 或 1,所以执行factorial(n-1) * n,也即执行factorial(4) * 5。为了求得这个表达式的结果,必须先调用 factorial(4),并暂停其他操作。换句话说,在得到 factorial(4) 的结果之前,不能进行其他操作。这就是第一次递归。

2) 调用 factorial(4) 时,实参为 4,形参 n 也为 4,不等于 0 或 1,会继续执行factorial(n-1) * n,也即执行factorial(3) * 4。为了求得这个表达式的结果,又必须先调用 factorial(3)。这就是第二次递归。

3) 以此类推,进行四次递归调用后,实参的值为 1,会调用 factorial(1)。此时能够直接得到常量 1 的值,并把结果 return,就不需要再次调用 factorial() 函数了,递归就结束了。

下表列出了逐层进入的过程
层次/层数 实参/形参 调用形式 需要计算的表达式 需要等待的结果
1 n=5 factorial(5) factorial(4) * 5 factorial(4) 的结果
2 n=4 factorial(4) factorial(3) * 4 factorial(3) 的结果
3 n=3 factorial(3) factorial(2) * 3 factorial(2) 的结果
4 n=2 factorial(2) factorial(1) * 2 factorial(1) 的结果
5 n=1 factorial(1) 1

递归的退出

当递归进入到最内层的时候,递归就结束了,就开始逐层退出了,也就是逐层执行 return 语句。

1) n 的值为 1 时达到最内层,此时 return 出去的结果为 1,也即 factorial(1) 的调用结果为 1。

2) 有了 factorial(1) 的结果,就可以返回上一层计算factorial(1) * 2的值了。此时得到的值为 2,return 出去的结果也为 2,也即 factorial(2) 的调用结果为 2。

3) 以此类推,当得到 factorial(4) 的调用结果后,就可以返回最顶层。经计算,factorial(4) 的结果为 24,那么表达式factorial(4) * 5的结果为 120,此时 return 得到的结果也为 120,也即 factorial(5) 的调用结果为 120,这样就得到了 5! 的值。

下表列出了逐层退出的过程
层次/层数 调用形式 需要计算的表达式 从内层递归得到的结果
(内层函数的返回值)
表达式的值
(当次调用的结果)
5 factorial(1) 1 1
4 factorial(2) factorial(1) * 2 factorial(1) 的返回值,也就是 1 2
3 factorial(3) factorial(2) * 3 factorial(2) 的返回值,也就是 2 6
2 factorial(4) factorial(3) * 4 factorial(3) 的返回值,也就是 6 24
1 factorial(5) factorial(4) * 5 factorial(4) 的返回值,也就是 24 120

至此,我们已经对递归函数 factorial() 的进入和退出流程做了深入的讲解,把看似复杂的调用细节逐一呈献给大家,即使你是初学者,相信你也能解开谜团。

递归的条件

每一个递归函数都应该只进行有限次的递归调用,否则它就会进入死胡同,永远也不能退出了,这样的程序是没有意义的。

要想让递归函数逐层进入再逐层退出,需要解决两个方面的问题:

  • 存在限制条件,当符合这个条件时递归便不再继续。对于 factorial(),当形参 n 等于 0 或 1 时,递归就结束了。
  • 每次递归调用之后越来越接近这个限制条件。对于 factorial(),每次递归调用的实参为 n - 1,这会使得形参 n 的值逐渐减小,越来越趋近于 1 或 0。

更多关于递归函数的内容

factorial() 是最简单的一种递归形式——尾递归,也就是递归调用位于函数体的结尾处。除了尾递归,还有更加烧脑的两种递归形式,分别是中间递归和多层递归:

  • 中间递归:发生递归调用的位置在函数体的中间;
  • 多层递归:在一个函数里面多次调用自己。

递归函数也只是一种解决问题的技巧,它和其它技巧一样,也存在某些缺陷,具体来说就是:递归函数的时间开销和内存开销都非常大,极端情况下会导致程序崩溃。

我们将在接下来的三节课程里面讲解这些进阶内容:C语言中间递归函数(比较复杂的一种递归)C语言多层递归函数(最烧脑的一种递归)递归函数的致命缺陷:巨大的时间开销和内存开销(附带优化方案)

C语言递归函数(递归调用)详解[带实例演示]相关推荐

  1. jQuery数组处理详解(含实例演示)

    jQuery的数组处理,便捷,功能齐全. 最近的项目中用到的比较多,深感实用,一步到位的封装了很多原生js数组不能企及的功能. 最近时间紧迫,今天抽了些时间回过头来看 jQuery中文文档 中对数组的 ...

  2. 递归调用详解(图文并茂)

    ** 本文主要依据C程序设计(第四版) 谭浩强著,这本书Hanoi的实例,详细讲解递归调用 .** 代码如下 #include<stdio.h>void move(int x, int y ...

  3. 10、Java 方法的递归调用详解(递归调用的分析和案例:阶乘、斐波那契、猴子吃桃)

    文章目录 一.递归缩写 二.递归调用 (1) 递归方式求累加和 (2) 递归内存分析 三.递归调用(概念) 四.递归调用举例 五.递归注意事项 六.斐波那契数列 七.猴子吃桃 一.递归缩写

  4. jq 组装数组_jQuery数组处理详解(含实例演示)

    jQuery的数组处理,便捷,功能齐全. 最近的项目中用到的比较多,深感实用,一步到位的封装了很多原生js数组不能企及的功能. 最近时间紧迫,今天抽了些时间回过头来看 jQuery中文文档 中对数组的 ...

  5. python递归调用详解_Python递归调用自己的函数

    原博文 2019-11-16 10:36 − def fact(x): if x == 1: return 1 else: return x * fact(x-1) ... 0 191 相关推荐 20 ...

  6. C语言const用法详解(带实例)

    const与define区别 在C语言中const常用来定义常量,表示这个常量是不可以被修改的.define宏定义也可以用来表示一个常量.那么他们有那些区别呢! 1.预编译指令只是对值进行简单的替换, ...

  7. C语言结构体struct详解与实例

    目录 1.定义 2.应用 2.1初始化 2.2使用 3.结构体对齐规则与存储 1.定义 C语言中结构体(struct关键字定义)是一种自定义数据类型.通过结构体的定义可以将多种不同类型数据形成一个组合 ...

  8. LIBSVM多分类问题 参数详解及实例演示

    前言 安装包及具体安装步骤,搜博主之前的MATLAB专栏文章 1. 入门案例 1.1 分类的小例子--根据身高体重进行性别预测 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ...

  9. C#调用存储过程详解(带返回值、参数输入输出等)

    C#调用存储过程详解(带返回值.参数输入输出等) 这篇文章主要介绍了C#调用存储过程的方法,结合实例形式详细分析了各种常用的存储过程调用方法,包括带返回值.参数输入输出等,需要的朋友可以参考下 本文实 ...

最新文章

  1. 给力!一行代码躺赚普通程序员10年薪资!
  2. python列表推导式格式_Python列表推导式(for表达式)及用法
  3. openpyxl删除添加excel列_Python | 如何使用Python操作Excel(二)
  4. python中字母大小顺序,如何在Python中按字母顺序对unicode字符串排序?
  5. SQL获取变量类型以及变量最大长度
  6. 我为何不再愿意打一份朝九晚五的工?
  7. efficientdet-pytorch训练自己的数据集
  8. [转]使用Microsoft Web Application Stress Tool对web进行压力测试
  9. fortran 学习笔记1-编译环境
  10. 服务器应用越狱修复,iOS 11.2-11.3.1越狱问题汇总 iOS 11.2-11.3.1越狱错误及修复教程...
  11. python 工具变量_工具变量读书笔记
  12. 四种用电脑给手机发短信方法
  13. 什么是非参数检验?应该如何操作与分析?
  14. Python 可轻松生成图文并茂的PDF报告!
  15. 固态硬盘简称是不是ssd_Tigo金泰克
  16. 状态模式、有限状态机 Unity版本实现
  17. android缩放组件,Android控件实现图片缩放功能
  18. 超好看的二次元透明网站登录页模板
  19. 树莓派4B爽上流安装python3的OpenCV(人脸检测识别—门禁“环境搭建篇”)
  20. Austroads交通管理指南 2022(英)(附下载)

热门文章

  1. 如何成为专业大数据架构师?
  2. 【stm32H7读内部flash程序】
  3. MAC版本Mysql数据库忘记密码解决教程
  4. 使用 vue2 制作一个知乎日报
  5. 黑苹果之路::联想YOGA C740+黑苹果Catalina 10.15.6+DW1820A实现隔空投送、接力、随航
  6. Ubuntu16.04安装flash
  7. 老卫带你学---sklearn实现留一法与自助法(booststrapping)
  8. 10种常见的回归算法总结和介绍
  9. CBOW 与 skip-gram
  10. 新浪微薄与腾讯微薄的第三方登录