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和b

int 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和b

bool isEqual = a == b;  // 是否相等
bool isLess = a < b;  // 是否小于
bool isGreater = a > b;  // 是否大于

逻辑运算符:

bool isTrue = true, isFalse = false;  // 定义变量isTrue和isFalse

bool andResult = isTrue && isFalse;  // 逻辑与
bool orResult = isTrue || isFalse;  // 逻辑或
bool notResult = !isTrue;  // 逻辑非

三目运算符:

int a = 10, b = 20;  // 定义变量a和b

int max = a > b ? a : b;  // 如果a>b,max等于a,否则等于b

1.5 控制流

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

if语句:

int a = 10, b = 20;  // 定义变量a和b

if(a > b) {  // 如果a>b,执行以下语句
    printf("a大于b\n");
} else {  // 否则执行以下语句
    printf("a小于等于b\n");
}

for循环:

int i;  // 声明计数器变量i

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

while循环:

int i = 0;  // 声明计数器变量i

while(i < 10) {  // 循环10次
    printf("%d\n", i);  // 打印当前计数器的值

    i++;  // 计数器每次循环自增1
}

switch语句:

int a = 1;  // 定义变量a

switch(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.mfbz.cn/a/102001.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

3D 碰撞检测

推荐&#xff1a;使用 NSDT场景编辑器快速搭建3D应用场景 轴对齐边界框 与 2D 碰撞检测一样&#xff0c;轴对齐边界框 &#xff08;AABB&#xff09; 是确定两个游戏实体是否重叠的最快算法。这包括将游戏实体包装在一个非旋转&#xff08;因此轴对齐&#xff09;的框中&#…

计算机毕业设计 校园二手交易平台 Vue+SpringBoot+MySQL

作者主页&#xff1a;Designer 小郑 作者简介&#xff1a;Java全栈软件工程师一枚&#xff0c;来自浙江宁波&#xff0c;负责开发管理公司OA项目&#xff0c;专注软件前后端开发、系统定制、远程技术指导。CSDN学院、蓝桥云课认证讲师&#xff0c;全栈领域优质创作者。 项目内容…

【C++入门】命名空间、缺省参数、函数重载、引用、内联函数

​&#x1f47b;内容专栏&#xff1a; C/C编程 &#x1f428;本文概括&#xff1a; C入门学习必备语法 &#x1f43c;本文作者&#xff1a; 阿四啊 &#x1f438;发布时间&#xff1a;2023.9.3 前言 C是在C的基础之上&#xff0c;容纳进去了面向对象编程思想&#xff0c;并增加…

Android JNI系列详解之ndk-build工具的使用

一、Android项目中使用ndk-build工具编译库文件 之前介绍过CMake编译工具的使用&#xff0c;今天介绍一种ndk自带的编译工具ndk-build的使用。 ndk-build目前主要有两种配置使用方式&#xff1a; 如上图所示&#xff0c;第一种方式是Android.mkApplication.mkgradle的方式生成…

plumelog介绍与应用-一个简单易用的java分布式日志系统

官方文档&#xff1a;http://www.plumelog.com/zh-cn/docs/FASTSTART.html 简介 无代码入侵的分布式日志系统&#xff0c;基于log4j、log4j2、logback搜集日志&#xff0c;设置链路ID&#xff0c;方便查询关联日志基于elasticsearch作为查询引擎高吞吐&#xff0c;查询效率高全…

UE4/5在蓝图细节面板中添加函数按钮(蓝图与c++的方法)

目录 在细节面板中添加按钮使用函数 蓝图的方法 事件 函数 效果 uec的方法 效果 在细节面板中添加按钮使用函数 很多时候&#xff0c;我们可以看到一些插件的actor类中&#xff0c;点击一下之后就可以实现如矩阵一样的效果。 实际上是因为其使用了函数来修改了蓝图中的数…

给oracle逻辑导出clob大字段、大数据量表提提速

文章目录 前言一、大表数据附&#xff1a;查询大表 二、解题思路1.导出排除大表的数据2.rowid切片导出大表数据Linux代码如下&#xff08;示例&#xff09;&#xff1a;Windows代码如下&#xff08;示例&#xff09;&#xff1a;手工执行代码如下&#xff08;示例&#xff09;&…

VR司法法治教育平台,沉浸式课堂教学培养刑侦思维和能力

VR司法法治教育平台提供了多种沉浸式体验&#xff0c;通过虚拟现实(Virtual Reality&#xff0c;简称VR)技术让用户深度参与和体验法治知识。以下是一些常见的沉浸式体验&#xff1a; 1.罪案重现 VR司法法治教育平台可以通过重现真实案例的方式&#xff0c;让用户亲眼目睹罪案发…

2023年“羊城杯”网络安全大赛 Web方向题解wp 全

团队名称&#xff1a;ZhangSan 序号&#xff1a;11 不得不说今年本科组打的是真激烈&#xff0c;初出茅庐的小后生没见过这场面QAQ~ D0n’t pl4y g4m3!!! 简单记录一下&#xff0c;实际做题踩坑很多&#xff0c;尝试很多。 先扫了个目录&#xff0c;扫出start.sh 内容如下…

P1886 滑动窗口 /【模板】(双端队列)+双端队列用法

例题 有一个长为 n 的序列 a&#xff0c;以及一个大小为 k 的窗口。现在这个从左边开始向右滑动&#xff0c;每次滑动一个单位&#xff0c;求出每次滑动后窗口中的最大值和最小值。 例如&#xff1a; The array is [1,3,−1,−3,5,3,6,7],and k3。 输入格式 输入一共有两行…

SpringBoot Mybatis 多数据源 MySQL+Oracle+Redis

一、背景 在SpringBoot Mybatis 项目中&#xff0c;需要连接 多个数据源&#xff0c;连接多个数据库&#xff0c;需要连接一个MySQL数据库和一个Oracle数据库和一个Redis 二、依赖 pom.xml <dependencies><dependency><groupId>org.springframework.boot&l…

python 深度学习 解决遇到的报错问题4

目录 一、DLL load failed while importing _imaging: 找不到指定的模块 二、Cartopy安装失败 三、simplejson.errors.JSONDecodeError: Expecting value: line 1 column 1 (char 0) 四、raise IndexError("single positional indexer is out-of-bounds") 五、T…

LLMs:OpenAI官方重磅更新——新增GPT-3.5Turbo调和API更新功能

LLMs&#xff1a;OpenAI官方重磅更新——新增GPT-3.5Turbo调和API更新功能 导读&#xff1a;2023年8月22日&#xff0c;OpenAI官方发布&#xff0c;开发者现在可以使用自己的数据来定制适用于其用例的GPT-3.5 Turbo模型。GPT-3.5 Turbo的微调现在已经可用&#xff0c;GPT-4的微…

redis 应用 4: HyperLogLog

我们先思考一个常见的业务问题&#xff1a;如果你负责开发维护一个大型的网站&#xff0c;有一天老板找产品经理要网站每个网页每天的 UV 数据&#xff0c;然后让你来开发这个统计模块&#xff0c;你会如何实现&#xff1f; img 如果统计 PV 那非常好办&#xff0c;给每个网页一…

Axure RP仿QQ音乐app高保真原型图交互模板源文件

Axure RP仿QQ音乐app高保真原型图交互模板源文件。本套素材模板的机型选择华为的mate30&#xff0c;在尺寸和风格方面&#xff0c;采用标准化制作方案&#xff0c;这样做出来的原型图模板显示效果非常优秀。 原型中使用大量的动态面板、中继器、母版&#xff0c;涵盖Axure中技…

【笔记】PyCharm快捷键大全

PyCharm是一种Python集成开发环境&#xff08;IDE&#xff09;&#xff0c;由JetBrains公司开发。它被认为是Python开发中最强大、最流行的IDE之一。PyCharm具有完整的Python开发工具链&#xff0c;包括先进的代码编辑器、代码分析工具、集成的调试器、版本控制系统集成、自动化…

Flink 如何处理反压?

分析&回答 什么是反压&#xff08;backpressure&#xff09; 反压通常是从某个节点传导至数据源并降低数据源&#xff08;比如 Kafka consumer&#xff09;的摄入速率。反压意味着数据管道中某个节点成为瓶颈&#xff0c;处理速率跟不上上游发送数据的速率&#xff0c;而…

关于Comparable、Comparator接口返回值决定顺序的问题

Comparable和Comparator接口都是实现集合中元素的比较、排序的&#xff0c;下面先简单介绍下他们的用法。 1. 使用示例 public class Person {private String name;private Integer age;public Person() {}public Person(String name, Integer age) {this.name name;this.ag…

MySQL高阶语句(三)

一、NULL值 在 SQL 语句使用过程中&#xff0c;经常会碰到 NULL 这几个字符。通常使用 NULL 来表示缺失 的值&#xff0c;也就是在表中该字段是没有值的。如果在创建表时&#xff0c;限制某些字段不为空&#xff0c;则可以使用 NOT NULL 关键字&#xff0c;不使用则默认可以为空…

自动化运维工具Ansible之playbooks剧本

自动化运维工具Ansible之playbooks剧本 一、playbooks1.playbooks简述2.playbooks剧本格式3.playbooks组成部分 二、实例1.编写脚本2.运行playbook3.定义、引用变量4.指定远程主机sudo切换用户5.when条件判断6.迭代7.Templates 模块8.tags 模块9.Roles 模块 三、编写应用模块1.…
最新文章