Go习惯用法(多值赋值短变量声明赋值简写模式)基础实例
作者:程序员的自我进化
1. 多值赋值
可以一次性声明多个变量,并可以在声明时赋值,而且可以省略类型,但必须遵守一定的规则要求。
package main
import "fmt"
func main() {
var x, y int // 相同类型变量可以在末尾带上类型
var a, b int = 1, 2
var c, d = 3, 4 // 不带类型时,编译器自动推断
var ( // 不同类型变量声明和隐式初始化
e int
f string
)
var g int, e int = 6, 7 // 多赋值语句中每个变量后面不能都带上类型
fmt.Println("x ", x)
fmt.Println("y ", y)
fmt.Println("a ", a)
fmt.Println("b ", b)
fmt.Println("c ", c)
fmt.Println("d ", d)
fmt.Println("e ", e)
fmt.Println("f ", f)
}有如下错误:
.\main.go:14:14: syntax error: unexpected comma at end of statement
2. 短变量的声明和赋值
Go 语言的语法允许多值短变量声明和赋值的多个变量中,只要有一个是新变量就可以使用 := 进行赋值。
也就是说,在多值短变量的声明和赋值时, 至少有一个变量是新创建的局部变量,其他的变量可以复用以前的变量,不是新创建的变量执行的仅仅是赋值。
package main
import "fmt"
func main() {
var a int = 0
var b int = 0
a, b := 1, 2
fmt.Println("a ", a)
fmt.Println("b ", b)
}
会有以下错误:
.\main.go:8:10: no new variables on left side of :=
要想通过编译, a, b := 1, 2 至少一个变量是新定义的局部变量,如果在赋值语句 a, b := 1, 2 中已经存在一个局部变量 a ,则赋值语句不会创建新的变量 a , 而是使用 1 赋值给已经声明的局部变量。但是会创建新的变量 b 并给其赋值。
赋值操作符 = 和 := 的区别:
=不会声明并创建新变量,而是在当前赋值语句所在的作用域由内向外逐层去搜寻变量,如果没有搜索到相同的变量名,则报编译错误。:=必须出现在函数或者类型方法内部。:=至少要创建一个局部变量并初始化。
多值短变量声明赋值 := 的最佳使用场景是在错误处理上。例如:
a, err := f()
if err ! = nil {
// do something
}
// 此时 err 可以是已存在的 err 变量,只是重新赋值了
b, err := g()3. 简写模式
Go 语言很多重复的引用或声明可以用 () 进行简写。
import 多个包;
// 推荐写法
import (
"os"
"fmt"
)
// 不推荐写法
import "os"
import "fmt"多个变量声明;
包中多个相关全局变量声明时,建议使用 () 进行合并声明
// 推荐写法
var (
uploadStatus bool
downStatus bool
)
// 不推荐写法
var uploadStatus bool
var uploadStatus bool4. 多值返回函数
多值返回函数里如果有 error 或 bool 类型的返回值,则应该将 error 和 bool 作为最后一个返回值。这是一种编程风格,没有对错。
buffer.go:107: func (b *Buffer) tryGrowByReslice(n int) (int, bool) {
buffer.go:335: func (b *Buffer) ReadByte() (byte , error) {
5. comma,ok 表达式
常见的几个 comma, ok 表达式用法有以下几种情况:
获取 map 值
获取 map 中不存在键的值不会发生异常,而是会返回值类型的零值,如果想确定 map 中是否存在某个 key,则可以使用获取 map 值的 comma, ok 语法。示例如下:
m := make(map[string] string)
v, ok := m["camera"]
if ok {
Println("camera exist")
} else {
Println("camera not exist")
}
读取 chan 值
读取已经关闭的通道不会阻塞,也不会引起 panic, 而是一直返回该通道的零值,判断通道关闭有两种方法,一种是 comma, ok 表达式;另一种是通过 range 循环迭代。
c := make(chan int)
go func() {
c <- 1
c <- 2
close(c)
}()
for {
v, ok := <- c
if ok {
Println("v exist")
} else {
Println("v not exist")
}
}
for v := range c {
Println(v)
}类型断言
如果 map 查找、类型断言或通道接收出现在赋值语句的右边,它们都可能会产生两个结果,有一个额外的布尔结果表示操作是否成功:
v, ok = m[key] // map lookup v, ok = x.(T) // type assertion v, ok = <-ch // channel receive
注意:map 查找、类型断言或通道接收出现在赋值语句的右边时,并不一定是产生两个结果,也可能只产生一个结果。对于只产生一个结果的情形, map 查找失败时会返回零值,类型断言失败时会发生运行时 panic 异常,通道接收失败时会返回零值(阻塞不算是失败)。例如下面的例子:
v = m[key] // map查找,失败时返回零值 v = x.(T) // type断言,失败时panic异常 v = <-ch // 管道接收,失败时返回零值(阻塞不算是失败) _, ok = m[key] // map返回2个值 _, ok = mm[""], false // map返回1个值 _ = mm[""] // map返回1个值
和变量声明一样,我们可以用下划线空白标识符_来丢弃不需要的值。
_, err = io.Copy(dst, src) // 丢弃字节数 _, ok = x.(T) // 只检测类型,忽略具体值
6. 传值规则
Go 只有一种参数传递规则,那就是值拷贝,这种规则包括两种含义:
函数参数传递时使用的是值拷贝。
实例赋值给接口变量,接口对实例的引用是值拷贝。
有时在明明是值拷贝的地方,结果却修改了变量的内容,有以下两种情况:
直接传递的是指针。指针传递同样是值拷贝,但指针和指针副本的值指向的地址是同一个地方,所以能修改实参值。
参数是复合数据类型,这些复合数据类型内部有指针类型的元素,此时参数的值拷贝并不影响指针的指向。
Go 复合类型中 chan 、 map 、 slice 、 interface 内部都是通过指针指向具体的数据,这些类型的变量在作为函数参数传递时,实际上相当于指针的副本。
package main
import "fmt"
func main() {
var x, y int = 3, 5
fmt.Printf("befor swap x is %d\n", x)
fmt.Printf("befor swapy is %d\n", y)
x, y = swap_value(x, y)
fmt.Printf("after swap x is %d\n", x)
fmt.Printf("after swap y is %d\n", y)
x, y = swap_reference(&x, &y)
fmt.Printf("after swap_reference x is %d\n", x)
fmt.Printf("after swap_reference y is %d\n", y)
}
func swap_value(x int, y int) (int, int) {
var tmp int
tmp = x
x = y
y = tmp
return x, y
}
func swap_reference(x *int, y *int) (int, int) {
var tmp int
tmp = *x
*x = *y
*y = tmp
return *x, *y
}输出:
befor swap x is 3
befor swapy is 5
after swap x is 5
after swap y is 3
after swap_reference x is 3
after swap_reference y is 5
以上就是Go习惯用法(多值赋值短变量声明赋值简写模式)基础实例的详细内容,更多关于Go多值赋值短变量声明的资料请关注脚本之家其它相关文章!
