04. 函数和函数调用机制

news2025/4/18 11:08:58

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.coloradmin.cn/o/966742.html

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

相关文章

Linux系统中驱动入门设备树DTS(经典)

设备树&#xff08;DTS:device tree source&#xff09;&#xff0c;字面意思就是一块电路板上设备如上图中CPU、DDR、I2C、GPIO、SPI等&#xff0c;按照树形结构描绘成的一棵树。按照策略和功能分离的思路&#xff0c;就是驱动代码&#xff08;功能&#xff09;和设备树DTS配置…

算法通关村16关 | 滑动窗口如此简单

1. 滑动窗口基本思想 滑动窗口就是快慢指针问题&#xff0c;快指针走一步&#xff0c;慢指针走一步&#xff0c;维护窗口的大小。 下图有大小为3的滑动窗口移动示例。 窗口&#xff1a;窗口是left和right之间的元素&#xff0c;也可以理解为一个区间。窗口的大小可能固定&#…

Win7设备和打印机里空白,0个对象,但是可以打印的处理办法

呉師傅 你是不是遇到过Win7系统打开设备和打印机的时候显示是空白的&#xff0c;0个设备的情况&#xff1f;要怎么操作才能解决这一问题呢&#xff0c;下面就分享一下如何处理这个问题的小方法大家可以尝试一下。 问题如下&#xff1a; 解决方法&#xff1a; 1、点击桌面左下…

Unity中Shader的消融视觉效果优化smoothstep(min,max,x)

文章目录 前言Unity中Shader的消融视觉效果优化 一、在clip(value) 的 基础上 用 smoothstep(min,max,x)&#xff0c;并且增加一个渐变纹理对消融边缘进行视觉上的优化二、进行优化 前言 Unity中Shader的消融视觉效果优化 一、在clip(value) 的 基础上 用 smoothstep(min,max…

3.0 Dubbo的可扩展机制SPI源码解析

1. Dubbo SPI 架构图 2. Demo ExtensionLoader<Protocol> extensionLoader ExtensionLoader.getExt ensionLoader(Protocol.class); Protocol http extensionLoader.getExtension("dubbo"); System.out.println(http); 上⾯这个Demo就是Dubbo常⻅的写法&am…

java八股文面试[多线程]——newWorkStealingPool

newWorkStealingPool是什么&#xff1f; newWorkStealingPool简单翻译是任务窃取线程池。 newWorkStealingPool 是Java8添加的线程池。和别的4种不同&#xff0c;它用的是ForkJoinPool。 使用ForkJoinPool的好处是&#xff0c;把1个任务拆分成多个“小任务”&#xff0c;把这…

【MySQL学习笔记】(七)内置函数

内置函数 日期函数示例案例-1案例-2 字符串函数示例 数学函数其他函数 日期函数 示例 获得当前年月日 mysql> select current_date(); ---------------- | current_date() | ---------------- | 2023-09-03 | ---------------- 1 row in set (0.00 sec)获得当前时分秒…

剑指 Offer 57 - II. 和为s的连续正数序列(简单)

题目&#xff1a; class Solution { public:vector<vector<int>> findContinuousSequence(int target) { //本题使用滑动窗口&#xff08;双指针&#xff09;int i1, j1; //定义左右边界&#xff0c;一般是左闭右开int sum0; //窗口内的和vector&…

区块链技术与应用 - 学习笔记1【引言】

大家好&#xff0c;我是比特桃。本系列主要将我之前学习区块链技术时所做的笔记&#xff0c;进行统一的优化及整合。其中大量笔记源于视频课程&#xff1a;北京大学肖臻老师《区块链技术与应用》公开课。肖老师的课让我找回了求知若渴般的感觉&#xff0c;非常享受学习这门课的…

2023/9/3周报

目录 摘要 文献阅读1 1、标题和提出问题 2、物理模型对于水质预测的缺陷 3、模型框架 4、相关公式 5、结果分析 文献阅读2 1、标题和提出问题 2、问题叙述 3、模型框架 4、误差修补 5、实验结果和分析 总结 摘要 本周阅读了2篇论文&#xff0c;分别为一种基于深…

ReactNative 井字游戏 实战

效果展示 需要的插件准备 此实战项目需要用到两个插件。 react-native-snackbar 底部信息提示组件。 react-native-vector-icons 图标组件。 安装组件&#xff1a; npm i react-native-snackbar npm i react-native-vector-icons npm i types/react-native-vector-icons /…

uni-app之android离线自定义基座

一 为什么要自定义基座 1&#xff0c;基座其实就是一个app&#xff0c;然后新开发的页面可以直接在手机上面显示&#xff0c;查看效果。 2&#xff0c;默认的基座就是uniapp帮我们打包好的基座app&#xff0c;然后我们可以进行页面的调试。 3&#xff0c;自定义基座主要用来…

Python:列表推导式

相关阅读 Python专栏https://blog.csdn.net/weixin_45791458/category_12403403.html?spm1001.2014.3001.5482 列表推导式使得创建特定列表的方式更简洁。常见的用法为&#xff0c;对序列或可迭代对象中的每个元素应用某种操作&#xff0c;用生成的结果创建新的列表&#xff…

VSC++: 奇怪的风吹

void 奇怪的风吹() {//缘由https://ask.csdn.net/questions/1062454int aa[]{15, 30, 12, 36, 11, 20, 19, 17, 16, 18, 38, 15, 30, 12, 36, 11, 20, 19, 17, 16, 18, 38, -1},j 0, a 0, y 0, z 0;while (aa[j] > 0){if (j && aa[j] > 35 || aa[j] < 15)…

CXL.cache D2H Message 释义

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

记录一下自己对linux分区挂载的理解

一直狠模糊&#xff0c;分两个区&#xff0c;一个挂载/, 一个挂载/home 两者是什么关系 实测 先看挂载的内容 然后umount /home后创建一个新文件 再挂载回去 发现旧分区又回来了&#xff0c;说明路径只是个抽象的概念&#xff0c;分区挂载&#xff0c;互相之间数据是不影响…

只有一个线程的程序(main函数)

C并发编程入门 目录 只有一个线程的程序 就是main函数所在的线程。 这个线程无需我们手动创建&#xff0c;main函数就是这个线程的执行体。 运行main函数&#xff0c;就是执行只有一个线程的程序。 线程是进程内正在执行的代码所在的函数 代码示例 #include <iostream&…

弹性盒子的使用

一、定义 弹性盒子是一种用于按照布局元素的一维布局方法&#xff0c;它可以简便、完整、响应式地实现各种页面布局。 容器中存在两条轴&#xff0c;主轴和交叉轴(相当于我们坐标轴的x轴和y轴)。我们可以通过flex-direction来决定主轴的方向。 主轴&#xff08;main axis&am…

CentOS 7.6源码安装gdb 12.1

参考文章&#xff1a;《GDB调试-从安装到使用》 gdb --version看一下当前gdb的版本&#xff0c;可以看到是7.6.1-120.el7。 https://www.sourceware.org/gdb/download/可以下载gdb源码。 sudo nohup wget https://sourceware.org/pub/gdb/releases/gdb-12.1.tar.xz &下…

CXL.cache H2D Message 释义

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