Golang 的一些陷阱(一)
这里是 50 Shades of Go: Traps,Gotchas,and Common Mistakes for New Golang Devs 的一些总结, 忽略了一些原文提到编译器能检查到的陷阱, 我想这些陷阱写代码是多遇到几次,就会非常熟悉了o(∩_∩)o 。
关于空白符 "_"
原文中提到了 用"_"去保证一些未被引用的变量,导入包 能到顺利编译。空白符在实际应用中的作用主要是
- 当我们不需要显式的调用某个包中的功能,仅需要它正确初始化时:
import ( _ "xxx/abc" // xxx/abc包中的 init函数将会被调用 )
- 忽略某些不需要的值
nums := []int{2,3,4} sum := 0 for _,num := range nums { // 忽略数组index sum += num }
- 开发阶段标记或调试
import ( _ "xxx/abc" // 仅仅是提示后面开发会用到这个包 )
关于操作符
-
异或(XOR) 和 按位求反 是同一个操作符 "^"
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,a ^ b) fmt.Printf("%08b & %08b = %08b [A AND B]\n",a & b) fmt.Printf("%08b &^%08b = %08b [A 'AND NOT' B]\n",a &^ b) fmt.Printf("%08b&(^%08b)= %08b [A AND (NOT B)]\n",a & (^b)) }
-
操作符及其优先级
+ sum integers,floats,complex values,strings - difference integers,complex values * product integers,complex values / quotient integers,complex values % remainder integers & bitwise AND integers | bitwise OR integers ^ bitwise XOR integers &^ bit clear (AND NOT) integers << left shift integer << unsigned integer >> right shift integer >> unsigned integer Precedence Operator 5 * / % << >> & &^ 4 + - | ^ 3 == != < <= > >= 2 && 1 ||
++和--的优先级由外层操作符确定, 比如*p++和(*p)++是一样的
意外的变量屏蔽
如下代码: ``` package main
import "fmt" func main() { x := 1 fmt.Println(x) // prints 1 { fmt.Println(x) // prints 1 x := 2 // 重新定义和赋值x,这里的x相当于一个新的变量,但作用域只在当前花括号内 fmt.Println(x) // prints 2 } fmt.Println(x) // prints 1 (这里你需要2的话, 将会是个错误) } ```
我们可以用 @H_301_47@go tool vet -shadow xxx.go 来检测这样的陷阱。
关于"nil"
- 不要用nil去初始化类型不明确的变量,如:
var x = nil // it is error var x interface{} = nil // it is ok
- 为nil的切片可以直接使用, 但使用为nil的map,将会panic。
// it is ok for slices var s []int s = append(s,1) // here will panic var m map[string]int m["one"] = 1 //error
- string 不能初始化为nil,string的初始化默认值是""。
var x string = nil //error var x string // ok
关于数组 与 切片
-
数组是值类型的,所以它在相互赋值和作为函数参数时会发生数据拷贝, 通常情况下应避免这样情况出现。
package main import "fmt" func main() { 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]) }
-
"var a [3]int" 与 "var b [5]byte" 中, a 和 b 是不同的类型。 不能相互直接赋值。
-
Golang只支持一维数组和一维切片, 可以通过一维的数组或切片构建多维数组和切片, 必须自己负责索引,边界检测 和 初始化及内存分配。
import "fmt" func main() { h,w := 2,4 raw := make([]int,h*w) for i := range raw { raw[i] = i } fmt.Println(raw,&raw[4]) //prints: [0 1 2 3 4 5 6 7] <ptr_addr_x> table := make([][]int,h) for i:= range table { table[i] = raw[i*w:i*w + w] } fmt.Println(table,&table[1][0]) //prints: [[0 1 2 3] [4 5 6 7]] <ptr_addr_x> }
关于 map
- 访问不存在的 map key
x := map[string]string{"one":"a","two":"","three":"c"} if v := x["two"]; v == "" { //incorrect,当key不存在时 v == "" 也为true fmt.Println("no entry") } // ................................................ if _,ok := x["two"]; !ok { //正确的方法 fmt.Println("no entry") }
- map中的数据是无序的,当通过for range 去遍历map时,每次得到的数据顺序都可能是不一样的。
关于switch
- switch中 匹配当前case后,将会从switch 退出,若想继续执行下一个case,可以使用 fallthrough 关键字。
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')) //prints true (ok) fmt.Println(isSpace(' ')) //prints also true (ok) }
关于 string
- string是不可以变的,如果要修改string的值, 可以将其转换为byte切片, 如下:
package main import "fmt" func main() { x := "text" xbytes := []byte(x) xbytes[0] = 'T' fmt.Println(string(xbytes)) //prints Text }
- string和[]byte相互转换会发送数据拷贝, 目前来说两种情况除外:
- key为[]byte,当它作为map[string]的key去访问map[string]时: m[string(key)]
- 在 for range中转换, 如下
for i,v := range []byte(str) {...}
- string做索引操作时返回的是byte而不是character:
x := "text" fmt.Println(x[0]) //print 116 fmt.Printf("%T",x[0]) //prints uint8
若想访问字符串中的字符应用 for range 或转化成runes切片 - string中并不总是UTF8 字符,它可以包含任何字符
package main import ( "fmt" "unicode/utf8" ) func main() { data1 := "ABC" fmt.Println(utf8.ValidString(data1)) //prints: true data2 := "A\xfeC" fmt.Println(utf8.ValidString(data2)) //prints: false }
- 在golang中 string是一个rune序列, 一个UTF8字符由一个或多个rune组成。 rune对应的是字符编码中的Code point概念。
package main import ( "bytes" "fmt" "strings" "unicode/utf8" ) func main() { data := "é中文" //é由两个rune组成,中和文各由一个rune组成 fmt.Println(len(data)) //prints: 9 fmt.Println(utf8.RuneCountInString(data)) //prints: 4 fmt.Println(strings.Count(data,"") - 1) //prints: 4 fmt.Println(bytes.Count([]byte(data),nil) - 1) //prints: 4 }
- 通过for range可以获取string中的rune,但当string含有非UTF8字符时,如果不能被for range理解的话将会将原有数据用0xfffd代替。此时如果要正确获取到string中的数据,可以将string转化成[]byte
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) }
关于struct
- 未导出的结构体字段,不能被json,xml,gob之类的编码和解码。
package main import ( "fmt" "encoding/json" ) type MyData struct { One int two string } func main() { 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:""} }
- 值类型Receiver的方法不能改变原有变量的中的数据, 指针类型的则可以。
package main import "fmt" type data struct { num int key *string items map[string]bool } func (this *data) pmethod() { //this 将改变原有变量的值 this.num = 7 } func (this data) vmethod() { //这里this 将会是原有变量的拷贝,所以对this的任何修改都不会影响原有变量。 this.num = 8 *this.key = "v.key" this.items["vmethod"] = true } func main() { key := "key.1" d := data{1,&key,make(map[string]bool)} fmt.Printf("num=%v key=%v items=%v\n",d.num,*d.key,d.items) //prints num=1 key=key.1 items=map[] d.pmethod() fmt.Printf("num=%v key=%v items=%v\n",d.items) //prints num=7 key=key.1 items=map[] d.vmethod() fmt.Printf("num=%v key=%v items=%v\n",d.items) //prints num=7 key=v.key items=map[vmethod:true] }
log库
- log库中 log.Fatal*() 和 log.Panic*() 会导致程序退出。