c语言基础知识帮助理解(函数递归详解)

简介: c语言基础知识帮助理解(函数递归详解)

4c36a556fbfc438e87f8c26eb4f5108a.png"从前有座山,山里有座庙,庙里有个老和尚和一个小和尚。有一天老和尚对小和尚说:“从前有座山.山里有座庙,庙里有个老和尚和一个小和尚,有一天老和尚对小和尚说:“从前有座山.山里有座庙,庙里有个老和尚和一个小和尚......" (虽能体现递归特点,但又不是递归)


1.什么是递归

当一个函数在其定义中调用自身的过程称为递归。递归是一种强大的编程技巧,可以解决许多问题,特别是那些可以被分解为相同问题的子问题的情况


递归的主要思考方式在于:把大事化小

在C语言中,函数递归的基本原理是将一个大问题分解为一个或多个更小的问题,然后通过调用自身来解决这些更小的问题,直到达到基本情况,即不再需要递归调用的情况


2.递归的两个必要条件

  • 存在限制条件,当满足这个限制条件的时候,递归便不再继续。
  • 每次递归调用之后越来越接近这个限制条件。

试想如若没有限制条件,那便无限循环,真就成了上面的“从前有座山 山里有座庙” 的故事了。


3.事例和讲解

用递归来实现求n的阶乘(factorial) :

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int factorial(int n)
{
  if (n > 1)//递归终止的条件
  {
    return n * factorial(n - 1);
  }
  else
  {
    return 1;
  }
}
int main()//递归实现求n的阶乘
{
  int n = 0;
  scanf("%d", &n);
  int n_factorial = factorial(n);
  printf("%d", n_factorial);
  return 0;
}

4c36a556fbfc438e87f8c26eb4f5108a.png


下面是一个示例的递归树,用于说明计算4的阶乘的过程:

4! = 4 * 3!

|

3! = 3 * 2!

|

2! = 2 * 1!

|

1! = 1


bf67f76eac0742d8a7303b9d456c9b86.png


下面讲解一下思路:

  • 最基本便是:n!=n*(n-1)!——让我们有了返回n * factorial(n - 1)的想法
  • 当n=1时,便是终止条件——返回1 ,使用factorial(n - 1)也是为了更加靠近n=1的终止条件

4.递归原理讲解

当一个函数被调用时,会在内存中分配一个称为函数栈帧(Function Stack Frame)的数据结构。函数栈帧用于存储函数的局部变量、函数的参数、函数的返回地址等信息。

下面是一个示意图,展示了函数栈帧的结构:

                                       b6c6726131194122a19cabc17d0a3bc5.png

  • Return Address:保存着函数调用结束后需要返回的地址,即函数调用的下一条指令的地址。当函数执行完毕后,CPU会根据该地址跳转到正确的位置继续执行
  • Previous Stack Frame:指向调用函数的函数栈帧的地址,用于在函数返回时恢复调用函数的上下文
  • Local Variables:存储函数的局部变量。每个函数栈帧都有自己的一块内存空间,用于存储局部变量的值
  • Parameters:存储函数的参数。参数在函数调用时被传递给函数,并存储在函数栈帧中

在递归函数中,每次递归调用都会生成一个新的函数栈帧,这些函数栈帧会按照一定的顺序依次排列在内存中:先调用的函数先进入栈中,后销毁

651b9d2d65fd4647ac5def694522eaf8.png


5.递归弊端

5.1利用计算斐波那契数列来引入

int Fib(int n)
{
  if (n <= 2)
    return 1;
  else
    return Fib(n - 1) + Fib(n - 2);
}
int main()
{
  int n = 3;
  printf("第三个斐波那契数%d\n", Fib(n));
  int x = 50;
  printf("第五十个斐波那契数%d", Fib(x));
  return 0;
}

e520912652474b8e974a6f5c8461778e.png

我们会发现,第三个斐波纳契数立马就出来了,而第五十个迟迟不出来。

这就暴露了问题:

  • 这个函数的时候如果我们要计算第50个斐波那契数字的时候特别耗费时间
  • 函数求10000的阶乘(不考虑结果的正确性),程序会崩溃(我已经为各位试过了,不要再试了),也就是栈溢出了


这也说明:有时递归的效率不是很高 ,而且不得不说有时递归代码的可读性是不及循环的

5.2如何改进

那我们如何改进呢?

  • 将递归改写成非递归。
  • 使用static对象替代 nonstatic 局部对象。在递归函数设计中,可以使用 static 对象替代nonstatic 局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放 nonstatic 对象的开销,而且 static 对象还可以保存递归调用的中间状态,并且可为各个调用层所访问


下面便用循环来改写求斐波那契数列:

int Fib(int n)
{
  int n1 = 1;
  int n2 = 1;
  int result = 0;
  while (n > 2)
  {n--;
    result = n1 + n2;
    n1 = n2;
    n2 = result;
  }
  return result;
}
int main()
{
  int n = 3;
  printf("第三个斐波那契数%d\n", Fib(n));
  int x = 30;
  printf("第三十个斐波那契数%d", Fib(x));
  return 0;
}

3fe6d47e4d4646ac90228fc3cc428c35.png

所以我们也不要一味地去使用递归在合适的代码里使用合适的方法才能让我们的编程水平更加出色。


总之,递归是一项强大的编程技术,但在使用时需要注意栈溢出问题。通过合理的算法设计和对函数栈帧的了解,我们可以更好地应对递归问题,使代码更加健壮和可靠


这次的分享先到这里的,感谢大家的支持,下一次会总结数组相关的知识!!!


目录
相关文章
|
24天前
|
存储 C语言 开发者
【C语言】字符串操作函数详解
这些字符串操作函数在C语言中提供了强大的功能,帮助开发者有效地处理字符串数据。通过对每个函数的详细讲解、示例代码和表格说明,可以更好地理解如何使用这些函数进行各种字符串操作。如果在实际编程中遇到特定的字符串处理需求,可以参考这些函数和示例,灵活运用。
48 10
|
24天前
|
存储 程序员 C语言
【C语言】文件操作函数详解
C语言提供了一组标准库函数来处理文件操作,这些函数定义在 `<stdio.h>` 头文件中。文件操作包括文件的打开、读写、关闭以及文件属性的查询等。以下是常用文件操作函数的详细讲解,包括函数原型、参数说明、返回值说明、示例代码和表格汇总。
43 9
|
24天前
|
存储 Unix Serverless
【C语言】常用函数汇总表
本文总结了C语言中常用的函数,涵盖输入/输出、字符串操作、内存管理、数学运算、时间处理、文件操作及布尔类型等多个方面。每类函数均以表格形式列出其功能和使用示例,便于快速查阅和学习。通过综合示例代码,展示了这些函数的实际应用,帮助读者更好地理解和掌握C语言的基本功能和标准库函数的使用方法。感谢阅读,希望对你有所帮助!
33 8
|
24天前
|
C语言 开发者
【C语言】数学函数详解
在C语言中,数学函数是由标准库 `math.h` 提供的。使用这些函数时,需要包含 `#include <math.h>` 头文件。以下是一些常用的数学函数的详细讲解,包括函数原型、参数说明、返回值说明以及示例代码和表格汇总。
43 6
|
24天前
|
存储 C语言
【C语言】输入/输出函数详解
在C语言中,输入/输出操作是通过标准库函数来实现的。这些函数分为两类:标准输入输出函数和文件输入输出函数。
166 6
|
24天前
|
存储 缓存 算法
【C语言】内存管理函数详细讲解
在C语言编程中,内存管理是至关重要的。动态内存分配函数允许程序在运行时请求和释放内存,这对于处理不确定大小的数据结构至关重要。以下是C语言内存管理函数的详细讲解,包括每个函数的功能、标准格式、示例代码、代码解释及其输出。
52 6
|
24天前
|
C语言 开发者
【C语言】断言函数 -《深入解析C语言调试利器 !》
断言(assert)是一种调试工具,用于在程序运行时检查某些条件是否成立。如果条件不成立,断言会触发错误,并通常会终止程序的执行。断言有助于在开发和测试阶段捕捉逻辑错误。
35 5
|
1月前
|
存储 人工智能 算法
数据结构实验之C 语言的函数数组指针结构体知识
本实验旨在复习C语言中的函数、数组、指针、结构体与共用体等核心概念,并通过具体编程任务加深理解。任务包括输出100以内所有素数、逆序排列一维数组、查找二维数组中的鞍点、利用指针输出二维数组元素,以及使用结构体和共用体处理教师与学生信息。每个任务不仅强化了基本语法的应用,还涉及到了算法逻辑的设计与优化。实验结果显示,学生能够有效掌握并运用这些知识完成指定任务。
57 4
|
1月前
|
C语言
c语言调用的函数的声明
被调用的函数的声明: 一个函数调用另一个函数需具备的条件: 首先被调用的函数必须是已经存在的函数,即头文件中存在或已经定义过; 如果使用库函数,一般应该在本文件开头用#include命令将调用有关库函数时在所需要用到的信息“包含”到本文件中。.h文件是头文件所用的后缀。 如果使用用户自己定义的函数,而且该函数与使用它的函数在同一个文件中,一般还应该在主调函数中对被调用的函数做声明。 如果被调用的函数定义出现在主调函数之前可以不必声明。 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必多所调用的函数在做声明
37 6
|
1月前
|
存储 算法 程序员
C语言:库函数
C语言的库函数是预定义的函数,用于执行常见的编程任务,如输入输出、字符串处理、数学运算等。使用库函数可以简化编程工作,提高开发效率。C标准库提供了丰富的函数,满足各种需求。