11|代码块与作用域:如何保证变量不会被遮蔽?
11|代码块与作用域:如何保证变量不会被遮蔽?
讲述:Tony Bai
时长17:12大小15.71M
代码块与作用域
避免变量遮蔽的原则
利用工具检测变量遮蔽问题
小结
思考题
赞 34
提建议
精选留言(34)
- Geek_b6da5d2021-11-11goland其实能够看出变量的作用域 颜色上是不一样的共 2 条评论29
- lesserror2021-11-05关于这一块儿的知识其实还挺绕的。 不同代码块中的重名变量与变量重声明中的变量区别到底在哪儿?为了方便描述,我就把不同代码块中的重名变量叫做“可重名变量”吧。注意,在同一个代码块中不允许出现重名的变量,这违背了 Go 语言的语法。关于这两者的表象和机理,我们已经讨论得足够充分了。你现在可以说出几条区别?请想一想,然后再看下面的列表。 1. 变量重声明中的变量一定是在某一个代码块内的。注意,这里的“某一个代码块内”并不包含它的任何子代码块,否则就变成了“多个代码块之间”。而可重名变量指的正是在多个代码块之间由相同的标识符代表的变量。 2. 变量重声明是对同一个变量的多次声明,这里的变量只有一个。而可重名变量中涉及的变量肯定是有多个的。 3. 不论对变量重声明多少次,其类型必须始终一致,具体遵从它第一次被声明时给定的类型。而可重名变量之间不存在类似的限制,它们的类型可以是任意的。 4. 如果可重名变量所在的代码块之间,存在直接或间接的嵌套关系,那么它们之间一定会存在“屏蔽”的现象。但是这种现象绝对不会在变量重声明的场景下出现。展开
作者回复: 👍
共 7 条评论10 - 扣剑书生2021-12-02func 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 }展开
作者回复: 👍
共 3 条评论6 - Amosヾ2021-11-05可不可以通过变量尽量不重名来避免变量遮蔽呢?
作者回复: 不重名肯定不会遮蔽。但是实际编码中,一些常用的功能变量,比如表示错误的err、表示下标的i,表示key和value的k、v等,如果要做不同命名,很容易在代码中出现大量的k1,v1,k2,v2等,阅读起来总是感觉缺少了一些优雅感。不知你是否有同感。
共 3 条评论6 - 文经2021-11-12约定号包级别的变量用长的名字,越是局部的变量用越短小的名字,应该能够解决一大部分变量zhe遮蔽的问题。
作者回复: 也算是一个办法。前提是明确规则,且大家都遵守。这样才能在协作中,减少遮蔽问题的发生频度。
5 - 进化菌2021-11-08这么来看的话,代码还是不要嵌套太深的好,同文件中的变量命名也尽量不重名,大概率能减少变量遮蔽的问题。3
- 程旭阳2021-11-06go1.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") }展开
作者回复: 正确✅
共 3 条评论3 - Rayjun2021-11-06修改两个地方,把 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.
2 - aoe2021-11-05不使用一样的名字就行啊2
- 罗杰2021-11-05这节课适应在电脑上阅读,而且要用心看2
- 🐎2022-08-21 来自北京感觉和js一样,变量就近使用
作者回复: 其实这是一个编程通用原则,利于提升可读性
1 - 独钓寒江2022-01-15“位于第 18 行的 new,这本是 Go 语言的一个预定义标识符,但上面示例代码呢,却用 new 这个名字定义了一个新类型,于是 new 这个标识符就被遮蔽了” 为什么不禁止用预定义标识符定义新类型或者变量的行为呢?
作者回复: 一旦“禁止用预定义标识符定义新类型或者变量的行为”,那么new这样的预定义标识符就和关键字没啥区别了。
1 - 子杨2022-12-13 来自辽宁「作者回复: 一旦“禁止用预定义标识符定义新类型或者变量的行为”,那么new这样的预定义标识符就和关键字没啥区别了。」 想请问老师,预定义标识符和关键字的区别是啥?
作者回复: 预定义标识符可以被重新定义。 比如 var new int = 5 这时new就是一个变量。 但关键字不可以做标识符。 你不能用for作为变量名: var for int = 5 // error
1 - 我是熊大2022-11-24 来自北京老师讲的晦涩了,作用域问题,这么讲不好理解,本质就是:局部量优先于全局变量
- Min2022-09-21 来自北京记录下在 Windows Gitbash 终端使用 go vet 遇到的坑: $ go vet -vettool=$(which shadow) -strict main.go shadow: open C:\Users\myangvgo\go\bin\shadow: The system cannot find the file specified. go: error obtaining buildID for C:\Users\myangvgo\go\bin\shadow: exit status 1 原因是 shadow 在 windows 下叫 shadow.exe,而不是 Unix 系统下的 shadow。解决办法就是加上.exe $ go vet -vettool=$(which shadow).exe -strict main.go # command-line-arguments .\main.go:14:12: declaration of "err" shadows declaration at line 11展开
作者回复: 👍。感谢补充,我个人很少使用windows,所以代码都是在mac或linux上执行的。
- He2022-06-30package main import ( "errors" "fmt" ) var a int = 2020 func checkYear() error { err := errors.New("wrong year") var a new // 声明 a 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") }展开
- He2022-06-30package main import ( "errors" "fmt" ) var a int = 2020 func checkYear() error { err := errors.New("wrong year") var a new 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") }展开
作者回复: 👍
- westfall2022-05-02奇怪了,mac 上执行 go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest 安装成功之后,执行 which shadow 总是 shadow not found
作者回复: 安装后的shadow一般在$GOPATH/bin下,如果没有显式设置GOPATH,则在~/go/bin下。如果该目录没有被加入到PATH环境变量中,which命令是找不到shadow的。
共 2 条评论 - 时间带走初心2022-04-19老师,想请问一下为啥在window下执行go vet -vettool=$(which shadow) -strict complex.go 会出现这样的提示which : 无法将“which”项识别为 cmdlet、函数、脚本文件或可运行程序的名称 。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。 所在位置 行:1 字符: 19
作者回复: 这个命令行不适用windows,windows下默认没有which这个命令。你可以在windows下安装一个linux虚拟机或用wsl2安装一个ubuntu来运行上面命令。
- 史努比2022-03-27Universe Block是不是翻译成“全局代码块”更贴切一些,“宇宙代码块”总觉得怪怪的。
作者回复: 我倒是觉得“宇宙代码块”更形象生动罒ω罒。否则go官方也不会用universe block,而会用global block了。 另外代码块不要与作用域混淆。包代码块中声明的首字母大写的标识符实际是也是拥有全局作用域的,可以被任意其他代码所引用的。