1、 多值赋值
可以一次性声明多个变量,并可以在声明时赋值,而且可以省略类型,但必须遵守一定的规则要求,具体看下面的示例。
- 如下都是合法的
//相同类型的变量可以在末尾带上类型
var x, y int
var x, y int = 1, 2
//如果不带类型,编译器则可以直接进行类型推断
var x, y = 1, 2
var x, y = 1, "tata'
//不同类型的变量声明和隐式初始化可以使用如下语法
var (
x int
y string
)
- 如下都是非法的
//多值赋值语句中每个变量后面不能都带上类型
var x int,y int 1,2
var x int,y string 1,"tata"
var x int,y int
var x int,y string
多值赋值的两种格式
- 右边是一个返回多值的表达式,可以是返回多值的函数调用,也可以是range对map、slice等函数的操作,还可以是类型断言。例如:
//函数调用
x,y = f()
//range表达式
for k,v :range map {
}
//type assertion
v,ok := i.(xxxx)
- )赋值的左边操作数和右边的单一返回值的表达式的个数一样,逐个从左向右依次对左边的操作数赋值。例如:
x, y, z = a, b, c
多值赋值语义
多值赋值看似简化了代码,但相互引用会产生让人困惑的结果。关键是要理解多值赋值的语义,才能消除这种困惑。多值赋值包括两层语义:
- 对左侧操作数中的表达式、索引值进行计算和确定,首先确定左侧的操作数的地址:然后对右侧的赋值表达式进行计算,加果发现右侧的表达式计算引用了左侧的变量,则创建临时变量进行值拷贝,最后完成计算。
- 从左到右的顺序依次赋值。
看下面的示例:
package main
import "fmt"
func main() {
x := []int{1, 2, 3}
i := 0
i, x[i] = 1, 2 //set i = 1, x[0] = 2
fmt.Println(i, x)
x = []int{1, 2, 3}
i = 0
x[i], i = 2, 1 //set x[0] = 2, i = 1
fmt.Println(i, x)
x = []int{1, 2, 3}
i = 0
x[i], i = 2, x[i] // set tmp = x[0], x[0] = 2, i = tmp ==> i = 1
fmt.Println(i, x)
x[0], x[0] = 1, 2 // set x[0] = 1, then x[0] = 2
fmt.Println(x[0])
}
//结果
1 [2 2 3]
1 [2 2 3]
1 [2 2 3]
2
结果分析:
- 第8行先计算x[i]中的数组索引i的值,此时i=0,两个被赋值变量是i和x[0],然后从左向右赋值操作i=1,x[0]=2。
- 第13行和第8行的逻辑一样。
- 第16行先计算赋值语句左右两侧x[i])中的数组索引i的值,此时=0,两个被赋值变量是i和x[0],两个赋值变量分别是2、x[0]。由于x[0]是左边的操作数,所以编译器创建一个临时变量tmp,将其赋值为x[0],然后从左向右依次赋值操作x[0]=2,i=tmp,i的值为1。
- )第22行按照从左到右的执行顺序,先执行x[0]=1,然后执行x[0]=2,所以最后x[0]的值为2。
2、短变量的声明和赋值
短变量的声明和赋值是指在G0函数或类型方法内部使用“:=”声明并初始化变量,支持多值赋值,格式如下:
a := va
a,b := va,vb
短变量的声明和赋值的语法要约:
- 使用“=”操作符,变量的定义和初始化同时完成。
- 变量名后不要跟任何类型名,Go编译器完全靠右边的值进行推导。
- 支持多值短变量声明赋值。
- 只能用在函数和类型方法的内部。
短变量的声明和赋值中的最容易产生歧义的是多值短变量的声明和赋值,这个问题的根源是Go语言的语法允许多值短变量声明和赋值的多个变量中,只要有一个是新变量就可以使用":="进行赋值。也就是说,在多值段变量的声明和赋值时,至少有一个变量是新创建的局部变量,其他的变量可以复用以前的变量,不是新创建的变量执行的仅仅就是赋值。
package main
func main() {
//此时main函数作用域里面没有n
//所以创建的局部变量n
n, _ := foo()
//访问的是全局变量n
g() //0
//访问的是main函数作用域下的n
println(n) //1
}
var n int
func foo() (int, error) {
return 1, nil
}
//访问全局变量n
func g() {
println(n)
}
通过上例分析得知,a,b:=va,vb什么时候定义新变量,什么时候复用已存在变量有以下规则:
-
如果想编译通过,则a和b中至少要有一个是新定义的局部变量。换句话说,在此语句所在代码块中,不能同时预先声明a、b两个局部变量。
-
如果在赋值语句a,b:=va,vb所在的代码块中已经存在一个局部变量a,则赋值语句a,b:=va,vb不会创建新变量a,而是直接使用va赋值给已经声明的局部变量a,但是会创建新变
量b,并将vb赋值给b。 -
如果在赋值语句a,b:=va,vb所在的代码块中没有局部变量a和b,但在全局命名空间有变量a和b,则该语句会创建新的局部变量a和b并使用va、vb初始化它们。此时赋值语句
所在的局部作用域类内,全局的a和b被屏蔽。
赋值操作符“=”和“:=”的区别:
- “=”不会声明并创建新变量,而是在当前赋值语句所在的作用域由内向外逐层去搜寻变量,如果没有搜索到相同变量名,则报编译错误。
- “:=”必须出现在函数或类型方法内部。
- “:=”至少要创建一个局部变量并初始化。
func f() {
var a, b int
//如下语句不能通过编译,原因是没有创建新变量,无法使用":="
a, b := 1, 2
}
func f() {
var a int
//如下语能通过编译,a是上调语句声明的a,b是新建的
a, b := 1, 2
}
func f() {
//如下语句能够通过编译,a、b都是新创建的
a, b := 1, 2
}
如何避免“:=”引入的副作用?一个好办法就是先声明变量,然后使用“=”赋值。例如:
func f(){
var a,b int
a,b=1,2
}
多值短变量声明赋值“=”的最佳使用场景是在错误处理上。例如:
a, err := f()
if err != nil {
xxx
}
//此时err可以是已经存在的err变量,只是重新赋值了
b, err := g()