04. 函数和函数调用机制

1. 先学习/复习C语言的入门知识

1.1 C语言简介

C语言是一种通用的编程语言,于1972年由丹尼斯·里奇(Dennis Ritchie)创建。C语言最初目的是为了开发UNIX操作系统,但由于其简洁的语法、快速的执行速度和可移植性,自此成为了一种广泛应用的系统和应用程序开发语言。

1.2 基本数据类型

C语言中的基本数据类型有整数(int)、浮点数(float、double)、字符(char)和布尔(bool)。下面是一些常用的基本数据类型示例:

int num = 10;  // 整数类型变量
float pi = 3.14159;  // 单精度浮点数类型变量
double money = 1000.5;  // 双精度浮点数类型变量
char letter = 'A';  // 字符类型变量
bool isTrue = true;  // 布尔类型变量

1.3 变量

在C语言中,需要使用变量来存储和操作数据。变量在使用前需要先声明,声明变量时需要指定变量的类型。例如:

int a;  // 定义整数类型变量a
float b;  // 定义单精度浮点数类型变量b
double c;  // 定义双精度浮点数类型变量c
char d;  // 定义字符类型变量d
bool e;  // 定义布尔类型变量e

在C语言中,变量需要初始化后才能使用。可以在声明变量时进行初始化:

int a = 10;  // 定义整数类型变量a并初始化为10
float b = 3.14;  // 定义单精度浮点数类型变量b并初始化为3.14
char c = 'A';  // 定义字符类型变量c并初始化为'A'
bool d = true;  // 定义布尔类型变量d并初始化为true

1.4 运算符

C语言中常用的运算符包括算术运算符、关系运算符、逻辑运算符和三目运算符等。

算术运算符:

int a = 10, b = 20;  // 定义变量a和bint sum = a + b;  // 相加
int diff = a - b;  // 相减
int mul = a * b;  // 相乘
int div = b / a;  // 相除
int mod = b % a;  // 取模

关系运算符:

int a = 10, b = 20;  // 定义变量a和bbool isEqual = a == b;  // 是否相等
bool isLess = a < b;  // 是否小于
bool isGreater = a > b;  // 是否大于

逻辑运算符:

bool isTrue = true, isFalse = false;  // 定义变量isTrue和isFalsebool andResult = isTrue && isFalse;  // 逻辑与
bool orResult = isTrue || isFalse;  // 逻辑或
bool notResult = !isTrue;  // 逻辑非

三目运算符:

int a = 10, b = 20;  // 定义变量a和bint max = a > b ? a : b;  // 如果a>b,max等于a,否则等于b

1.5 控制流

C语言中常用的控制流语句包括if语句、for循环、while循环和switch语句等。

if语句:

int a = 10, b = 20;  // 定义变量a和bif(a > b) {  // 如果a>b,执行以下语句printf("a大于b\n");
} else {  // 否则执行以下语句printf("a小于等于b\n");
}

for循环:

int i;  // 声明计数器变量ifor(i = 0; i < 10; i++) {  // 循环10次printf("%d\n", i);  // 打印当前计数器的值
}

while循环:

int i = 0;  // 声明计数器变量iwhile(i < 10) {  // 循环10次printf("%d\n", i);  // 打印当前计数器的值i++;  // 计数器每次循环自增1
}

switch语句:

int a = 1;  // 定义变量aswitch(a) {case 0:  // 如果a等于0,执行以下语句printf("a等于0\n");break;case 1:  // 如果a等于1,执行以下语句printf("a等于1\n");break;default:  // 如果a不等于0或1,执行以下语句printf("其他情况\n");break;
}

1.6 函数

C语言中函数是一种独立的代码模块,用于完成特定的任务。函数包含函数头和函数体两部分。

函数头包括函数名、返回值类型和参数列表等。例如:

int add(int a, int b) {  // 定义add函数,包含两个整数类型的参数a和b,返回值为整数类型int sum = a + b;  // 计算a和b的和return sum;  // 返回计算结果
}

调用add函数:

int result = add(10, 20);  // 调用add函数,将10和20传入
printf("10和20的和为:%d\n", result);  // 打印结果

上面的程序会打印出以下内容:

10和20的和为:30

2. 理解函数

在初高中阶段,函数通常是指数学函数的概念。

数学函数是一种关系,它将一些输入值(称为自变量)映射到相应的输出值(称为因变量)。一般这样表示:y = f(x)。

初高中学习的函数和编程中的函数是有一些相似之处的。在编程中,函数用于封装可被重复使用的代码块,接收输入和返回输出,这和初中学习的函数有些相似之处。

C 语言中的函数是一种可以被重复执行并且可以接收输入和返回输出的代码块。函数可以减少代码的重复性,提高程序的可读性和可维护性。

C 语言的函数的格式包括函数名、参数列表、函数体以及返回值。

int add(int x,int y){int a = x + y;return a;
}
  • 函数名:add
  • 参数列表:xy,它们的类型都是int
  • 函数体:{}大括号里面的内容都是函数体
  • 返回值:return后面的值是返回值,因为add函数前面的int指定了返回值的类型,所以这里返回int类型的a

函数就是输入一些值,进行一些操作或处理,然后输出一些值!

3. 什么是栈?

把书或盘子摞起来,就像压栈一样;需要一次一个地取走它们,就像弹栈一样。

栈是一种常见的线性数据结构,它遵循后进先出(LIFO)的原则。栈可以看作是一个容器,其中的元素按照后进先出的顺序进行存储和访问。

3.1 后进先出

后进先出(Last In First Out,LIFO)的特性。当数据被插入栈中时,它会被放置在栈顶。而当从栈中弹出数据时,最后插入栈的数据会首先被弹出。

  • 首先,我们向栈中插入元素 A。由于栈是空的,插入的元素就变成了栈顶。

  • 然后,我们又向栈中插入元素 B。由于栈的特性是后进先出,B 就被放在了 A 的上面,即成为了栈顶元素。

  • 接下来,我们再插入一个元素 C,同样的,它会被放在栈顶。

  • 当我们从栈中弹出元素时,我们会弹出栈顶元素。因此,我们先弹出的是 C,随后是 B,最后是 A。

3.2 入栈和出栈

入栈 和 出栈 是栈的两个基本操作。

  1. 入栈(Push)操作:
    入栈是将一个新的元素添加到栈的顶部,也就是在栈中创建一个新的节点。新元素被放置在栈顶之上,并成为新的栈顶元素。

  2. 出栈(Pop)操作:
    出栈是从栈顶移除一个元素,即删除栈顶节点并返回其值。

3.3 类似于栈结构的场景

  • 浏览器的后退和前进:每次访问新页面时,该页面的信息都会被压入栈中,如果用户单击“后退”按钮,则最近访问的页面会从栈中弹出,以便用户返回上一个页面
  • 撤销操作:先恢复的最新的状态,直到回到所需的状态为止。
  • APP的点击:先点击进入商品列表页面,然后进入商品详情页面,再进入购买页面,先返回详情页面,再返回商品列表页面。

4. 栈内存和函数调用

4.1 栈内存与栈帧

栈内存(Stack Memory)是指程序在运行过程中用于存放栈的一段内存空间。栈内存是计算机内存中的一部分,通常大小是固定的,在程序的运行过程中,栈内存会被分成多个栈帧(Stack Frame)来存放程序需要临时保存的数据,比如函数的参数、局部变量、返回地址等等。

每当一个函数或过程被调用时,都会创建一个对应的栈帧,栈帧会在调用结束后被销毁。

栈帧主要包含以下信息:

  1. 返回地址(Return Address):记录了当前函数或过程执行完后,程序需要返回到哪个位置继续执行。

  2. 参数和局部变量(Parameters and Local Variables):用于存储函数或过程的输入参数、局部变量的值以及临时变量的空间。

  3. 调用者保存的寄存器(Caller-Saved Registers):保存了在函数调用之前由调用者保存的寄存器的值。

  4. 栈指针(Stack Pointer):记录了当前栈帧的边界,即栈顶位置。

4.2 函数调用为什么用到栈?

假设A函数调用B函数,B函数调用C函数,那么函数调用时栈的运作方式如下图所示:

当A函数调用B函数时,A的状态信息(如返回地址、参数等)被压入栈中,同时分配一块新的栈帧给B函数来保存B的状态信息。当B函数再调用C函数时,B的状态信息也会被压入栈中,C函数同样会被分配一块新的栈帧来保存C的状态信息。

当C函数执行完毕后,控制权回到B函数,并弹出C的栈帧,恢复到B函数的执行状态。同样,当B函数执行完毕后,控制权回到A函数,并弹出B的栈帧,恢复到A函数的执行状态。

函数调用使用栈的主要原因是为了实现函数调用的嵌套和返回的正确性。

当一个函数被调用时,它的参数、局部变量以及一些其他信息需要被保存起来,以便在函数执行完毕后能够被恢复。这些信息通常保存在函数的栈帧中。同时,调用者也需要存储一些信息,例如调用该函数前的执行状态,以便在函数返回后能够正确恢复。因此,调用者的信息也需要保存在栈中。

栈作为一种后进先出(LIFO,Last-In-First-Out)的数据结构,非常适合用于保存函数调用相关的信息。每当调用一个函数时,会创建一个新的栈帧,并将其压入栈中,即将栈指针下移。而当函数返回时,栈帧就会被弹出,即将栈指针上移,使得上一个栈帧成为当前栈帧。

5. 函数调用机制

一个简单的C语言代码:main函数调用A函数,A函数调用B函数,B函数调用C函数。

#include <stdio.h>int C(int z){return z;
}int B(int y){int res = C(3);return y + res;
}int A(int x){int res = B(2);return x + res;
}int main(){int a = A(1);printf("%d",a);return 0;
}
// 打印结果
// 6

  • ①main函数调用A函数,传入参数1
  • ②A函数调用B函数,传入参数2
  • ③B函数调用C函数,传入参数3
  • ④C函数将数值3返回给B函数
  • ⑤B函数将2和3相加的结果5返回给A函数
  • ①A函数将1和5相加的结果6返回给main函数

在程序运行的时候会创建一个栈内存区域,由于每个函数都有自己的栈帧,所以我们可以分别观察每个函数在调用时栈内存的变化。

ebp寄存器在函数调用和返回过程中起到重要的作用,主要有以下几个方面的功能:

  1. 函数调用时,ebp用于保存调用者函数的栈帧指针。当一个函数被调用时,它会在栈上分配一个新的栈帧来保存该函数的局部变量、参数和其他必要的信息。将调用者函数的ebp保存在当前函数的栈帧中,可以在当前函数执行完毕后恢复调用者函数的上下文。

  2. 在函数执行期间,ebp用于定位栈上的局部变量和函数参数。通过ebp,函数可以通过相对于EBP的偏移来访问其参数和局部变量,而不受栈帧的变化影响。这样可以在函数中方便地引用和修改局部变量和参数。

  3. 函数返回时,ebp用于恢复调用者函数的栈帧指针和上下文。当一个函数执行完毕后,它会从栈上弹出自己的栈帧,并恢复调用者函数的栈帧指针(即调用者函数的ebp)。这样就可以在返回后,继续执行调用者函数的代码,而不会导致栈帧混乱。

6. 总结

本文首先介绍了C语言的基本概念,包括数据类型、变量、表达式、流程控制语句等,从而为后续学习打下基础。在此基础上,我们阐述了函数调用机制,深入探讨了栈、栈内存和栈帧等概念,从而使读者能够全面了解函数调用的过程和栈帧的作用。

关注微信公众号:“小虎哥的技术博客”,让我们一起成为更优秀的程序员❤️!

文章和代码仓库:

gitee(推荐):https://gitee.com/cunzaizhe/xiaohuge-blog

github:https://github.com/tigerleeli/xiaohuge-blog

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

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

相关文章

【Python数据分析】数据分析之numpy基础

实验环境&#xff1a;建立在Python3的基础之上 numpy提供了一种数据类型&#xff0c;提供了数据分析的运算基础&#xff0c;安装方式 pip install numpy导入numpy到python项目 import numpy as np本文以案例的方式展示numpy的基本语法&#xff0c;没有介绍语法的细枝末节&am…

ZooKeeper基础命令和Java客户端操作

1、zkCli的常用命令操作 &#xff08;1&#xff09;Help &#xff08;2&#xff09;ls 使用 ls 命令来查看当前znode中所包含的内容 &#xff08;3&#xff09;ls2查看当前节点数据并能看到更新次数等数据 &#xff08;4&#xff09;stat查看节点状态 &#xff08;5&#xf…

【kubernetes】Argo Rollouts -- k8s下的自动化蓝绿部署

蓝绿(Blue-Green)部署简介 在现代软件开发和交付中,确保应用程序的平稳更新和发布对于用户体验和业务连续性至关重要。蓝绿部署是一种备受推崇的部署策略,它允许开发团队在不影响用户的情况下,将新版本的应用程序引入生产环境。 蓝绿部署的核心思想在于维护两个独立的环…

ESP32C3 LuatOS RC522①写入数据并读取M1卡

LuatOS RC522官方示例 官方示例没有针对具体开发板&#xff0c;现以ESP32C3开发板为例。 选用的RC522模块 ESP32C3-CORE开发板 注意ESP32C3的 SPI引脚位置&#xff0c;SPI的id2 示例代码 -- LuaTools需要PROJECT和VERSION这两个信息 PROJECT "helloworld" VERSIO…

前端list列表自定义图标并设置大小

前端list列表自定义图标并设置大小 一、前端list列表基础知识回顾 前端公有两种列表&#xff0c;一种是有序列表&#xff08;ol&#xff09;&#xff0c;一种是无序列表&#xff08;ul&#xff09;&#xff0c;它们的子元素都是&#xff08;li&#xff09;。 1.1 有序列表&a…

4.0 Spring与Dubbo整合原理与源码分析

#Dubbo# 文章介绍 Dubbo中propertie文件解析以及处理原理Dubbo中@Service注解解析以及处理原理Dubbo中@Reference注解解析以及处理原理1.0 整体架构和流程 应用启动类与配置 public class Application {public static void main(String[] args) throws Exception {Annotation…

【【萌新的STM32学习-27--USART异步通信配置步骤】】

萌新的STM32学习-27–USART异步通信配置步骤 USART/UART 异步通信配置步骤 1.配置串口工作参数 HAL_UART_Init() 我们会在此处调用MSP中的回调函数 2.串口底层初始化 用户定义HAL_UART_MspInit() 配置GPIO NVIC CLOCK 等 3.开启串口异步接收中断 HAL_UART_Receive_IT() 4.…

CXL Memory Cache 分类及 Cacheline 归属问题

&#x1f525;点击查看精选 CXL 系列文章&#x1f525; &#x1f525;点击进入【芯片设计验证】社区&#xff0c;查看更多精彩内容&#x1f525; &#x1f4e2; 声明&#xff1a; &#x1f96d; 作者主页&#xff1a;【MangoPapa的CSDN主页】。⚠️ 本文首发于CSDN&#xff0c…

单调递增的数字【贪心算法】

单调递增的数字 当且仅当每个相邻位数上的数字 x 和 y 满足 x < y 时&#xff0c;我们称这个整数是单调递增的。 给定一个整数 n &#xff0c;返回 小于或等于 n 的最大数字&#xff0c;且数字呈 单调递增 。 public class Solution {public int monotoneIncreasingDigits…

STM32+RTThread配置以太网无法ping通,无法获取动态ip的问题

记录一个非常蠢的问题&#xff0c;今天在移植rtthread的以太网驱动的时候出现无法获取动态ip的问题&#xff0c;问题如下&#xff1a; 设置为动态ip时不管是连接路由器还是电脑主机都无法ping通&#xff0c;也无法获取dns地址。 设置为静态ip时无法ping通主机。 使用wireshark…

【易售小程序项目】小程序首页完善(滑到底部数据翻页、回到顶端、基于回溯算法的两列数据高宽比平衡)【后端基于若依管理系统开发】

文章目录 说明细节一&#xff1a;首页滑动到底部&#xff0c;需要查询下一页的商品界面预览页面实现 细节二&#xff1a;当页面滑动到下方&#xff0c;出现一个回到顶端的悬浮按钮细节三&#xff1a;商品分列说明优化前后效果对比使用回溯算法实现ControllerService回溯算法 优…

stm32 iap sd卡升级

参考&#xff1a;STM32F4 IAP 跳转 APP问题_stm32程序跳转_古城码农的博客-CSDN博客 app程序改两个位置 1.程序首地址&#xff1a; 2.改中断向量表位移&#xff0c;偏移量和上面一样就可以 然后编译成bin文件就可以了

docker 笔记1

目录 1.为什么有docker ? 2.Docker 的核心概念 3.容器与虚拟机比较 3.1传统的虚拟化技术 3.2容器技术 3.3Docker容器的有什么作用&#xff1f; 3.4应用案例 4. docker 安装下载 4.1CentOS Docker 安装 4.2 Docker的基本组成 &#xff1f;&#xff08;面试&#xff09…

jmeter 性能测试工具的使用(Web性能测试)

1、下载 该软件不用安装&#xff0c;直接解压打开即可使用。 2、使用 这里就在win下进行&#xff0c;图形界面较为方便   在目录apache-jmeter-2.13\bin 下可以见到一个jmeter.bat文件&#xff0c;双击此文件&#xff0c;即看到JMeter控制面板。主界面如下&#xff1a; 3、创…

DEtection TRansformer (DETR) 与 You Only Look Once (YOLO)

曾经想过计算机如何分析图像&#xff0c;识别并定位其中的物体吗&#xff1f;这正是计算机视觉领域的目标检测所完成的任务。DEtection TRansformer&#xff08;DETR&#xff09;和You Only Look Once&#xff08;YOLO&#xff09;是目标检测的两种重要方法。YOLO已经赢得了作为…

【RISC-V】RISC-V寄存器

一、通用寄存器 32位RISC-V体系结构提供32个32位的整型通用寄存器寄存器别名全称说明X0zero零寄存器可做源寄存器(rs)或目标寄存器(rd)X1ra链接寄存器保存函数返回地址X2sp栈指针寄存器指向栈的地址X3gp全局寄存器用于链接器松弛优化X4tp线程寄存器常用于在OS中保存指向进程控…

回归预测 | MATLAB实现IBES-ELM改进的秃鹰搜索优化算法优化极限学习机多输入单输出回归预测(多指标,多图)

回归预测 | MATLAB实现IBES-ELM改进的秃鹰搜索优化算法优化极限学习机多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现IBES-ELM改进的秃鹰搜索优化算法优化极限学习机多输入单输出回归预测&#xff08;多指标&#xff0c;多图…

手撕二叉平衡树

今天给大家带来的是平衡树的代码实现&#xff0c;如下&#xff1a; #pragma once #include <iostream> #include <map> #include <set> #include <assert.h> #include <math.h> using namespace std; namespace cc {template<class K, clas…

CXL寄存器介绍(2)- CXL DVSEC

&#x1f525;点击查看精选 CXL 系列文章&#x1f525; &#x1f525;点击进入【芯片设计验证】社区&#xff0c;查看更多精彩内容&#x1f525; &#x1f4e2; 声明&#xff1a; &#x1f96d; 作者主页&#xff1a;【MangoPapa的CSDN主页】。⚠️ 本文首发于CSDN&#xff0c…

TiDB 一栈式综合交易查询解决方案获“金鼎奖”优秀金融科技解决方案奖

日前&#xff0c;2023“金鼎奖”评选结果揭晓&#xff0c; 平凯星辰&#xff08;北京&#xff09;科技有限公司研发的 TiDB 一栈式综合交易查询解决方案获“金鼎奖”优秀金融科技解决方案奖 &#xff0c; 该方案已成功运用于 多家国有大行、城商行和头部保险企业 。 此次获奖再…