C#开发笔记概述

该文章的最新版本已迁移至个人博客【比特飞】,单击链接 https://www.byteflying.com/archives/962 访问。

从A函数跳转到B函数,在B函数执行完毕后,程序为什么能精确的返回到A函数中未执行完的代码区域?

首先,我们要知道什么是栈和栈帧。

栈是一种特殊的线性表,仅能在线性表的一端-栈顶进行操作,栈底不允许操作。

栈的特性:后进先出(Last In First Out or LIFO)

栈帧是一个函数执行的环境:函数参数、函数的局部变量、函数执行完后返回到哪里等。

为了我们不会因为文字的描述而产生歧义,我们约定程序从左向右运行,左边为前,右边为后。

从函数A跳转到函数B时,将函数的执行环境压入栈中,显然栈帧就是栈中所需要存放的数据。函数B执行完成后,运行环境知道,刚执行完成了一个函数,现在要回到某个地方了,那么回到哪里呢?当然是从栈中弹出最近一个栈帧(如果有的话)并取出相关信息即可。

这堪称是一个完美的设计!这种设计为函数式编程带来了高可靠性的同时也带来了性能损失。事实上运行环境维护这样的数据结构并不轻松,首先我们需要一个特殊的数据区域来存放这个栈,这个栈通常被称为“调用堆栈”(Call Stack),并且通常有 1M 的空间限制(注意这个值是可以改的)和  次的数量限制(32位系统,一般情况下 1M 的空间限制首先到达)。即每次从一个函数跳转到另外一个函数会使栈增加一个计数并存放栈帧信息,下次程序执行路径在函数终点处需要返回时又要从栈中取出栈顶信息(如果有的话)。但情况并非总是如此!

一般情况下总是需要这样的一个栈帧,但如果函数A跳转到函数B时,该处已经是函数A的最后一句是又会怎么样呢?显然,这个栈帧不是必须的,因为返回此处时,由于函数A也即将结束,又会往前返回到上一个栈帧,那何不直接返回到上一个栈帧呢?事实上现代编译器都会为这种情况做出优化,运行时不会为其增加栈帧,而是在函数B运行完成后,直接返回至函数A之前所存放的栈帧信息(如果有的话)。显然递归属于特殊函数的跳转,它跳转到其本身。

再来看看什么是尾递归。

如果一个函数中所有递归形式的调用都出现在函数的末尾(逻辑上的末尾或者说代码路径的末尾),我们称这个递归函数是尾递归的。当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。

我们通过1个案例来具体的分析一下,之后再回头看上面的描述可能会更加清楚的了解运行环境是如何追踪函数的运行状态的。

public class Program {

    public static void Main(string[] args) {
Add(100);
Add(95);
Add(80);
Console.ReadKey();
} private static void Add(int score) {
#line 100
if(score == 100) {
Perfect();
}
//请注意这里是另起if
#line 200
if(score >= 90) {
Excellent();
}
#line 300
else {
ComeOn();
}
} private static void Perfect() {
Console.WriteLine("Perfect!");
} private static void Excellent() {
Console.WriteLine("Excellent!");
} private static void ComeOn() {
Console.WriteLine("ComeOn!");
} }

我们先来分析 Add(100) 的执行,Main->Add->Perfect,每次跳转函数都会增加一个栈帧,以便程序执行可以沿着 Perfect->Add->Main 这样的路径往前返回。在这个过程中显然每次函数的跳转都会增加栈帧。

如果你已经明白了 Add(100) 的执行过程,那么现在要分析的 Add(90) 的执行过程可能不会太难。但需要注意到的一点是,当分数为90的时候,第100行的代码(#line 100)被执行到的时候,运行环境已经知道这是最后一行可以被执行到的代码,因为 if else 中只有一个可以被命中的路径,所以程序往前返回时的路径是这样的 Excellent->Add->Main (红色删除部分表示未被返回) 。因为 Add(90) 跳转到 Excellent 方法时,没有为其增加栈帧,因为完全没有必要。显然 Add(80) 也是这样的。

接一下,我们再看看函数有返回值时,会出现什么情况。

private static void Add(int score) {
string description = string.Empty;
#line 100
if(score == 100) {
description = Perfect();
}
//请注意这里是另起if
#line 200
if(score >= 90) {
description = Excellent();
}
#line 300
else {
description = ComeOn();
}
} private static string Perfect() {
return "Perfect!";
} private static string Excellent() {
return "Excellent!";
} private static string ComeOn() {
return "ComeOn!";
}

在分析这种情况前,我们再来回顾一下尾递归的概念:

该文章的最新版本已迁移至个人博客【比特飞】,单击链接 https://www.byteflying.com/archives/962 访问。

如果一个函数中所有递归形式的调用都出现在函数的末尾(逻辑上的末尾或者说代码路径的末尾),我们称这个递归函数是尾递归的。当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。

显然,当需要这个函数的返回值时,其不属于尾递归。

C#开发笔记之06-为什么要尽可能的使用尾递归,编译器会为它做优化吗?的更多相关文章

  1. C#开发笔记,点点细微,处处真情,记录开发中的难言之隐

    该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/956 访问. 概述 本系列文章将会向大家介绍本人实际开发过程中所遇到技术 ...

  2. 驱动开发学习笔记. 0.06 嵌入式linux视频开发之预备知识

    驱动开发读书笔记. 0.06  嵌入式linux视频开发之预备知识 由于毕业设计选择了嵌入式linux视频开发相关的项目,于是找了相关的资料,下面是一下预备知识 UVC : UVC,全称为:USB v ...

  3. TERSUS无代码开发(笔记06)-简单实例手机端页面设计

    手机端的设计 1.页面说明 2.默认页面===>提交请假单(上面页面双击进入,页面主要编辑区) 2.1默认页面===>提交请假单===>头部区(页面部份主要编辑区01) 2.1.1默 ...

  4. cocos2dx3.0 超级马里奥开发笔记(两)——正确的规划游戏逻辑

    我将不得不拿出一个完整的开发笔记.由于个人原因.代码已OK该,博客,那么就不要粘贴代码,直接解释了整个游戏设计,更确切地说,当新手应该注意的地方发展. 1.继承类和扩展作用的权----展阅读(MVC) ...

  5. iOS回顾笔记(06) -- AutoLayout从入门到精通

    iOS回顾笔记(06) -- AutoLayout从入门到精通 随着iOS设备屏幕尺寸的增多,当下无论是纯代码开发还是Xib/StoryBoard开发,自动布局已经是必备的开发技能了. 我使用自动布局 ...

  6. Java开发笔记(四十)日期与字符串的互相转换

    前面介绍了如何通过Date工具获取各个时间数值,但是用户更喜欢形如“2018-11-24 23:04:18”这种结构清晰.简洁明了的字符串,而非啰里八唆依次汇报每个时间单位及其数值的描述.既然日期时间 ...

  7. 安卓开发笔记——打造万能适配器(Adapter)

    为什么要打造万能适配器? 在安卓开发中,用到ListView和GridView的地方实在是太多了,系统默认给我们提供的适配器(ArrayAdapter,SimpleAdapter)经常不能满足我们的需 ...

  8. 【转】Android开发笔记——圆角和边框们

    原文地址:http://blog.xianqu.org/2012/04/android-borders-and-radius-corners/ Android开发笔记——圆角和边框们 在做Androi ...

  9. Java开发笔记(一百零一)通过加解锁避免资源冲突

    前面介绍了如何通过线程同步来避免多线程并发的资源冲突问题,然而添加synchronized的方式只在简单场合够用,在一些高级场合就暴露出它的局限性,包括但不限于下列几点:1.synchronized必 ...

随机推荐

  1. [Qt2D绘图]-06QPainter的复合模式&&双缓冲绘图&&绘图中的其他问题

    本篇读书笔记主要记录QPainter的复合模式&&双缓冲绘图&&绘图中的其他问题   大纲:     复合模式     双缓冲绘图     绘图中的其他问题       ...

  2. OSCP Learning Notes - Enumeration(3)

    SMB Enumeration 1. Set the smb configurations. locate smb.conf vim /etc/samba/smb.conf Insert the gl ...

  3. 集训 T3-难题

    大致题意: 求方程 \(a_1+a_2+...+a_n=m\) 的非负整数解有几个. 基本思路: 深搜,虽然看起来可能会超时,但是对于100%的数据,0<=n,m<32767,结果< ...

  4. 在 CentOS 7(Linux)上部署ASP.NET Core 2.2 Web应用程序(Tengine、Asp.Net Core MVC、Centos 7、MySql)

    一.前言 1.简单记录一下Linux CentOS 7中安装与配置Tengine的详细步骤. 2.简单比较一下Tengine 和Nginx 3.搭建Asp.net Core和部署 Web程序 4.总结 ...

  5. swagger -- 前后端分离的API接口

    文章目录 一.背景 二.swagger介绍 三.在maven+springboot项目中使用swagger 四.swagger在项目中的好处 五.美化界面 参考链接:5分钟学会swagger配置 参考 ...

  6. C++语法小记---少见的语法之一

    很少用,列出来,便于理解和熟悉!!! // 1.单独使用位域限定符 ::xxx() //调用全局函数xxx // 2.全局重载new和delete T* tmp = (T*)(::operator n ...

  7. WPF 有缩放时显示线条的问题

    公司项目已经开发好几年了,用的WPF开发的,期间遇到好多问题,都是些小细节.很久没有写博客了,以后有时间还是需要写写博客啊!作为分享也好.记录也好,利人利己嘛. 今天主要说一下显示线条的问题,因为我们 ...

  8. Oracle可视化工具连接

    Oracle可是化工具有很多,以下只列举sql developer和sql plus这两款连接方式 sql developer: SQL Develope启动后,需要创建一个数据库连接,只有创建了数据 ...

  9. DeviceEventEmmiter使用

    发送广播一个事件 DeviceEventEmitter.emit('updatePlantList', '创建工厂成功');//通知刷新工厂列表 接收处,添加监听(监听要再事件发生之前添加,否则无法回 ...

  10. 大一寒假我在一个oj网站只刷了这些题从此入门了绝大部分通用算法

    如果你想入门算法,那么我这篇文章也许可以帮到你. 先说点题外话.这是在一个不冷不热的寒假,照理来说寒假应该很冷,但这个寒假是真的舒服.这样舒服的寒假学习似乎是一件不可能的事情,所以我继续我的游戏生涯, ...