>

在 Go 语言开发过程中,经常会遇到需要使用局部(或临时)变量隐藏(shadow)全局同名变量的情形(比如常见的语句:val, err := somefunc())。
因此,本文总结介绍 Go 语言中最常用到的变量隐藏的知识。


##### 在相同作用域中同名变量的关系。

Go 语言中申明一个变量的方法有两种,一种直接申明变量的名字和类型(如:var name string),另一种是通过变量的值来推导变量类型(如:name := "Rob Pike")。
如果已经使用第一种方式申明过变量类型,那么,在同一作用域中,就不能再使用第二种方式给变量赋值,而是应该直接用等号 = 来给变量赋值。

比如,下列语句将会编译报错:“no new variables on left side of :=”

1
2
var name string
name := "Rob Pike"

但是,如果 := 左边是多个变量,那么这种语句将是合法的。比如:

1
2
3
4
5
6
var test() (string, bool) {
return "Obama", true
}

var name string
name, ok := test()

需要注意的是:

  • 由于 := 左边的 name 与之前申明的 name 变量在同一作用域,上述 test() 函数返回的第一个返回值的类型也必须是 string 类型,
    否则,将会报错: “cannot assign int to name (type string) in multiple assignment”
  • 第二个返回值不能使用 _ 来忽略,必须显示返回。 如果使用 name, _ := test(),同样会报错 “no new variables on left side of :=”

还要注意,:= 左边必须至少要有一个是未使用第一种方式申明过类型的。也就是说,如果上述 name 和 ok 都声明过的话,使用 name, ok := test() 将会编译报错。


##### 在内部作用域中隐藏外部同名变量

在 Go 语言中,下列语法将指定一个局部作用域

  • {} 包起来的代码块
  • if, for, switch 等控制语句定义的代码块。
  • select 包起来的代码块
  • go func() 协程包起来的代码块。

当然,在上述所述的情况中,都可以引用作用域外的变量,但是当内部申明的变量与外部的变量同名时,将会隐藏外部的变量。这种隐藏是无条件的,也就是说,即使内部申明的变量类型不一样,
只要与外部变量名字相同,也同样会隐藏外部变量。

来看几个例子:

1
2
3
4
var name string
{
name := 19.99
}

上述语法完全正确,即使内部作用域中申明的 name 变量不是 string 类型。
由于 Go 语言中采用 error 返回值的方式来处理程序出错,在 if, for 等流程控制代码段中,需要注意返回的其他变量不要与作用域外申明的变量之间的关系,尤其是当遇到流程控制块需要修改外部变量时。
比如,下列这个例子展示了一个流程控制中修改全局变量但是修改失效的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

func GetValue(s string) (string, bool) {
idx := strings.Index(s, ":")
if idx < 0 {
return "", false
}

return s[idx:], true
}

func GenGroupId(s string) (string, error) {
var res string

if res, ok := GetValue("id:1881"); !ok {
return res, errors.New("Format error")
}

return "group-" + res, nil
}

在上述 if 流程块中,使用了一个与全局范围内同名的 res 变量,但是实际上 if 块中 res 变量是一个局部临时变量,隐藏了全局变量,对其的修改无法反映到全局范围中去,
因此 return 语句中的 res 依然是空的。对于这种在 if 控制块中需要修改全局变量情况,应该避免使用 :=

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

func GetValue(s string) (string, bool) {
idx := strings.Index(s, ":")
if idx < 0 {
return "", false
}

return s[idx:], true
}

func GenGroupId(s string) (res string, err error) {
var ok bool
if res, ok = GetValue("id:1881"); !ok {
err = errors.New("Format error")
return
}

return "group-" + res, nil
}