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的话, 将会是个错误)
}
```
我们可以用 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
关于 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 := "AxfeC"
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 := "e?中文" //e?由两个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 := "Axfex02xffx04"
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("%#vn",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("%#vn",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=%vn",d.num,*d.key,d.items)
//prints num=1 key=key.1 items=map[]
d.pmethod()
fmt.Printf("num=%v key=%v items=%vn",d.items)
//prints num=7 key=key.1 items=map[]
d.vmethod()
fmt.Printf("num=%v key=%v items=%vn",d.items)
//prints num=7 key=v.key items=map[vmethod:true]
}
log库
- log库中 log.Fatal*() 和 log.Panic*() 会导致程序退出。
关于并发
- 在并发操作中, 内置的数据结构是不安全的, 需要自己去保证数据安全。
- 程序不会等待所有goroutine完成,当程序退出后, 所有goroutine也会被终止。可以采用channel,sync.WaitGroup去等待goroutine完成。
- 为nil的channel将会永久block程序。
- 向关闭的channel发送数据将会panic。
(编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|