22、内建的数据结构操作不是同步的
即使Go本身有很多特性来支持并发,并发安全的数据集合并不是其中之一,确保数据集合以原子的方式更新是你的职责。Goroutines和channels是实现这些原子操作的推荐方式,但你也可以使用“sync”包,如果它对你的应用有意义的话。
23、String在“range”语句中的迭代值
索引值(“range”操作返回的第一个值)是返回的第二个值的当前“字符”(unicode编码的point/rune)的第一个byte的索引。它不是当前“字符”的索引,这与其他语言不同。注意真实的字符可能会由多个rune表示。如果你需要处理字符,确保你使用了“norm”包(golang.org/x/text/unicode/norm)。
string变量的for range语句将会尝试把数据翻译为UTF8文本。对于它无法理解的任何byte序列,它将返回0xfffd runes(即unicode替换字符),而不是真实的数据。如果你任意(非UTF8文本)的数据保存在string变量中,确保把它们转换为byte slice,以得到所有保存的数据。
package main
import "fmt"
func main() {
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)
}
运行结果:
0x41 0xfffd 0x2 0xfffd 0x4
0x41 0xfe 0x2 0xff 0x4
24、对Map使用“for range”语句迭代
如果你希望以某个顺序(比如,按key值排序)的方式得到元素,就需要这个技巧。每次的map迭代将会生成不同的结果。Go的runtime有心尝试随机化迭代顺序,但并不总会成功,这样你可能得到一些相同的map迭代结果。所以如果连续看到5个相同的迭代结果,不要惊讶。
package main
import "fmt"
func main() {
m := map[string]int{"one": 1, "two": 2, "three": 3, "four": 4}
for k, v := range m {
fmt.Println(k, v)
}
}
而且如果你使用Go的游乐场(https://play.golang.org/),你将总会得到同样的结果,因为除非你修改代码,否则它不会重新编译代码。
25、"switch"声明中的失效行为
在“switch”声明语句中的“case”语句块在默认情况下会break。这和其他语言中的进入下一个“next”代码块的默认行为不同。
package main
import "fmt"
func main() {
isSpace := func(ch byte) bool {
switch ch {
case ' ':
case '\t':
return true
}
return false
}
fmt.Println(isSpace('\t'))
fmt.Println(isSpace(' '))
}
运行结果:
true
false
你可以通过在每个“case”块的结尾使用“fallthrough”,来强制“case”代码块进入。你也可以重写switch语句,来使用“case”块中的表达式列表。
package main
import "fmt"
func main() {
isSpace := func(ch byte) bool {
switch ch {
case ' ':
fallthrough
case '\t':
return true
}
return false
}
fmt.Println(isSpace('\t'))
fmt.Println(isSpace(' '))
}
package main
import "fmt"
func main() {
isSpace := func(ch byte) bool {
switch ch {
case ' ', '\t':
return true
}
return false
}
fmt.Println(isSpace('\t'))
fmt.Println(isSpace(' '))
}
运行结果:
true
true
26、自增和自减
许多语言都有自增和自减操作。不像其他语言,Go不支持前置版本的操作。你也无法在表达式中使用这两个操作符。
错误代码:
package main
import (
"fmt"
)
func main() {
data := []int{1, 2, 3}
i := 0
++i
fmt.Println(data[i++])
}
编译错误:
./main.go:10:2: syntax error: unexpected ++, expecting }
正确代码:
package main
import "fmt"
func main() {
data := []int{1, 2, 3}
i := 0
i++
fmt.Println(data[i])
}
27、按位NOT操作
许多语言使用 ~作为一元的NOT操作符(即按位补足),但Go为了这个重用了XOR操作符(^)。
错误代码:
package main
import "fmt"
func main() {
fmt.Println(~2)
}
编译错误:
./main.go:6:14: bitwise complement operator is ^
正确代码:
package main
import "fmt"
func main() {
var d uint8 = 2
fmt.Printf("%08b\n", ^d)
}
Go依旧使用^作为XOR的操作符,这可能会让一些人迷惑。
如果你愿意,你可以使用一个二元的XOR操作(如, 0x02 XOR 0xff)来表示一个一元的NOT操作(如,NOT 0x02)。这可以解释为什么^被重用来表示一元的NOT操作。
Go也有特殊的‘AND NOT’按位操作(&^),这也让NOT操作更加的让人迷惑。这看起来需要特殊的特性/hack来支持 A AND (NOT B),而无需括号。
package main
import "fmt"
func main() {
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))
}
28、操作优先级的差异
除了”bit clear“操作(&^),Go也一个与许多其他语言共享的标准操作符的集合。尽管操作优先级并不总是一样。
package main
import "fmt"
func main() {
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
}
运行结果:
0x2 & 0x2 + 0x4 -> 0x6
0x2 + 0x2 << 0x1 -> 0x6
0xf | 0x2 ^ 0x2 -> 0xd