函数调用栈Function Call Stack与递归

函数调用栈(Function Call Stack)是计算机内存中用于跟踪函数调用和返回的一种数据结构。在程序执行期间,每当一个函数被调用时,相关的信息(例如函数参数、局部变量和返回地址)都会被存储在栈内存中。当函数执行完毕并返回时,这些信息会被弹出栈,控制流程回到调用该函数的地方。

具体来说,每次函数调用时,以下步骤会发生:

1. 被调用函数的参数被压入栈中。
2. 被调用函数的返回地址(即调用该函数的下一条指令的地址)也被压入栈中。
3. 控制转移到被调用函数的代码中,开始执行。
4. 在被调用函数中,可能会有更多的函数调用,这些调用会在栈中嵌套存储。
5. 当一个函数执行完毕并返回时,它的返回值会被存储到指定的寄存器或者内存位置中,然后栈中的数据被弹出,控制流程回到调用该函数的地方。

这样,函数调用栈的结构就形成了一个“后进先出”(LIFO)的堆栈结构,最后被调用的函数最先返回,直到最初的调用者函数。

函数调用栈在程序执行期间起着至关重要的作用,它不仅跟踪了函数调用的顺序,还管理了函数调用过程中的各种数据。当栈空间不足或者发生溢出时,就会导致栈溢出错误,这通常是由于递归调用层次过深或者函数调用过程中分配的局部变量过多所致。
----------
 

理解递归的过程涉及到函数调用栈的概念,也就是说,在每次递归调用时,函数的参数、局部变量等信息都会被存储在内存中的一个栈结构中。让我们来详细解释一下递归函数在压栈和弹栈过程中的具体操作。

1. **压栈(Push):** 当一个函数被调用时,系统会为该函数分配一块内存空间,用于存储函数的参数、局部变量以及函数执行过程中的其他信息。这个内存空间被称为栈帧(Stack Frame)。栈帧包含了函数调用所需的所有信息。在递归调用中,每次函数调用都会创建一个新的栈帧,并将其压入函数调用栈中。

2. **执行函数体:** 一旦函数被调用,系统就会开始执行函数体内的代码。在递归函数中,通常会包含一个基本情况(base case),用于结束递归调用。如果基本情况未达到,函数会继续递归调用自身,每次调用都会创建一个新的栈帧。

3. **递归调用:** 在函数体内部,如果遇到递归调用,系统会暂时停止当前函数的执行,转而执行被调用的函数。这时,新的函数调用会在栈中创建一个新的栈帧,并继续执行相应的函数体。

4. **弹栈(Pop):** 当函数执行结束时,系统会从栈顶弹出当前函数的栈帧,将控制权返回给上一级函数。这意味着上一级函数可以继续执行它之前被中断的部分。如果递归调用嵌套了多层,那么每个函数执行完毕后都会被弹出栈,直到回到最初的调用位置。

这样,递归函数的压栈和弹栈过程就完成了一次完整的递归调用。通过不断地压栈、执行函数体、递归调用和弹栈,递归函数可以解决问题并得到结果。

在递归调用中,每次递归返回的结果会依次相乘,直到达到基线条件,然后将这些结果相乘得到最终的阶乘值。让我们用一个简单的例子来说明这个过程:

以计算 5 的阶乘(5!)为例:

1. 调用 `factorial(5)`,这将导致函数内部调用 `factorial(4)` 并将结果乘以 5。
2. 在 `factorial(4)` 中,又会调用 `factorial(3)` 并将结果乘以 4。
3. 在 `factorial(3)` 中,又会调用 `factorial(2)` 并将结果乘以 3。
4. 在 `factorial(2)` 中,又会调用 `factorial(1)` 并将结果乘以 2。
5. 在 `factorial(1)` 中,由于 `n` 等于 1,满足基线条件,直接返回 1。

然后,递归开始回溯每次返回时,将上一级递归调用的结果乘以当前的 `n` 值:

- `factorial(1)` 返回 1
- `factorial(2)` 返回 2 * 1 = 2
- `factorial(3)` 返回 3 * 2 = 6
- `factorial(4)` 返回 4 * 6 = 24
- `factorial(5)` 返回 5 * 24 = 120

所以,5 的阶乘为 120。这就是递归过程中依次返回结果进行相乘的简要描述。

-----------

让我用更简单的方式来解释一下递归

想象你有一套小人积木,每个积木上都写着一个数字。现在,你想要计算所有积木上数字的总和。但是,这些积木是堆叠在一起的,你不能直接看到每个积木上的数字。

现在,你该怎么做呢?一种方法是逐个拿掉积木,看看上面的数字,然后加起来。但是,这会很麻烦,因为积木很多。

另一种方法是:你拿掉最上面的一个积木,看看上面的数字,然后把剩下的积木当作一个整体,再次进行相同的操作。你继续这样做,直到所有积木都被拿掉为止。

这种方法就是递归。你不断地重复相同的操作,直到达到了某个特定的条件(比如没有积木了),然后逐层返回结果。

在这个例子中,拿掉最上面的积木并查看上面的数字,就相当于计算了一个阶乘的一部分。而把剩下的积木当作一个整体并进行相同操作,就相当于用递归的方式计算剩余部分的阶乘。

----------
进一步解释。

想象一下,当你在程序中调用一个函数时,比如说你有一个函数 `foo()` 调用了另一个函数 `bar()`,而 `bar()` 又调用了另一个函数 `baz()`。这时候,函数调用栈就会起作用。

1. 当你调用 `foo()` 时,当前的执行状态(包括变量、执行指针等)会被保存到栈中,并且控制流会转移到 `foo()` 的代码中。
2. 当 `foo()` 调用 `bar()` 时,`bar()` 的参数和返回地址会被压入栈中,然后控制流程会转移到 `bar()` 的代码中。
3. 同样地,当 `bar()` 调用 `baz()` 时,`baz()` 的参数和返回地址也会被压入栈中,然后控制流程会转移到 `baz()` 的代码中。

这样,调用栈就像一个记录函数调用和返回的历史记录表。当 `baz()` 执行完成并返回时,它的数据会被弹出栈,控制流程会返回到 `bar()`,然后再弹出 `bar()` 的数据,最后返回到 `foo()`。

函数调用栈的大小是有限的,当函数嵌套调用过深或者使用递归的时候,如果栈空间不足,就会导致栈溢出错误。这就是为什么编程中需要小心处理递归调用或者深度嵌套函数的原因之一。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/328.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

(十一)PostgreSQL的wal日志(2)-默认wal日志大小

PostgreSQL的wal日志(2)-默认wal日志大小 PostgreSQL的WAL(Write-Ahead Logging)日志文件默认大小为16MB是基于对性能和可靠性权衡的结果。这个默认值是在PostgreSQL早期版本中设定的,目的是在维持良好的系统性能和提高数据恢复能力之间找到…

Gitlab: Python项目CI/CD实践

目录 1. 说明 2. 准备工作 2.1 服务器 2.2 开发机hosts文件 2.3 项目 3. 步骤过程 3.1 建仓Fastapi T1 3.2 开发机测试构建与推送 ​编辑 3.3 在工作站添加gitlab-runner 3.4 提交代码,查看Pipelines结果 3.5 观察部署情况 4. 参考 1. 说明 分别以一个…

【2024 SCI一区】 基于DCS-BiLSTM-Attention的多元回归预测(Matlab实现)

【2024 SCI一区】 基于DCS-BiLSTM-Attention的多元回归预测(Matlab实现) 目录 【2024 SCI一区】 基于DCS-BiLSTM-Attention的多元回归预测(Matlab实现)效果一览基本介绍程序设计参考资料 效果一览 基本介绍 差异创意搜索算法&…

C++ 静态成员函数(二)

一、访问静态成员变量 静态成员函数可以通过作用域运算符::来访问类的静态成员变量和静态成员函数 静态成员函数不属于任何特定的对象,而是属于整个类,可以通过类名直接调用,无需创建类的实例。静态成员函数不能访问类的非静态成员变量和非…

设计模式学习笔记 - 开源实战一(下):通过剖析JDK源码学习灵活应用设计模式

概述 上篇文章我们讲解了工厂模式、建造者模式、适配器模式适配器模式在 JDK 中的应用,其中 Calendar 类用到了工厂模式和建造者模式, Collections 类用到了装饰器模式和适配器模式。学习的重点是让你了解,在真实的项目中模式的实现和应用更…

在 Linux 终端中创建目录

目录 ⛳️推荐 前言 在 Linux 中创建一个新目录 创建多个新目录 创建多个嵌套的子目录 测试你的知识 ⛳️推荐 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站 前言 在本系列的这一部…

Maven的dependencyManagement与dependencies区别

先说结论:Maven 使用dependencyManagement 元素来提供了一种管理依赖版本号的方式。 在maven多模块项目的pom文件中,有的小伙伴会发现最外层的pom文件和里面的pom文件有个地方不一样 如下图 父pom 子pom 一般来说是在maven的最外父工程pom文件里&…

npm内部机制与核心原理

npm 的核心目标: Bring the best of open source to you, your team and your company. npm 最重要的任务是安装和维护开源库。 npm 安装机制与背后思想 npm 的安装机制非常值得探究。Ruby 的 Gem,Python的pip都是全局安装机制,但是npm的安装…

界面组件Telerik UI for WPF 2024 Q1新版亮点 - 全新DateRangePicker组件

Telerik UI for WPF拥有超过100个控件来创建美观、高性能的桌面应用程序,同时还能快速构建企业级办公WPF应用程序。UI for WPF支持MVVM、触摸等,创建的应用程序可靠且结构良好,非常容易维护,其直观的API将无缝地集成Visual Studio…

spring事务处理

spring事务 事务介绍 一个事务要么同时成功,要么同时失败 特性 Atomic原子性 事务是由一个或多个活动组成的一个工作单元。原子性确保事务中的所有操作全部发生或全部不发生 Consistent一致性 一旦事务完成,系统必须确保它所建模的业务处于一致的状态 Is…

Hive进阶Day06

目录 一、MapReduce的计算过程 二、Yarn的资源调度 1、yarn的资源调度策略 三、Hive的语法树 四、数据开发 五、数据仓库 六、数据仓库开发流程 七、数仓分层 八、ETL和ELT 一、MapReduce的计算过程 分布式计算框架 需要编写代码执行,执行时会数据所在服务器…

如何正确查看容器的CPU使用率

进入容器中top,虽然看到的PID是容器的,但是%Cpu的统计信息却是宿主机的。 如图 原理 进程的cpu使用率是如何计算出来的? 每个进程的状态是放在文件里的,在/proc目录下,每个进程有自己pid命名的文件夹, …

.NET 爬虫从入门到入狱

目录 前言 1.💡使用HttpClient爬取数据 2.🚀模拟User-Agent 3.🤵使用HTML解析库 3.👌前端Price显示 4.🌱运行实例 获取金价Au 5.🧾使用正则表达式解析 6.💫获取BTC价格 7.✨获取CSDN热点…

4.15报错记录

打开文件时出错a bytes-like object is required,notNoneType 确保E:/data/stdata/st- images-1208-json|ST-WT-1.json是一个有效的标签文件。 今天用X-anylabling更改标签目录时出现这个报错 解决方案:图片文件夹中创建同名的一个文件夹把json文件放进去就可以打…

QML语法基础一

import QtQuick QML类型系统 1.基本类型:int bool real double string url list var enum 2.Quick类型:color font matrix4*4 quaternion vector2d vector3d vector4d date point size rect 等 3.javascripte类型:qml支持标准的javascript类型 4.对象类型:用于QML对象实例化 对…

[Qt网络编程]之UDP通讯的简单编程实现

hello!欢迎大家来到我的Qt学习系列之网络编程之UDP通讯的简单编程实现。希望这篇文章能对你有所帮助!!! 本篇文章的相关知识请看我的上篇文章: http://t.csdnimg.cn/UKyeM 目录 UDP通讯 基于主窗口的实现 基于线程的实现 UDP通讯…

【YOLO系列PR、F1绘图】更改v5、v7、v8(附v8训练、验证方式),实现调用val.py或者test.py后生成pr.csv,然后再整合绘制到一张图上(使用matplotlib绘制)

目录 1. 前提 效果图2. 更改步骤2.1 得到PR_curve.csv和F1_curve.csv2.1.1 YOLOv7的更改2.1.1.1 得到PR_curve.csv2.2.1.2 得到F1_curve.csv 2.1.2 YOLOv5的更改(v6.1版本)2.1.3 YOLOv8的更改(附训练、验证方式) 2.2 绘制PR曲线 …

【创建型模式】抽象工厂模式

一、抽象工厂模式概述 抽象工厂模式定义:提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。 模式动机: 1.当系统提供的工厂生产的具体产品并不是一个简单的对象,而是多个位于不同产品等级结构、属于不同类型的…

HiveSql中的函数家族(一)

一.内置函数 1-1 日期类型操作 -- 获取当前日期 select current_date(); -- 获取当前日期时间 select current_timestamp(); -- 获取unix时间(时间戳) 从1970年1月1号0时0分0秒 到现在过去了多少秒 select unix_timestamp();-- unix时间 和日期时间的转…

41、二叉树-二叉树的层序遍历

思路: 层序遍历就是从左到右依次遍历。这个时候就可以使用队列的方式。例如先把头节点入队,然后遍历开始,首先计算队列长度,第一层,长度为了,遍历一次,依次出队,头结点出队&#xff…