本文的整理于 50 Shades of Go: Traps, Gotchas, and Common Mistakes for New Golang Devs, 下面是自己阅读完源文章后翻译了一些以及加上自己的一点见解。
这些情况使用于 Go 1.5 以及更低版本
目录 #
- 大括号开端不能独立于一行
- 不可存在未使用的变量
- 未引入包问题
- 不可使用 短声明变量 重复声明变量
- Strings 是不可修改的
- 短声明变量只能在函数内部使用
- 不能 声明短变量 同时给实例成员赋值
- 覆盖变量问题
- 不能使用无类型的”nil”去初始化一个变量
- “nil”在 Slices 和 Maps 中的使用问题
- Map 的容量问题
- Strings 不能为”nil”
- 数组作为函数参数问题
- Strings 并不总是 UTF-8 格式的
- string 的长度问题
- 多行 Slice/ 数组 /Map 迭代之行缺少’,’
- log.Fatal 和 log.Panic 不只是记日志
- 内置的数据结构在操作上并不是同步(具有原子性)的
- 在 Strings 中使用 range 遍历的问题
- Fallthrough 在 switch 中的行为
- 加加减减问题
- 位运算中 Not 操作
- 位运算中的优先级问题
- 不可导出的结构变量是不可压缩加密的 (Not Encoded)
- 程序退出却没有把 Goroutines 退出问题
- 向没有缓冲的 channel 发送消息问题
- 向已经关闭的 Channel 发送消息会导致 Panic 问题
- 使用”nil”Channels(未初始化的 Channels))
- 值接收者的方法在调用时不会更改原来的结构变量的值问题
大括号开端不能独立于一行 #
喜欢把{
另起一行的 coder 要抓狂了,因为这在 go 里面这是不允许的
不可存在未使用的变量 #
在 go 里面这是不允许的存在声明却不使用的变量
未引入包问题 #
go 使用 package 这个包的概念来管理代码,这没什么好解释的
不可使用 短声明变量 重复声明变量 #
什么是 短声明变量
(short variable declearation)?
像
a:="xx"
b:="xx"
这里的 a 和 b 就是 短声明变量
若同一代码域里面出现两次a:="xx"
, 编译器就会报错,这种出现两次var a string var a string
一样的道理
Strings 是不可修改的 #
strings 本质就是一连串的 byte, 但它有 []byte 的 length, 你可以使用 len(str) 来获得 str 的长度,但却不能用 str[i]=‘x’去更改 i 下标对应的 byte
短声明变量只能在函数内部使用 #
使用 x:="xxx"
这样的短声明变量去声明一个全局变量是不允许的,要使用var x type
不能 声明短变量 同时给实例成员赋值 #
像这种
structInstace.M,a:="xxx","xxx"
不允许的,可以这样理解,a:="xxx"
与var a="xxx"
等效,而一个结构的实例是不再需要 var
来声明变量的
覆盖变量问题 #
x := 1
fmt.Println(x) //prints 1
{
fmt.Println(x) //prints 1
x := 2
fmt.Println(x) //prints 2
}
fmt.Println(x) //prints 1 (如果你预想 x 的值为 2, 你会失望的)
不能使用无类型的’nil’去初始化一个变量 #
在使用var
关键字去声明一个空值变量时若不指定变量的类型,是允许的
var x = nil //error
“nil”在 Slices 和 Maps 中的使用问题 #
slice 不太一样,你可能会这样使用声明和使用一个 slice
var s []int
s = append(s,1)
但如果是这样使用 map
var m map[string]int
m["one"] = 1 //error
那就会 panic 了,map 必须使用 make(), 如m := make(map[string]int,99)
去初始化后才能使用。
Map 的容量问题 #
map 没有容量,故像 cap(map) 这的操作是不允许的,map 不是 slice!
Strings 不能为”nil” #
var x string = nil //error
这种初始化 string 的方式不允许,同样要判断一个 string 是否为空的方法一个是使用 len(str) 来判断是否大于 0或者直接与 [“”] 比较
数组作为函数参数问题 #
x := [3]int{1,2,3}
func(arr [3]int) {
arr[0] = 7
fmt.Println(arr) //prints [7 2 3]
}(x)
fmt.Println(x) //prints [1 2 3] (not ok if you need [7 2 3])
上面的 x 是一个 array,作为函数参数传入时传的是值,不是地址,故函数内对 array 的更新操作不会影响外面的 array
x := []int{1,2,3}
func(arr []int) {
arr[0] = 7
fmt.Println(arr) //prints [7 2 3]
}(x)
fmt.Println(x) //prints [7 2 3]
上面的 x 是一个 slice,与下面的代码等效
x := [3]int{1,2,3}
func(arr *[3]int) {
(*arr)[0] = 7
fmt.Println(arr) //prints &[7 2 3]
}(&x)
fmt.Println(x) //prints [7 2 3]
怎么判断一个变量是 slice 还是 array?
array 在声明时会指定长度并把所有元素初始化(为 0),而 slice 则不一定,slice 有容量 cap() 和长度 len() 的概念,len<=cap, len 就是直接访问的最大值下标 -1,cap 为预申请了空间但未使用的长度,会随着 slice 的长度使用情况自动增长 (2x)
slice 不是 array, 是指向 array 的一个指针
值接收者的方法在调用时不会更改原来的结构变量的值问题 #
这其实就是指针的基础知识,没什么好解释的
Strings 并不总是 UTF-8 格式的 #
string 可以包含特定的一些字节 (arbitrary bytes),可以使用 utf8.ValidString() 来检查一个字符串是不是 UTF-8 格式
data1 := "ABC"
fmt.Println(utf8.ValidString(data1)) //prints: true
data2 := "A\xfeC"
fmt.Println(utf8.ValidString(data2)) //prints: false
string 的长度问题 #
使用内置的 len() 去获取一个字符串的长度时会根据字符中的符号特殊情况返回不同的情况比如
data := "♥"
fmt.Println(len(data)) //prints: 3
并不是通常所想到的返回 1,因为一个字符 (character) 可能由几个元字节 (runes) 组成,
data := "♥"
fmt.Println(utf8.RuneCountInString(data)) //prints: 1
多行 Slice/ 数组 /Map 迭代之行缺少’,’ #
在声明并初化一个 slice 或者 map 时,如果把每一个元素另起一行,记得在每行末尾加一个 [,], 否则报错;
若最后一个元素紧接一个 [}] 结尾,则不需要 [,]
log.Fatal 和 log.Panic 不只是记日志 #
这两个函数内部的最后会调用os.Exit(1)
, 这会导致整个程序推出
内置的数据结构在操作上并不是同步(具有原子性)的 #
像 map 这种数据结构在进行数据读取时是不会保证读写顺序的,需要自己使用 sync 包的锁或者比较推荐的 channels 去进行数据的读写保护
在 Strings 中使用 range 遍历的问题 #
由于一个 character 可以由几个 rune 组成,所以在处理 character 时记得先了解一下 norm 这个包 (golang.org/x/text/unicode/norm) range 会把每个元素当成 UTF-8 格式去处理,如果它不能识别这个元素则将会返回0xfffd
这个 rune 而不实际的元素数据,所以最好先转换成 byte slice 来获得实际的数据后再使用 range 遍历
data := "A\xfe\x02\xff\x04"
for _,v := range data {
fmt.Printf("%#x ",v)
}
//prints: 0x41 0xfffd 0x2 0xfffd 0x4 (not ok)
fmt.Println()
for _,v := range []byte(data) {
fmt.Printf("%#x ",v)
}
//prints: 0x41 0xfe 0x2 0xff 0x4 (good)
Fallthrough 在 switch 中的行为 #
fallthrough 可以在执行一个 case 再继续向下一个 case 执行,与 case a,b: 有差不多的效果
加加减减问题 #
go 中不支持同时使用两种运算,如data[i++]
中即对 i 的数据进行运算同时也计算获取 i+1 下标中的数据
位运算中 Not 操作 #
不像其它编程语言使用~
来表示位取反操作,go 使用^
来表示*或*和取反操作
var a uint8 = 0x82
var b uint8 = 0x02
fmt.Printf("%08b [A]\n",a)
fmt.Printf("%08b [B]\n",b)
fmt.Printf("%08b (NOT B)\n",^b)
fmt.Printf("%08b ^ %08b = %08b [B XOR 0xff]\n",b,0xff,b ^ 0xff)
fmt.Printf("%08b ^ %08b = %08b [A XOR B]\n",a,b,a ^ b)
fmt.Printf("%08b & %08b = %08b [A AND B]\n",a,b,a & b)
fmt.Printf("%08b &^%08b = %08b [A 'AND NOT' B]\n",a,b,a &^ b)
fmt.Printf("%08b&(^%08b)= %08b [A AND (NOT B)]\n",a,b,a & (^b))
位运算中的优先级问题 #
go 中的运算操作符与其它语言有点区别,拿 cpp 来比较
fmt.Printf("0x2 & 0x2 + 0x4 -> %#x\n",0x2 & 0x2 + 0x4)
//prints: 0x2 & 0x2 + 0x4 -> 0x6
//Go: (0x2 & 0x2) + 0x4
//C++: 0x2 & (0x2 + 0x4) -> 0x2
fmt.Printf("0x2 + 0x2 << 0x1 -> %#x\n",0x2 + 0x2 << 0x1)
//prints: 0x2 + 0x2 << 0x1 -> 0x6
//Go: 0x2 + (0x2 << 0x1)
//C++: (0x2 + 0x2) << 0x1 -> 0x8
fmt.Printf("0xf | 0x2 ^ 0x2 -> %#x\n",0xf | 0x2 ^ 0x2)
//prints: 0xf | 0x2 ^ 0x2 -> 0xd
//Go: (0xf | 0x2) ^ 0x2
//C++: 0xf | (0x2 ^ 0x2) -> 0xf
不想记得这些优先级的次序,最稳妥的方法把 [()] 把这个运算包起来
不可导出的结构变量是不可压缩加密的 (Not Encoded) #
对结构数据进行 encoded 时,不可导出的字段数据 (Unexported Structure Fields) 最不会被处理的,比如下面的数据
type MyData struct {
One int
two string
}
{
in := MyData{1,"two"}
fmt.Printf("%#v\n",in) //prints main.MyData{One:1, two:"two"}
encoded,_ := json.Marshal(in)
fmt.Println(string(encoded)) //prints {"One":1}
var out MyData
json.Unmarshal(encoded,&out)
fmt.Printf("%#v\n",out) //prints main.MyData{One:1, two:""}
}
two 在 Marshal 后不会生成数据
程序退出却没有把 Goroutines 退出问题 #
主程序退出时没有把运行着的 goroutines 退出会浪费系统资源,容易造成系统不稳定,
可以通过sync.WaitGroup
来进行退出同步操作,或者 select 读一个 channel, 当关闭这个 channel 时 return 相应的 goroutine 上的 function()
a := make(chan int, 1)
close(a)
...
//goroutine
for {
select {
case <-a:
return
}
}
向没有缓冲的 channel 发送消息问题 #
如果初始化时指定 channel 的缓冲数量,默认为 1,即写一个在读一个后才能再写
若缓冲数大于 1,则在缓冲未满时可写
向已经关闭的 Channel 发送消息会导致 Panic 问题 #
不只在读 channel 可以select
来选择可读的 channel, 往 channel 写时,也可以用select
来选择可以写的 channel
ch := make(chan int)
done := make(chan struct{})
for i := 0; i < 3; i++ {
go func(idx int) {
select {
case ch <- (idx + 1) * 2: fmt.Println(idx,"sent result")
case <- done: fmt.Println(idx,"exiting")
}
}(i)
}
使用”nil”Channels(未初始化的 Channels) #
在 nil 的 channel 上读写数据会把 goroutine 一直堵塞
var ch chan int
for i := 0; i < 3; i++ {
go func(idx int) {
ch <- (idx + 1) * 2
}(i)
}