当前位置: 首页 > news >正文

C专题5:函数进阶和递归

1.函数进阶

1.1编译链接和内存布局

系统栈的工作原理 - BattleHeart - 博客园具体可以参考这个博客,这里只简单提及一下。

        根据不同的操作系统,一个进程可能被分配到不同的内存区域去执行。但是不管什么样的操作系统、什么样的计算机架构,进程使用的内存都可以按照功能大致分为以下4个部分:
1)代码区:这个区域存储着被装入执行的二进制机器代码,处理器(CUP)会到这个区域取指并执行。
2)数据区:用于存储全局变量,静态全局变量,静态局部变量,字符串常量等。
3)堆区:进程可以在堆区动态地请求一定大小的内存,并在用完之后归还给堆区。动态分配和回收是堆区的特点。
4)栈区:函数被调时分配栈区,用于存放函数的参数值,局部变量等值;还要动态地存储函数之间的关系,以保证被调用函数在返回时恢复到被调用函数中继续执行。

​​1.2 函数调用机制​​

局部变量占用的内存是在程序执行过程中“动态”地建立和释放的。这种“动态”是通过栈由系统自动管理进行的。当任何一个函数调用发生时,系统都要作以下工作:
1)建立栈帧空间;
2)保护现场:主调函数运行状态和返回地址入栈;
3)为被调函数传递数据(进行实参和形参的结合),同时形参获得存储空间;接着给局部变量分配空间;
4)执行被调函数函数体;
5)当被调函数执行完成,释放被调函数中局部变量占用的栈空间;
6)恢复现场:取主调函数运行状态及返回地址,释放栈帧空间;
7)继续主调函数后续语句。

1.3 作用域(可见性)与生存期(局部变量和全局变量)

        作用域指标识符能够被使用的范围。只有在作用域内标识符才可以被访问(称为可见性)。
        变量和函数的可见性是针对编译和链接过程,生存期是针对程序的运行(进程)。当程序没有编译和链接成功,就不要去分析变量和函数的生存期。

1.3.1局部域

        局部域包括块域和函数原型域。任何标识符作用域的起始点均为标识符说明处。图解块作用域(可见性): 

总结:局部变量具有局部作用域使得程序在不同块中可以使用同名变量。这些同名变量各自在自己的作用域中可见,在其它地方不可见。
当函数域内定义的变量名称与块内定义的变量名称同名时,编译器的原则是局部有先原则。

        对于块中嵌套其它块的情况,如果嵌套块中有同名局部变量,服从局部优先原则,即在内层块中屏蔽外层块中的同名变量,换句话说,内层块中局部变量的作用域为内层块;外层块中局部变量的作用域为外层除去包含同名变量的内层块部分。 

1.3.2文件作用域(全局作用域)

 总结:​​

可见性从另一个角度说明标识符的有效性,可见性与作用域具有一定的一致性。标识符的作用域包含可见范围,可见范围不会超过作用域。可见性在理解同名标识符的作用域嵌套时十分直观。对于外层块与内层块定义了同名标识符的,在外层作用域中,内层所定义的标识符是不可见的,即外层引用的是外层所定义的标识符;同样,在内层作用域中,外层的标识符将被内层的同名标识符屏蔽,变得不可见,即外层中同名标识符的可见范围为作用域中挖去内层块的范围。

1.4 生存期

        存储类型决定了变量的生命周期,变量生命期指从获得空间到空间释放之间的时期(程序的执行过程)。
        存储类型的说明符有四个:auto,register,static和extern。前两者称为自动类型,后两者分别为静态和外部类型。
​​auto:​​前面提到的局部变量都是自动类型。其空间分配于块始,空间释放于块终,且由系统自动进行。自动变量保存在栈中,且是在程序运行过程中获得和释放空间,未初始化时值为随机数,现在定义局部变量不在使用auto修饰。
​​register:​​为提高程序运行效率,可以将某些变量保存在寄存器中,即说明为寄存器变量,但不提倡使用。
​​static:​​静态变量。根据被修饰变量的位置不同,分为局部(内部)静态变量和全局(外部)静态变量。所有静态变量存放在数据区,编译时获得存储空间,未初始化时自动全0,且只初始化一次。
​​extern:​​意为“外来的”,它的作用在于告诉编译器:有这个变量,它可能不存在当前的文件中,但它肯定要存在于工程中的某一个源文件中。

        在函数内部或块中定义的标识符具有局部生命期,其生命期开始于执行到该函数或块的标识符声明处,结束于该函数或块的结束处。具有局部生命期的标识符存放在栈区。具有局部生命期的标识符如果未被初始化,其内容是随机的,不可用。具有局部生命期的标识符必定具有局部作用域;但反之不然,

        静态生命期指的是标识符从程序开始运行时存在,即具有存储空间,到程序运行结束时消亡,即释放存储空间。具有静态生命期的标识符存放在静态数据区,属于静态存储类型,如全局变量、静态全局变量、静态局部变量。具有静态生命期的标识符将在未被用户初始化的情况下,系统会自动将其初始化为0。
        函数驻留在代码区,也具有静态生命期。所有具有文件作用域的标识符都具有静态生命期。 

    局部静态变量的总结:局部静态变量的的作用域为块域,但生命期为整个文件。即当块结束时,局部静态变量空间仍然保持,直到整个程序文件结束时该局部静态变量空间才释放,生命期结束。
static 总结:
常见的两种用途:
1)统计函数被调用的次数。
2)减少局部数组建立和赋值的开销,变量的建立和赋值是需要一定的处理器开销的,特别是数组等含有较多元素的存储类型。在一些含有较多的变量并且被经常调用的函数中,可以将一些数组声明为 static 类型,以减少建立或者初始化这些变量的开销。(主要用途)

详细说明:
1)局部变量用static修饰,会被放在程序的.data存储区中,这样可以在下一次调用的时候还可以保持原来的赋值。这一点是它与栈变量的区别。
2)局部变量用static修饰,可见性不变(作用域),生存期改变;全局变量用static修饰,可见性该变(作用域),生存期不变。
3)当static用来修饰全局变量时,它就改变了全局变量的作用域,使其不能被别的程序extern使用,限制在了当前文件里,但是没有改变其存放位置,还是在静态.data存储区。
使用注意:
1)若全局变量仅在单个C文件中访问,则可以将这个变量修改为静态全局变量,以降低模块间的耦合度;
2)若全局变量仅由单个函数访问,则可以将这个变量改为该函数的静态局部变量,以降低模块间的耦合度;
3)设计和使用访问动态全局变量、静态全局变量、静态局部变量的函数时,需要考虑重入问题(只要输入数据相同就应产生相同的输出)。

extern:(只能修饰全局变量和函数,不能修饰局部变量,也不能修饰静态局部变量)
        外部存储类型包括外部变量和外部函数。在由多个源程序文件组成的程序中,如果一个文件要使用另一个文件中定义的全局变量或函数,这些源程序文件之间通过外部类型的变量和函数进行沟通。
        在一个文件中定义的全局变量和函数都缺省为外部的,即其作用域可以延伸到程序的其他文件中。但其他文件如果要使用这个文件中定义的全局变量和函数,必须在使用前用“extern”作外部声明,外部声明通常放在文件的开头。
        变量定义时编译器为其分配存储空间,而变量声明指明该全局变量已在其他地方说明过,编译系统不再分配存储空间,直接使用变量定义时所分配的空间。函数声明缺省为外部的,因此修饰词extern通常省略。

2.分治策略与递归

分治策略​​
        是将规模比较大的问题可分割成规模较小的相同问题。问题不变,规模变小。这自然导致递归过程的产生。分治与递归像一对孪生兄弟,经常同时应用在算法设计之中,并由此产生许多高效算法。
​​递归​​
        若一个函数直接地或间接地调用自己,则称这个函数是递归的函数。(简单地描述为“自己调用自己”)。
​​分治法所能解决的问题一般具有以下四个特征​​
● 该问题的规模缩小到一定的程度就可以容易地解决。
● 该问题可以分解为若干个规模较小的相同问题。
● 使用小规模的解,可以合并成该问题原规模的解。
● 该问题所分解出的各个子规模是相互独立的。

​​2.1 分治法步骤​​

在分治策略中递归地求解一个问题,在每层递归中应用如下三个步骤:
​​分解​​:将问题划分成一些子问题,子问题的形式与原问题一样,只是规模更小。
​​解决​​:递归地求解子问题。如果子问题的规模足够小,则停止递归,直接求解。
​​合并​​:将小规模的解组合成原规模问题的解。

示例​​:求解n的阶乘。(不考虑int溢出)
分析: 

 阶乘可递归的定义为:

        递归函数的执行分为“递推”和“回归”两个过程,这两个过程由递归终止条件控制,即逐层递推,直至递归终止条件满足,终止递归,然后逐层回归。
        递归调用同普通的函数调用一样,每当调用发生时,就要分配新的栈帧(形参数据,现场保护,局部变量);而与普通的函数调用不同的是,由于递推的过程是一个逐层调用的过程,因此存在一个逐层连续的分配栈帧过程,直至遇到递归终止条件时,才开始回归,这时才逐层释放栈帧空间,返回到上一层,直至最后返回到主调函数。

 

http://www.dtcms.com/a/285609.html

相关文章:

  • 最小生成树算法详解
  • 2025外卖江湖:巨头争霸,谁主沉浮?
  • 洞见AI时代数据底座的思考——YashanDB亮相2025可信数据库发展大会
  • NIO网络通信基础
  • AndroidX中ComponentActivity与原生 Activity 的区别
  • 关于字符编辑器vi、vim版本的安装过程及其常用命令:
  • 从抓包GitHub Copilot认证请求,认识OAuth 2.0技术
  • web3 区块链技术与用
  • 基于深度学习的语音识别:从音频信号到文本转录
  • 开源的大语言模型(LLM)应用开发平台Dify
  • 如何用Python并发下载?深入解析concurrent.futures 与期物机制
  • 服务攻防-Java组件安全FastJson高版本JNDI不出网C3P0编码绕WAF写入文件CI链
  • ARM64高速缓存,内存属性及MAIR配置
  • 预测导管原位癌浸润性复发的深度学习:利用组织病理学图像和临床特征
  • Nand2Tetris(计算机系统要素)学习笔记 Project 3
  • sqli(1-8)
  • ASP.NET Core Web API 内存缓存(IMemoryCache)入门指南
  • Pytorch下载Mnist手写数据识别训练数据集的代码详解
  • PyTorch新手实操 安装
  • 填坑 | React Context原理
  • SpringMVC + Tomcat10
  • 小结:Spring MVC 的 XML 的经典配置方式
  • 计算机视觉与机器视觉
  • Tensorflow小白安装教程(包含GPU版本和CPU版本)
  • C++并发编程-13. 无锁并发队列
  • div和span区别
  • 【Python】python 爬取某站视频批量下载
  • 前端实现 web获取麦克风权限 录制音频 (需求:ai对话问答)
  • 20250718【顺着234回文链表做两题反转】Leetcodehot100之20692【直接过12明天吧】今天计划
  • AugmentCode还没对个人开放?