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

Go 代码块与作用域,变量遮蔽问题详解

Go 代码块与作用域详解

文章目录

  • Go 代码块与作用域详解
    • 一、引入
    • 二、代码块 (Block)
      • 2.1 代码块介绍
      • 2.2 显式代码块
      • 2.3 隐式代码块
      • 2.4 空代码块
      • 2.5 支持嵌套代码块
    • 三、作用域 (Scope)
      • 3.1 作用域介绍
      • 3.2 作用域划定原则
      • 3.3 标识符的作用域范围
        • 3.3.1 预定义标识符作用域
        • 3.3.2 包代码块级作用域
        • 3.3.3 文件代码块作用域(包的导入作用域)
        • 3.3.4 函数体的作用域
        • 3.3.5 流程控制作用域
    • 四、避免变量遮蔽的原则
      • 4.1 变量遮蔽的根本原因
      • 4.2 变量遮蔽问题分析
        • 4.2.1 第一个问题:遮蔽预定义标识符
        • 4.2.2 第二个问题:遮蔽包代码块中的变量
        • 4.2.3 第三个问题:遮蔽外层显式代码块中的变量
    • 五、利用工具检测变量遮蔽问题

一、引入

首先我们从一个 Go 变量遮蔽(Variable Shadowing)的问题说起。

什么是变量遮蔽呢?

变量遮蔽(Variable Shadowing)是指在程序中一个作用域内的变量名(或标识符)隐藏(遮蔽)了外部作用域中相同名称的变量。这会导致在遮蔽内部作用域内,无法直接访问外部作用域的变量,因为编译器或解释器将优先选择内部作用域的变量,而不是外部的。

我们来看下面这段示例代码:

package mainimport "fmt"var x = 10 // 包级作用域的变量func main() {x := 5 // 函数内的局部变量,遮蔽了包级作用域的 xfmt.Println(x) // 输出:5
}func anotherFunction() {fmt.Println(x) // 在这个函数中,外部包级作用域的 x 是可见的,输出:10
}

你可以看到,在这段代码中,函数main内部有一个局部变量 x,它遮蔽了包级作用域的 x。因此,在main函数内部,通过变量 x 访问的是局部变量,而不是外部包级作用域的变量。然而,在anotherFunction中,没有局部变量 x,因此外部包级作用域的 x 是可见的。

二、代码块 (Block)

2.1 代码块介绍

在Go语言中,代码块是包裹在一对大括号{} 包围的声明和语句序列。

2.2 显式代码块

这些代码块是你在代码中明确可见的,由一对大括号 {} 包围。比如函数的函数体、for循环的循环体、以及其他控制结构内部的代码块。这些代码块明确定义了它们的作用域,包括变量的可见性:

func Foo() {// 这里是显式代码块,包裹在函数的函数体内// ...for {// 这里是显式代码块,包裹在for循环体内// 该代码块也是嵌套在函数体显式代码块内部的代码块// ...}if true {// 这里是显式代码块,包裹在if语句的true分支内// 该代码块也是嵌套在函数体显式代码块内部的代码块// ...}
}

2.3 隐式代码块

隐式代码块没有显式代码块那样的肉眼可见的配对大括号包裹,我们无法通过大括号来识别隐式代码块。

虽然隐式代码块身着“隐身衣”,但我们也不是没有方法来识别它,因为 Go 语言规范对现存的几类隐式代码块做了明确的定义,我们可以看下这张图:

WechatIMG221

我们按代码块范围从大到小,逐一说明:

  • 宇宙(Universe)代码块:它囊括的范围最大,所有 Go 源码都在这个隐式代码块中,你也可以将该隐式代码块想象为在所有 Go 代码的最外层加一对大括号,就像图中最外层的那对大括号那样。
  • 包代码块:在宇宙代码块内部嵌套了包代码块(Package Block),每个 Go 包都对应一个隐式包代码块,每个包代码块包含了该包中的所有 Go 源码,不管这些代码分布在这个包里的多少个的源文件中。
  • 文件代码块:在包代码块的内部嵌套着若干文件代码块(File Block),每个 Go 源文件都对应着一个文件代码块,也就是说一个 Go 包如果有多个源文件,那么就会有多个对应的文件代码块。
  • 再下一个级别的隐式代码块就在控制语句层面了,包括 ifforswitch。我们可以把每个控制语句都视为在它自己的隐式代码块里。不过你要注意,这里的控制语句隐式代码块与控制语句使用大括号包裹的显式代码块并不是一个代码块。你再看一下前面的图,switch 控制语句的隐式代码块的位置是在它显式代码块的外面的。
  • 最后,位于最内层的隐式代码块是 switchselect 语句的每个 case/default 子句中,虽然没有大括号包裹,但实质上,每个子句都自成一个代码块。

2.4 空代码块

如果一对大括号内部没有任何声明或其他语句,我们就把它叫做空代码块

空代码块在Go语言中是有效的,并且在某些情况下可以有一定的用途,尤其是在控制结构中,如if语句、for循环或switch语句的特定分支。它们充当了占位符,允许你将来添加代码而不需要改变代码的结构。

以下是一个示例,演示了空代码块的使用:

func main() {x := 10if x > 5 {// 非空代码块fmt.Println("x 大于 5")} else {// 空代码块,什么都不做}for i := 0; i < 5; i++ {// 空代码块,什么都不做}
}

2.5 支持嵌套代码块

Go 代码块支持嵌套,我们可以在一个代码块中嵌入多个层次的代码块,如下面示例代码所示:

func foo() { //代码块1{ // 代码块2{ // 代码块3{ // 代码块4}}}
}

三、作用域 (Scope)

3.1 作用域介绍

作用域的概念是针对标识符的,不局限于变量。每个标识符都有自己的作用域,而一个标识符的作用域就是指这个标识符在被声明后可以被有效使用的源码区域。

显然,作用域是一个编译期的概念,也就是说,编译器在编译过程中会对每个标识符的作用域进行检查,对于在标识符作用域外使用该标识符的行为会给出编译错误的报错。

3.2 作用域划定原则

我们可以使用代码块的概念来划定每个标识符的作用域。一般划定**原则就是声明于外层代码块中的标识符,其作用域包括所有内层代码块。**而且,这一原则同时适于显式代码块与隐式代码块。

3.3 标识符的作用域范围

3.3.1 预定义标识符作用域

首先,我们来看看位于最外层的宇宙隐式代码块的标识符。这一区域是 Go 语言预定义标识符的自留地。你可以看看下面这张表是Go 语言当前版本定义里的所有预定义标识符:

WechatIMG222

由于这些预定义标识符位于包代码块的外层,所以它们的作用域是范围最大的,对于开发者而言,它们的作用域就是源代码中的任何位置。不过,这些预定义标识符不是关键字,我们同样可以在内层代码块中声明同名的标识符。

3.3.2 包代码块级作用域

包顶层声明中的常量、类型、变量或函数(不包括方法)对应的标识符的作用域是包代码块。

不过,对于作用域为包代码块的标识符,我需要你知道一个特殊情况。那就是当一个包 A 导入另外一个包 B 后,包 A 仅可以使用被导入包包 B 中的导出标识符(Exported Identifier)。

按照 Go 语言定义,一个标识符要成为导出标识符需同时具备两个条件:一是这个标识符声明在包代码块中,或者它是一个字段名或方法名;二是它名字第一个字符是一个大写的 Unicode 字符。这两个条件缺一不可。

// 包 A
package Aimport "B"func SomeFunction() {// 可以访问包 B 中的导出标识符B.ExportFunction()
}// 这里无法访问包 B 中的非导出标识符
3.3.3 文件代码块作用域(包的导入作用域)

在Go语言中,除了大多数在包顶层声明的标识符具有包代码块范围的作用域外,还有一个特殊情况,即导入的包名。导入的包名的作用域是文件代码块范围,这意味着它在包含它的源代码文件中可见,但对其他源文件不可见。

考虑以下示例,其中一个包A有两个源文件,它们都依赖包B中的标识符:

// 文件1:source1.gopackage Aimport "B"func FunctionInSource1() {B.SomeFunctionFromB() // 可以使用导入的包名 B
}
// 文件2:source2.gopackage Aimport "B"func FunctionInSource2() {B.AnotherFunctionFromB() // 可以使用导入的包名 B
}

在这个示例中,两个源文件都导入了包B,但每个文件内的包名 B文件级别可见。这意味着FunctionInSource1FunctionInSource2函数都可以访问B包中的导出标识符(以大写字母开头的标识符),但对于其他包和源文件而言,它们不可见。

3.3.4 函数体的作用域

函数体内的标识符的作用域被限制在函数的开始和结束之间。这意味着函数体内的局部变量只能在函数体内部访问。

func exampleFunction() {var localVar = 42fmt.Println(localVar) // 可以访问局部变量 localVar
}fmt.Println(localVar) // 这里无法访问局部变量 localVar
3.3.5 流程控制作用域

流程控制结构,如if语句、for循环和switch语句,也会引入新的作用域。在这些结构中声明的局部变量的作用域限制在结构内部,不会泄漏到外部。

if x := 10; x > 5 {// x 只能在 if 语句块内访问fmt.Println(x)
}fmt.Println(x) // 这里无法访问 x

在上面的示例中,变量 x 在if语句内部有一个新的局部作用域,因此它只在if语句块内可见。

四、避免变量遮蔽的原则

4.1 变量遮蔽的根本原因

变量是标识符的一种,通过以上我们知道,一个变量的作用域起始于其声明所在的代码块,并且可以一直扩展到嵌入到该代码块中的所有内层代码块,而正是这样的作用域规则,成为了滋生“变量遮蔽问题”的土壤。

变量遮蔽问题的根本原因,就是内层代码块中声明了一个与外层代码块同名且同类型的变量,这样,内层代码块中的同名变量就会替代那个外层变量,参与此层代码块内的相关计算,我们也就说内层变量遮蔽了外层同名变量。现在,我们先来看一下这个示例代码,它就存在着多种变量遮蔽的问题:

... ...var a int = 2020func checkYear() error {err := errors.New("wrong year")switch a, err := getYear(); a {case 2020:fmt.Println("it is", a, err)case 2021:fmt.Println("it is", a)err = nil}fmt.Println("after check, it is", a)return err}type new intfunc getYear() (new, error) {var b int16 = 2021return new(b), nil}func main() {err := checkYear()if err != nil {fmt.Println("call checkYear error:", err)return}fmt.Println("call checkYear ok")}

这个变量遮蔽的例子还是有点复杂的,我们首先运行一下这个例子:

$go run complex.go
it is 2021
after check, it is 2020
call checkYear error: wrong year

我们可以看到,第 20 行定义的 getYear 函数返回了正确的年份 (2021),但是 checkYear 在结尾却输出“after check, it is 2020”,并且返回的 err 并非为 nil,这显然是变量遮蔽的“锅”!

根据我们前面给出的变量遮蔽的根本原因,看看上面这段代码究竟有几处变量遮蔽问题(包括标识符遮蔽问题)。

4.2 变量遮蔽问题分析

4.2.1 第一个问题:遮蔽预定义标识符

面对上面代码,我们一眼就看到了位于第 18 行的 new,这本是 Go 语言的一个预定义标识符,但上面示例代码呢,却用 new 这个名字定义了一个新类型,于是 new 这个标识符就被遮蔽了。如果这个时候你在 main 函数下方放上下面代码:

p := new(int)
*p = 11

你就会收到 Go 编译器的错误提示:“type int is not an expression”,如果没有意识到 new 被遮蔽掉,这个提示就会让你不知所措。不过,在上面示例代码中,遮蔽 new 并不是示例未按预期输出结果的真实原因,我们还得继续往下看。

4.2.2 第二个问题:遮蔽包代码块中的变量

你看,位于第 7 行的 switch 语句在它自身的隐式代码块中,通过短变量声明形式重新声明了一个变量 a,这个变量 a 就遮蔽了外层包代码块中的包级变量 a,这就是打印“after check, it is 2020”的原因。包级变量 a 没有如预期那样被 getYear 的返回值赋值为正确的年份 2021,2021 被赋值给了遮蔽它的 switch 语句隐式代码块中的那个新声明的 a。

4.2.3 第三个问题:遮蔽外层显式代码块中的变量

同样还是第 7 行的 switch 语句,除了声明一个新的变量 a 之外,它还声明了一个名为 err 的变量,这个变量就遮蔽了第 4 行 checkYear 函数在显式代码块中声明的 err 变量,这导致第 12 行的 nil 赋值动作作用到了 switch 隐式代码块中的 err 变量上,而不是外层 checkYear 声明的本地变量 err 变量上,后者并非 nil,这样 checkYear 虽然从 getYear 得到了正确的年份值,但却返回了一个错误给 main 函数,这直接导致了 main 函数打印了错误:“call checkYear error: wrong year”。

通过这个示例,我们也可以看到,短变量声明与控制语句的结合十分容易导致变量遮蔽问题,并且很不容易识别,因此在日常 go 代码开发中你要尤其注意两者结合使用的地方。

五、利用工具检测变量遮蔽问题

依靠肉眼识别变量遮蔽问题终归不是长久之计,所以Go 官方提供了 go vet 工具可以用于对 Go 源码做一系列静态检查,在 Go 1.14 版以前默认支持变量遮蔽检查,Go 1.14 版之后,变量遮蔽检查的插件就需要我们单独安装了,安装方法如下:

go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest

安装成功后,我们就可以通过 go vet 扫描代码并检查这里面有没有变量遮蔽的问题了。我们检查一下前面的示例代码,看看效果怎么样。执行检查的命令如下:

$go vet -vettool=$(which shadow) -strict complex.go 
./complex.go:13:12: declaration of "err" shadows declaration at line 11

相关文章:

Go 代码块与作用域,变量遮蔽问题详解

Go 代码块与作用域详解 文章目录 Go 代码块与作用域详解一、引入二、代码块 (Block)2.1 代码块介绍2.2 显式代码块2.3 隐式代码块2.4 空代码块2.5 支持嵌套代码块 三、作用域 (Scope)3.1 作用域介绍3.2 作用域划定原则3.3 标识符的作用域范围3.3.1 预定义标识符作用域3.3.2 包代…...

可观测性-Metrics-WebClient异步Http远程Call

代码示例 1.依赖导入 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><ar…...

Android之播放本地视频和Url视频方法

一、播放本地视频文件 根据文件路径在浏览器中播放&#xff0c;可用于视频预览等场景 效果&#xff1a; 用浏览器播放本地视频 文件路径例子&#xff1a; /storage/emulated/0/Android/data/com.custom.jfrb/files/Movies/1697687179497.mp4 File file new File("文件…...

设计模式:工厂方法模式(C#、JAVA、JavaScript、C++、Python、Go、PHP):

本节主要介绍设计模式中的工厂方法模式。 简介&#xff1a; 工厂方法模式&#xff0c;它是对简单工厂模式的进一步抽象化&#xff0c;其好处是可以使系统在不修改原来代码的情况下引进新的产品&#xff0c;即满足开闭原则。 它定义了一个用于创建对象的工厂接口&#xff0c;让…...

C++基础——指针

1 概述 指针的作用&#xff1a;可以通过指针间接访问内存 内存编号从0开始&#xff0c;一般使用十六进制数字表示&#xff0c;指针可以保存地址 2 指针变量定义和作用 int main() {//1、指针的定义int a 10; //定义整型变量a//指针定义语法&#xff1a; 数据类型 * 变量名 …...

PLC 学习day02 硬件输入/输入的知识

1.资料来源 1.链接&#xff1a;三菱PLC视频教程全集之FX3U基本单元输入接线_哔哩哔哩_bilibili 2. 链接&#xff1a; 三菱plc视频教程全集之FX3U基本单元输出接线_哔哩哔哩_bilibili 2. PLC 的输入部分器件连接。 2.1 PLC输入部分的硬件知识 1. 一般输入部分是PLC获取信息的地…...

rabbitMq (2)

RabbitMQ 消息应答与发布 文章目录 1. 消息应答1.2 自动应答1.2 手动应答1.3 代码案例 2. RabbitMQ 持久化2.1 队列持久化2.2 消息持久化 3. 不公平分发4. 预取值分发5. 发布确认5.1 发布确认逻辑5.2 开启发布确认的方法5.3 单个确认发布5.4 批量确认发布5.5 异步确认5.5.1 处理…...

通讯协议学习之路:RS422协议理论

通讯协议之路主要分为两部分&#xff0c;第一部分从理论上面讲解各类协议的通讯原理以及通讯格式&#xff0c;第二部分从具体运用上讲解各类通讯协议的具体应用方法。 后续文章会同时发表在个人博客(jason1016.club)、CSDN&#xff1b;视频会发布在bilibili(UID:399951374) 一、…...

剪映failed to initialize,cuda.is_available()为false解决

debug记录帖 错误1&#xff1a;打开剪映发现弹窗提示failed to initialize graphics backed for D3D11 错误2&#xff1a;torch版本、cuda版本&#xff08;之前的正常环境&#xff09;都对但是torch.cuda.is_available()为false 怀疑是显卡驱动的问题 打开Nvidia Geforce Exp…...

基于Spring Boot的LDAP开发全教程

写在前面 协议概述 LDAP&#xff08;轻量级目录访问协议&#xff0c;Lightweight Directory Access Protocol)是一种用于访问和维护分布式目录服务的开放标准协议,是一种基于TCP/IP协议的客户端-服务器协议&#xff0c;用于访问和管理分布式目录服务&#xff0c;如企业内部的…...

在 Linux 上保护 SSH 服务器连接的 8 种方法

SSH 是一种广泛使用的协议&#xff0c;用于安全地访问 Linux 服务器。大多数用户使用默认设置的 SSH 连接来连接到远程服务器。但是&#xff0c;不安全的默认配置也会带来各种安全风险。 具有开放 SSH 访问权限的服务器的 root 帐户可能存在风险。尤其是如果使用的是公共 IP 地…...

摩尔信使MThings的协议转换(数据网关)功能

摩尔信使MThings可以作为现场总线&#xff08;RS485&#xff09;和以太网的数据中枢&#xff0c;并拥有强大的Modbus协议转换功能。 数据网关功能提供协议转换和数据汇聚功能&#xff0c;可实现多维度映射&#xff0c;包括&#xff1a;不同的通道(总线)类型、协议类型&#xff…...

Mac安装Kali保姆级教程

Mac安装Kali保姆级教程 其他安装教程&#xff1a;使用VMware安装系统Window、Linux&#xff08;kali&#xff09;、Mac操作系统 1 虚拟机安装VM Fusion 去官网下载VM Fusion 地址&#xff1a;https://customerconnect.vmware.com/en/evalcenter?pfusion-player-personal-13 …...

利用Spring Boot框架做事件发布和监听

一、编写事件 1.编写事件类并集成spring boot 事件接口&#xff0c;提供访问事件参数属性 public class PeriodicityRuleChangeEvent extends ApplicationEvent {private final JwpDeployWorkOrderRuleDTO jwpDeployWorkOrderRuleDTO;public PeriodicityRuleChangeEvent(Obje…...

KingBase库模式表空间和客户端认证(kylin)

库、模式、表空间 数据库 数据库基集簇与数据库实例 KES集簇是由单个KES实例管理的数据库的集合KES集簇中的库使用相同的全局配置文件和监听端口、共享相关的进程和内存结构同一数据库集簇中的进程、相关的内存结构统称为实例 数据库 数据库是一个长期存储在计算机内的、有…...

h5的扫一扫功能 (非微信浏览器环境下)

必须在 https 域名下才生效 <template><div><van-field label"服务商编码" right-icon"scan" placeholder"扫描二维码获取" click-right-icon"getCameras" /> <div class"scan" :style"{disp…...

Typora 导出PDF 报错 failed to export as pdf. undefined 解决方案

情况 我想把一个很大的markdown 导出为 248页的pdf 然后就报错 failed to export as pdf. undefined 原因 &#xff1a; 个人感觉应该是图片太大了 格式问题之类导致的 解决 文件 -> 偏好设置 - > 导出 -> pdf -> 自定义 -> 把大小全部改为24mm (虽然图中是32 …...

[架构之路-239]:目标系统 - 纵向分层 - 中间件middleware

目录 前言&#xff1a; 一、中间件概述 1.1 中间件在软件层次中的位置 1.2 什么是中间件 1.3 为什么需要中间件 1.4 中间件应用场合&#xff08;应用程序不用的底层需求&#xff1a;计算、存储、通信&#xff09; 1.5 中间件分类 - 按内容分 二、嵌入式系统的中间件 2…...

javascript利用xhr对象实现http流的comet轮循,主要是利用readyState等于3的特点

//此文件 为前端获取http流 <!DOCTYPE html> <html xmlns"http://www.w3.org/1999/xhtml" lang"UTF-8"></html> <html><head><meta http-equiv"Content-Type" content"text/html; charsetUTF-8"/&g…...

【Mybatis源码】XPathParser解析器

XPathParser是Mybatis中定义的进行解析XML文件的类,此类用于读取XML文件中的节点文本与属性;本篇我们主要介绍XPathParser解析XML的原理。 一、XPathParser构造方法 这里我们介绍主要的构造方法 public XPathParser(InputStream inputStream, boolean validation, Propert…...

辉视智慧酒店解决方案助力传统酒店通过智能升级焕发新生

辉视智慧酒店解决方案基于强大的物联网平台&#xff0c;将酒店客控、网络覆盖、客房智能化控制、酒店服务交互等完美融合&#xff0c;打造出全方位的酒店智慧化产品。利用最新的信息化技术&#xff0c;我们推动酒店智慧化转型&#xff0c;综合运用前沿的信息科学和技术、消费方…...

文件和命令的查找与处理

1.命令查找 which which 接命令 2.文件查找 find 按文件名字查找 准确查找 find / -name "hosts" 粗略查找 find / -name "ho*ts" 扩展名查找 find / -name "*.txt" 按文件类型查找 find / -type f 文件查找 find / -ty…...

第七章:最新版零基础学习 PYTHON 教程—Python 列表(第三节 -Python程序访问列表中的索引和值)

有多种方法可以访问列表的元素,但有时我们可能需要访问元素及其所在的索引。让我们看看访问列表中的索引和值的所有不同方法。 目录 使用Naive 方法访问列表中的索引和值 使用列表理解访问列表中的索引和值...

接口测试面试题整理​​​​​​​

HTTP, HTTPS协议 什么是DNSHTTP协议怎么抓取HTTPS协议说出请求接口中常见的返回状态码http协议请求方式HTTP和HTTPS协议区别HTTP和HTTPS实现机有什么不同POST和GET的区别HTTP请求报文与响应报文格式什么是Http协议无状态协议?怎么解决HTTP协议无状态协议常见的POST提交数据方…...

【保姆级教程】ChatGPT/GPT4科研技术应用与AI绘图

查看原文>>>https://mp.weixin.qq.com/s?__bizMzAxNzcxMzc5MQ&mid2247663763&idx1&snbaeb113ffe0e9ebf2b81602b7ccfa0c6&chksm9bed5f83ac9ad6955d78e4a696949ca02e1e531186464847ea9c25a95ba322f817c1fc7d4e86&token1656039588&langzh_CN#rd…...

凉鞋的 Godot 笔记 202. 变量概述与简介

202. 变量概述与简介 想要用好变量不是一件简单的事情&#xff0c;因为变量需要命名。 我们可以从两个角度去看待一个变量&#xff0c;第一个角度是变量的功能&#xff0c;第二个是变量的可读性。 变量的功能其实非常简单&#xff0c;变量可以存储一个值&#xff0c;这个值是…...

HTML 常用标签及练习

常用标签 <head>中的标签 概述 head中的内容不显示到页面上 标签说明<title>定义网页的标题<meta>定义网页的基本信息&#xff08;供搜索引擎&#xff09;<style>定义CSS样式<link>链接外部CSS文件或脚本文件<script>定义脚本语言<…...

Python 编程基础 | 第六章-包与模块管理 | 1、包与模块简介

一、模块 在程序开发过程中&#xff0c;文件代码越来越长&#xff0c;维护越来越不容易。可以把很多不同的功能编写成函数&#xff0c;放到不同的文件里&#xff0c;方便管理和调用。在Python中&#xff0c;一个.py文件就称之为一个模块&#xff08;Module&#xff09;。 1、简…...

为中小企业的网络推广策略解析:扩大品牌知名度和曝光度

目前网络推广已经成为企业获取潜在客户和提升品牌知名度的重要手段。对于中小企业而言&#xff0c;网络推广是一个具有巨大潜力和可行性的营销策略。在本文中&#xff0c;我们将探讨中小企业为什么有必要进行网络推广&#xff0c;并分享一些实用的网络推广策略。 一、扩大品牌知…...

资源受限MCU Flash空间占用优化

资源受限MCU Flash空间占用优化步骤&#xff1a; 避免重复功能&#xff0c;排查软件工程中&#xff0c;重复功能的代码片段&#xff0c;抽象出来&#xff0c;重新进行封装&#xff1b;减少内联函数inline&#xff0c;内联函数过大且过多的调用无疑会增加代码量&#xff0c;占用…...