funcmain() { var one int// error: one declared and not used two := 2// error: two declared and not used var three int// error: three declared and not used three = 3 }
// 正确示例 // 可以直接注释或移除未使用的变量 funcmain() { var one int _ = one two := 2 println(two) var three int one = three
// 错误示例 funcmain() { m := make(map[string]int, 99) println(cap(m)) // error: invalid argument m1 (type map[string]int) for cap }
11. string 类型的变量值不能为 nil
对那些喜欢用 nil 初始化字符串的人来说,这就是坑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// 错误示例 funcmain() { var s string = nil// cannot use nil as type string in assignment if s == nil { // invalid operation: s == nil (mismatched types string and nil) s = "default" } }
// 正确示例 funcmain() { var s string// 字符串类型的零值是空串 "" if s == "" { s = "default" } }
Go 则会返回元素对应数据类型的零值,比如 nil、'' 、false 和 0,取值操作总有值返回,故不能通过取出来的值来判断 key 是不是在 map 中。
检查 key 是否存在可以用 map 直接访问,检查返回的第二个参数即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// 错误的 key 检测方式 funcmain() { x := map[string]string{"one": "2", "two": "", "three": "3"} if v := x["two"]; v == "" { fmt.Println("key two is no entry") // 键 two 存不存在都会返回的空字符串 } }
// 正确示例 funcmain() { x := map[string]string{"one": "2", "two": "", "three": "3"} if _, ok := x["two"]; !ok { fmt.Println("key two is no entry") } }
funcdoIt(workerID int, ch <-chaninterface{}, done <-chanstruct{}, wg *sync.WaitGroup) { fmt.Printf("[%v] is running\n", workerID) defer wg.Done() for { select { case m := <-ch: fmt.Printf("[%v] m => %v\n", workerID, m) case <-done: fmt.Printf("[%v] is done\n", workerID) return } } }
// 指定字段类型 funcmain() { var data = []byte(`{"status": 200}`) var result map[string]interface{} var decoder = json.NewDecoder(bytes.NewReader(data)) decoder.UseNumber()
if err := decoder.Decode(&result); err != nil { log.Fatalln(err) }
var status, _ = result["status"].(json.Number).Int64() fmt.Println("Status value: ", status) }
// 你可以使用 string 来存储数值数据,在 decode 时再决定按 int 还是 float 使用 // 将数据转为 decode 为 string funcmain() { var data = []byte({"status": 200}) var result map[string]interface{} var decoder = json.NewDecoder(bytes.NewReader(data)) decoder.UseNumber() if err := decoder.Decode(&result); err != nil { log.Fatalln(err) } var status uint64 err := json.Unmarshal([]byte(result["status"].(json.Number).String()), &status); checkError(err) fmt.Println("Status value: ", status) }
- 使用 struct 类型将你需要的数据映射为数值型
1 2 3 4 5 6 7 8 9 10 11
// struct 中指定字段类型 funcmain() { var data = []byte(`{"status": 200}`) var result struct { Status uint64`json:"status"` }
// 状态名称可能是 int 也可能是 string,指定为 json.RawMessage 类型 funcmain() { records := [][]byte{ []byte(`{"status":200, "tag":"one"}`), []byte(`{"status":"ok", "tag":"two"}`), }
for idx, record := range records { var result struct { StatusCode uint64 StatusName string Status json.RawMessage `json:"status"` Tag string`json:"tag"` }
在 range 迭代中,得到的值其实是元素的一份值拷贝,更新拷贝并不会更改原来的元素,即是拷贝的地址并不是原有元素的地址:
1 2 3 4 5 6 7
funcmain() { data := []int{1, 2, 3} for _, v := range data { v *= 10// data 中原有元素是不会被修改的 } fmt.Println("data: ", data) // data: [1 2 3] }
如果要修改原有元素的值,应该使用索引直接访问:
1 2 3 4 5 6 7
funcmain() { data := []int{1, 2, 3} for i, v := range data { data[i] = v * 10 } fmt.Println("data: ", data) // data: [10 20 30] }
如果你的集合保存的是指向值的指针,需稍作修改。依旧需要使用索引访问元素,不过可以使用 range 出来的元素直接更新原有值:
1 2 3 4 5 6 7
funcmain() { data := []*struct{ num int }{{1}, {2}, {3},} for _, v := range data { v.num *= 10// 直接使用指针更新 } fmt.Println(data[0], data[1], data[2]) // &{10} &{20} &{30} }
// 错误示例 funcmain() { data := []field{{"one"}, {"two"}, {"three"}} for _, v := range data { go v.print() } time.Sleep(3 * time.Second) // 输出 three three three }
// 正确示例 funcmain() { data := []field{{"one"}, {"two"}, {"three"}} for _, v := range data { v := v go v.print() } time.Sleep(3 * time.Second) // 输出 one two three }
// 正确示例 funcmain() { data := []*field{{"one"}, {"two"}, {"three"}} for _, v := range data { // 此时迭代值 v 是三个元素值的地址,每次 v 指向的值不同 go v.print() } time.Sleep(3 * time.Second) // 输出 one two three }
47. defer 函数的参数值
对 defer 延迟执行的函数,它的参数会在声明时候就会求出具体值,而不是在执行时才求值:
1 2 3 4 5 6
// 在 defer 函数中参数会提前求值 funcmain() { var i = 1 defer fmt.Println("result: ", func()int { return i * 2 }()) i++ }
// 错误示例 funcmain() { var data interface{} = "great"
// data 混用 if data, ok := data.(int); ok { fmt.Println("[is an int], data: ", data) } else { fmt.Println("[not an int], data: ", data) // [isn't a int], data: 0 } }
// 正确示例 funcmain() { var data interface{} = "great"
if res, ok := data.(int); ok { fmt.Println("[is an int], data: ", res) } else { fmt.Println("[not an int], data: ", data) // [not an int], data: great } }
funcFirst(query string, replicas []Search)Result { c := make(chan Result) replicaSearch := func(i int) { c <- replicas[i](query) } for i := range replicas { go replicaSearch(i) } return <-c }
funcFirst(query string, replicas ...Search)Result { c := make(chan Result,len(replicas)) searchReplica := func(i int) { c <- replicas[i](query) } for i := range replicas { go searchReplica(i) } return <-c }
funcFirst(query string, replicas ...Search)Result { c := make(chan Result,1) searchReplica := func(i int) { select { case c <- replicas[i](query): default: } } for i := range replicas { go searchReplica(i) } return <-c }
funcFirst(query string, replicas ...Search)Result { c := make(chan Result) done := make(chanstruct{}) deferclose(done) searchReplica := func(i int) { select { case c <- replicas[i](query): case <- done: } } for i := range replicas { go searchReplica(i) }
return <-c }
Rob Pike 为了简化演示,没有提及演讲代码中存在的这些问题。不过对于新手来说,可能会不加思考直接使用。
// 错误示例 funcmain() { doIt := func(arg int)interface{} { var result *struct{} = nil if arg > 0 { result = &struct{}{} } return result }
if res := doIt(-1); res != nil { fmt.Println("Good result: ", res) // Good result: <nil> fmt.Printf("%T\n", res) // *struct {} // res 不是 nil,它的值为 nil fmt.Printf("%v\n", res) // <nil> } }
// 正确示例 funcmain() { doIt := func(arg int)interface{} { var result *struct{} = nil if arg > 0 { result = &struct{}{} } else { returnnil// 明确指明返回 nil } return result }
if res := doIt(-1); res != nil { fmt.Println("Good result: ", res) } else { fmt.Println("Bad result: ", res) // Bad result: <nil> } }
54. 堆栈变量
你并不总是清楚你的变量是分配到了堆还是栈。
在 C++ 中使用 new 创建的变量总是分配到堆内存上的,但在 Go 中即使使用 new()、make() 来创建变量,变量为内存分配位置依旧归 Go 编译器管。
Go 编译器会根据变量的大小及其 “escape analysis” 的结果来决定变量的存储位置,故能准确返回本地变量的地址,这在 C/C++ 中是不行的。
在 go build 或 go run 时,加入 -m 参数,能准确分析程序的变量分配位置:
55. GOMAXPROCS、Concurrency(并发)and Parallelism(并行)
Go 1.4 及以下版本,程序只会使用 1 个执行上下文 / OS 线程,即任何时间都最多只有 1 个 goroutine 在执行。
Go 1.5 版本将可执行上下文的数量设置为 runtime.NumCPU() 返回的逻辑 CPU 核心数,这个数与系统实际总的 CPU 逻辑核心数是否一致,取决于你的 CPU 分配给程序的核心数,可以使用 GOMAXPROCS 环境变量或者动态的使用 runtime.GOMAXPROCS() 来调整。
funcmain() { var one int// error: one declared and not used two := 2// error: two declared and not used var three int// error: three declared and not used three = 3 }
// 正确示例 // 可以直接注释或移除未使用的变量 funcmain() { var one int _ = one two := 2 println(two) var three int one = three
// 错误示例 funcmain() { m := make(map[string]int, 99) println(cap(m)) // error: invalid argument m1 (type map[string]int) for cap }
11. string 类型的变量值不能为 nil
对那些喜欢用 nil 初始化字符串的人来说,这就是坑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// 错误示例 funcmain() { var s string = nil// cannot use nil as type string in assignment if s == nil { // invalid operation: s == nil (mismatched types string and nil) s = "default" } }
// 正确示例 funcmain() { var s string// 字符串类型的零值是空串 "" if s == "" { s = "default" } }
Go 则会返回元素对应数据类型的零值,比如 nil、'' 、false 和 0,取值操作总有值返回,故不能通过取出来的值来判断 key 是不是在 map 中。
检查 key 是否存在可以用 map 直接访问,检查返回的第二个参数即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// 错误的 key 检测方式 funcmain() { x := map[string]string{"one": "2", "two": "", "three": "3"} if v := x["two"]; v == "" { fmt.Println("key two is no entry") // 键 two 存不存在都会返回的空字符串 } }
// 正确示例 funcmain() { x := map[string]string{"one": "2", "two": "", "three": "3"} if _, ok := x["two"]; !ok { fmt.Println("key two is no entry") } }
funcdoIt(workerID int, ch <-chaninterface{}, done <-chanstruct{}, wg *sync.WaitGroup) { fmt.Printf("[%v] is running\n", workerID) defer wg.Done() for { select { case m := <-ch: fmt.Printf("[%v] m => %v\n", workerID, m) case <-done: fmt.Printf("[%v] is done\n", workerID) return } } }
// 指定字段类型 funcmain() { var data = []byte(`{"status": 200}`) var result map[string]interface{} var decoder = json.NewDecoder(bytes.NewReader(data)) decoder.UseNumber()
if err := decoder.Decode(&result); err != nil { log.Fatalln(err) }
var status, _ = result["status"].(json.Number).Int64() fmt.Println("Status value: ", status) }
// 你可以使用 string 来存储数值数据,在 decode 时再决定按 int 还是 float 使用 // 将数据转为 decode 为 string funcmain() { var data = []byte({"status": 200}) var result map[string]interface{} var decoder = json.NewDecoder(bytes.NewReader(data)) decoder.UseNumber() if err := decoder.Decode(&result); err != nil { log.Fatalln(err) } var status uint64 err := json.Unmarshal([]byte(result["status"].(json.Number).String()), &status); checkError(err) fmt.Println("Status value: ", status) }
- 使用 struct 类型将你需要的数据映射为数值型
1 2 3 4 5 6 7 8 9 10 11
// struct 中指定字段类型 funcmain() { var data = []byte(`{"status": 200}`) var result struct { Status uint64`json:"status"` }
// 状态名称可能是 int 也可能是 string,指定为 json.RawMessage 类型 funcmain() { records := [][]byte{ []byte(`{"status":200, "tag":"one"}`), []byte(`{"status":"ok", "tag":"two"}`), }
for idx, record := range records { var result struct { StatusCode uint64 StatusName string Status json.RawMessage `json:"status"` Tag string`json:"tag"` }
在 range 迭代中,得到的值其实是元素的一份值拷贝,更新拷贝并不会更改原来的元素,即是拷贝的地址并不是原有元素的地址:
1 2 3 4 5 6 7
funcmain() { data := []int{1, 2, 3} for _, v := range data { v *= 10// data 中原有元素是不会被修改的 } fmt.Println("data: ", data) // data: [1 2 3] }
如果要修改原有元素的值,应该使用索引直接访问:
1 2 3 4 5 6 7
funcmain() { data := []int{1, 2, 3} for i, v := range data { data[i] = v * 10 } fmt.Println("data: ", data) // data: [10 20 30] }
如果你的集合保存的是指向值的指针,需稍作修改。依旧需要使用索引访问元素,不过可以使用 range 出来的元素直接更新原有值:
1 2 3 4 5 6 7
funcmain() { data := []*struct{ num int }{{1}, {2}, {3},} for _, v := range data { v.num *= 10// 直接使用指针更新 } fmt.Println(data[0], data[1], data[2]) // &{10} &{20} &{30} }
// 错误示例 funcmain() { data := []field{{"one"}, {"two"}, {"three"}} for _, v := range data { go v.print() } time.Sleep(3 * time.Second) // 输出 three three three }
// 正确示例 funcmain() { data := []field{{"one"}, {"two"}, {"three"}} for _, v := range data { v := v go v.print() } time.Sleep(3 * time.Second) // 输出 one two three }
// 正确示例 funcmain() { data := []*field{{"one"}, {"two"}, {"three"}} for _, v := range data { // 此时迭代值 v 是三个元素值的地址,每次 v 指向的值不同 go v.print() } time.Sleep(3 * time.Second) // 输出 one two three }
47. defer 函数的参数值
对 defer 延迟执行的函数,它的参数会在声明时候就会求出具体值,而不是在执行时才求值:
1 2 3 4 5 6
// 在 defer 函数中参数会提前求值 funcmain() { var i = 1 defer fmt.Println("result: ", func()int { return i * 2 }()) i++ }
// 错误示例 funcmain() { var data interface{} = "great"
// data 混用 if data, ok := data.(int); ok { fmt.Println("[is an int], data: ", data) } else { fmt.Println("[not an int], data: ", data) // [isn't a int], data: 0 } }
// 正确示例 funcmain() { var data interface{} = "great"
if res, ok := data.(int); ok { fmt.Println("[is an int], data: ", res) } else { fmt.Println("[not an int], data: ", data) // [not an int], data: great } }
funcFirst(query string, replicas []Search)Result { c := make(chan Result) replicaSearch := func(i int) { c <- replicas[i](query) } for i := range replicas { go replicaSearch(i) } return <-c }
funcFirst(query string, replicas ...Search)Result { c := make(chan Result,len(replicas)) searchReplica := func(i int) { c <- replicas[i](query) } for i := range replicas { go searchReplica(i) } return <-c }
funcFirst(query string, replicas ...Search)Result { c := make(chan Result,1) searchReplica := func(i int) { select { case c <- replicas[i](query): default: } } for i := range replicas { go searchReplica(i) } return <-c }
funcFirst(query string, replicas ...Search)Result { c := make(chan Result) done := make(chanstruct{}) deferclose(done) searchReplica := func(i int) { select { case c <- replicas[i](query): case <- done: } } for i := range replicas { go searchReplica(i) }
return <-c }
Rob Pike 为了简化演示,没有提及演讲代码中存在的这些问题。不过对于新手来说,可能会不加思考直接使用。
// 错误示例 funcmain() { doIt := func(arg int)interface{} { var result *struct{} = nil if arg > 0 { result = &struct{}{} } return result }
if res := doIt(-1); res != nil { fmt.Println("Good result: ", res) // Good result: <nil> fmt.Printf("%T\n", res) // *struct {} // res 不是 nil,它的值为 nil fmt.Printf("%v\n", res) // <nil> } }
// 正确示例 funcmain() { doIt := func(arg int)interface{} { var result *struct{} = nil if arg > 0 { result = &struct{}{} } else { returnnil// 明确指明返回 nil } return result }
if res := doIt(-1); res != nil { fmt.Println("Good result: ", res) } else { fmt.Println("Bad result: ", res) // Bad result: <nil> } }
54. 堆栈变量
你并不总是清楚你的变量是分配到了堆还是栈。
在 C++ 中使用 new 创建的变量总是分配到堆内存上的,但在 Go 中即使使用 new()、make() 来创建变量,变量为内存分配位置依旧归 Go 编译器管。
Go 编译器会根据变量的大小及其 “escape analysis” 的结果来决定变量的存储位置,故能准确返回本地变量的地址,这在 C/C++ 中是不行的。
在 go build 或 go run 时,加入 -m 参数,能准确分析程序的变量分配位置:
55. GOMAXPROCS、Concurrency(并发)and Parallelism(并行)
Go 1.4 及以下版本,程序只会使用 1 个执行上下文 / OS 线程,即任何时间都最多只有 1 个 goroutine 在执行。
Go 1.5 版本将可执行上下文的数量设置为 runtime.NumCPU() 返回的逻辑 CPU 核心数,这个数与系统实际总的 CPU 逻辑核心数是否一致,取决于你的 CPU 分配给程序的核心数,可以使用 GOMAXPROCS 环境变量或者动态的使用 runtime.GOMAXPROCS() 来调整。