Tony Bai · Go 语言第一课
Tony Bai
资深架构师,tonybai.com 博主
21492 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 59 讲
开篇词 (1讲)
结束语 (1讲)
Tony Bai · Go 语言第一课
15
15
1.0x
00:00/00:00
登录|注册

11|代码块与作用域:如何保证变量不会被遮蔽?

switch/select的子句隐式代码块
分支控制语句隐式代码块
文件代码块
包代码块
宇宙代码块
划定原则:声明于外层代码块中的标识符,其作用域包括所有内层代码块
作用域是编译期概念
隐式代码块
显式代码块
遮蔽外层显式代码块中的变量
遮蔽包代码块中的变量
遮蔽预定义标识符
修正复杂的变量遮蔽的例子
限制
静态检查
go vet工具
作用域概念
代码块概念
示例代码分析
根本原因:内层代码块中声明了与外层代码块同名且同类型的变量,导致内层变量替代外层变量参与计算
思考题
利用工具检测变量遮蔽问题
代码块与作用域
变量遮蔽问题
Go语言变量遮蔽问题与作用域概念

该思维导图由 AI 生成,仅供参考

你好,我是 Tony Bai。
在上一节课,我们学习了变量的几种声明形式,还掌握了不同类型的变量应该采用哪种声明形式。在这一节课里,我们还是继续聊聊有关变量的事情。聊什么呢?别急,我们从一个 Go 变量遮蔽(Variable Shadowing)的问题说起。
什么是变量遮蔽呢?我们来看下面这段示例代码:
var a = 11
func foo(n int) {
a := 1
a += n
}
func main() {
fmt.Println("a =", a) // 11
foo(5)
fmt.Println("after calling foo, a =", a) // 11
}
你可以看到,在这段代码中,函数 foo 调用前后,包级变量 a 的值都没有发生变化。这是因为,虽然 foo 函数中也使用了变量 a,但是 foo 函数中的变量 a 遮蔽了外面的包级变量 a,这使得包级变量 a 没有参与到 foo 函数的逻辑中,所以就没有发生变化了。
变量遮蔽是 Go 开发人员在日常开发工作中最容易犯的编码错误之一,它低级又不容易查找,常常会让你陷入漫长的调试过程。上面的实例较为简单,你可以通过肉眼很快找到问题所在,但一旦遇到更为复杂的变量遮蔽的问题,你就可能会被折腾很久,甚至只能通过工具才能帮助捕捉问题所在。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

Go语言中的变量遮蔽问题及解决方法是本文的核心内容。文章通过深入探讨代码块和作用域的概念,清晰地阐述了变量遮蔽问题的根本原因以及如何避免这一问题。在Go语言中,代码块支持嵌套,而作用域是针对标识符的,每个标识符都有自己的作用域。通过对代码块和作用域的深入理解,开发人员可以彻底避免变量遮蔽问题。文章还介绍了Go官方提供的go vet工具,可以用于检测变量遮蔽问题,但也指出了工具的局限性,强调了深入理解代码块与作用域概念的重要性。总的来说,本文内容简洁明了,适合读者快速了解变量遮蔽问题及解决方法,为编码提供指导和帮助。文章通过深入的技术讨论和实例分析,为读者提供了对Go语言中变量遮蔽问题的全面理解和解决思路。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Tony Bai · Go 语言第一课》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(48)

  • 最新
  • 精选
  • lesserror
    关于这一块儿的知识其实还挺绕的。 不同代码块中的重名变量与变量重声明中的变量区别到底在哪儿?为了方便描述,我就把不同代码块中的重名变量叫做“可重名变量”吧。注意,在同一个代码块中不允许出现重名的变量,这违背了 Go 语言的语法。关于这两者的表象和机理,我们已经讨论得足够充分了。你现在可以说出几条区别?请想一想,然后再看下面的列表。 1. 变量重声明中的变量一定是在某一个代码块内的。注意,这里的“某一个代码块内”并不包含它的任何子代码块,否则就变成了“多个代码块之间”。而可重名变量指的正是在多个代码块之间由相同的标识符代表的变量。 2. 变量重声明是对同一个变量的多次声明,这里的变量只有一个。而可重名变量中涉及的变量肯定是有多个的。 3. 不论对变量重声明多少次,其类型必须始终一致,具体遵从它第一次被声明时给定的类型。而可重名变量之间不存在类似的限制,它们的类型可以是任意的。 4. 如果可重名变量所在的代码块之间,存在直接或间接的嵌套关系,那么它们之间一定会存在“屏蔽”的现象。但是这种现象绝对不会在变量重声明的场景下出现。

    作者回复: 👍

    2021-11-05
    7
    11
  • Amosヾ
    可不可以通过变量尽量不重名来避免变量遮蔽呢?

    作者回复: 不重名肯定不会遮蔽。但是实际编码中,一些常用的功能变量,比如表示错误的err、表示下标的i,表示key和value的k、v等,如果要做不同命名,很容易在代码中出现大量的k1,v1,k2,v2等,阅读起来总是感觉缺少了一些优雅感。不知你是否有同感。

    2021-11-05
    4
    9
  • 扣剑书生
    func checkYear() error { err := errors.New("wrong year") // 短变量形式,屏蔽了外层的包级变量 a,代替 其接收值 // err代替上面 的 err接收值 // 接收放在 switch 作用域外 a, err := getYear() switch 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 }

    作者回复: 👍

    2021-12-02
    6
    8
  • 文经
    约定号包级别的变量用长的名字,越是局部的变量用越短小的名字,应该能够解决一大部分变量zhe遮蔽的问题。

    作者回复: 也算是一个办法。前提是明确规则,且大家都遵守。这样才能在协作中,减少遮蔽问题的发生频度。

    2021-11-12
    7
  • 程旭阳
    go1.17.1 `type new int`会报错: cannot assign new to a (type int) in multiple assignment cannot use new value as type int in assignment 修改为 `type new = int` 之后不再报错 思考题解决方法: package main import ( "fmt" "errors" ) var a int = 2020 func 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 year = int func getYear() (year, error) { var b int16 = 2021 return year(b), nil } func main() { err := checkYear() if err != nil { fmt.Println("call checkYear error:", err) return } fmt.Println("call checkYear ok") }

    作者回复: 正确✅

    2021-11-06
    3
    6
  • 子杨
    「作者回复: 一旦“禁止用预定义标识符定义新类型或者变量的行为”,那么new这样的预定义标识符就和关键字没啥区别了。」 想请问老师,预定义标识符和关键字的区别是啥?

    作者回复: 预定义标识符可以被重新定义。 比如 var new int = 5 这时new就是一个变量。 但关键字不可以做标识符。 你不能用for作为变量名: var for int = 5 // error

    2022-12-13归属地:辽宁
    5
  • 615
    刚细翻看了下go语言圣经,switch里的a和err确实是新声明的而不存在赋值行为,因为重新赋值行为只存在于变量已经在相同block中声明过。

    作者回复: 正解✅

    2023-04-29归属地:北京
    3
  • 🐎
    感觉和js一样,变量就近使用

    作者回复: 其实这是一个编程通用原则,利于提升可读性

    2022-08-21归属地:北京
    3
  • Rayjun
    老师,这里我还有一个问题,我发现如果是在同级的作用域中,声明两个一样的变量就会报错,但是在不同级的作用域中就不会,那么 go 语言为何不禁止同名变量的声明,这样不就可以解决变量遮蔽的问题么

    作者回复: 好问题!不过这是编程语言设计范畴的问题,我不是编程语言设计专家,不能从原理上给予解释。但从目前情况来看,似乎没有哪门编程语言禁止不同作用域的同名变量声明,显然这是一个语言设计与实现的惯例。

    2022-03-01
    3
  • Rayjun
    修改两个地方,把 a 的类型改成 new,并 去掉 switch 那的一个引号 var a new = 2020 func 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 int func getYear() (new, error) { var b int16 = 2021 return new(b), nil } func main() { err := checkYear() if err != nil { fmt.Println("call checkYear error:", err) return } fmt.Println("call checkYear ok") }

    作者回复: 有一点提醒一下:既然我们知道了new是预定义的标识符,我们在日常编写代码中尽量要避免重新定义new.

    2021-11-06
    3
收起评论
显示
设置
留言
48
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部