golang 新手要注意的陷阱和常見(jiàn)錯(cuò)誤

Golang 新手要注意的陷阱和常見(jiàn)錯(cuò)誤

Go是一門簡(jiǎn)單有趣的語(yǔ)言,但與其他語(yǔ)言類似,它會(huì)有一些技巧彪薛。。怠蹂。這些技巧的絕大部分并不是Go的缺陷造成的陪汽。如果你以前使用的是其他語(yǔ)言,那么這其中的有些錯(cuò)誤就是很自然的陷阱褥蚯。其它的是由錯(cuò)誤的假設(shè)和缺少細(xì)節(jié)造成的。

如果你花時(shí)間學(xué)習(xí)這門語(yǔ)言况增,閱讀官方說(shuō)明赞庶、wiki、郵件列表討論澳骤、大量的優(yōu)秀博文和Rob Pike的展示歧强,以及源代碼,這些技巧中的絕大多數(shù)都是顯而易見(jiàn)的为肮。盡管不是每個(gè)人都是以這種方式開始學(xué)習(xí)的摊册,但也沒(méi)關(guān)系。如果你是Go語(yǔ)言新人颊艳,那么這里的信息將會(huì)節(jié)約你大量的調(diào)試代碼的時(shí)間茅特。

初級(jí)

開大括號(hào)不能放在單獨(dú)的一行

在大多數(shù)其他使用大括號(hào)的語(yǔ)言中,你需要選擇放置它們的位置棋枕。Go的方式不同白修。你可以為此感謝下自動(dòng)分號(hào)的注入(沒(méi)有預(yù)讀)。是的重斑,Go中也是有分號(hào)的:-)
失敗的例子:

package main

import "fmt"

func main()  
{ //error, can't have the opening brace on a separate line
    fmt.Println("hello there!")
}

編譯錯(cuò)誤:

/tmp/sandbox826898458/main.go:6: syntax error: unexpected semicolon or newline before {

有效的例子:

package main

import "fmt"

func main() {  
    fmt.Println("works!")
}

未使用的變量

如果你有未使用的變量兵睛,代碼將編譯失敗。當(dāng)然也有例外窥浪。在函數(shù)內(nèi)一定要使用聲明的變量祖很,但未使用的全局變量是沒(méi)問(wèn)題的。
如果你給未使用的變量分配了一個(gè)新的值漾脂,代碼還是會(huì)編譯失敗假颇。你需要在某個(gè)地方使用這個(gè)變量,才能讓編譯器愉快的編譯符相。
Fails:

package main

var gvar int //not an error

func main() {  
    var one int   //error, unused variable
    two := 2      //error, unused variable
    var three int //error, even though it's assigned 3 on the next line
    three = 3     
}

Compile Errors:

/tmp/sandbox473116179/main.go:6: one declared and not used
/tmp/sandbox473116179/main.go:7: two declared and not used
/tmp/sandbox473116179/main.go:8: three declared and not used

Works:

package main

import "fmt"

func main() {  
    var one int
    _ = one

    two := 2 
    fmt.Println(two)

    var three int 
    three = 3
    one = three

    var four int
    four = four
}

另一個(gè)選擇是注釋掉或者移除未使用的變量 :-)

未使用的Imports

如果你引入一個(gè)包拆融,而沒(méi)有使用其中的任何函數(shù)蠢琳、接口、結(jié)構(gòu)體或者變量的話镜豹,代碼將會(huì)編譯失敗傲须。
你可以使用goimports來(lái)增加引入或者移除未使用的引用:

$ go get golang.org/x/tools/cmd/goimports

如果你真的需要引入的包,你可以添加一個(gè)下劃線標(biāo)記符趟脂,_泰讽,來(lái)作為這個(gè)包的名字,從而避免編譯失敗昔期。下滑線標(biāo)記符用于引入已卸,但不使用。

Fails:

package main

import (  
    "fmt"
    "log"
    "time"
)

func main() {  
}

Compile Errors:

/tmp/sandbox627475386/main.go:4: imported and not used: "fmt"
/tmp/sandbox627475386/main.go:5: imported and not used: "log"
/tmp/sandbox627475386/main.go:6: imported and not used: "time"

Works:

package main

import (  
    _ "fmt"
    "log"
    "time"
)

var _ = log.Println

func main() {  
    _ = time.Now
}

另一個(gè)選擇是移除或者注釋掉未使用的imports :-)

簡(jiǎn)式的變量聲明僅可以在函數(shù)內(nèi)部使用

Fails:

package main

myvar := 1 //error

func main() {  
}

Compile Error:

/tmp/sandbox265716165/main.go:3: non-declaration statement outside function body

Works:

package main

var myvar = 1

func main() {  
}

使用簡(jiǎn)式聲明重復(fù)聲明變量

你不能在一個(gè)單獨(dú)的聲明中重復(fù)聲明一個(gè)變量硼一,但在多變量聲明中這是允許的累澡,其中至少要有一個(gè)新的聲明變量。
重復(fù)變量需要在相同的代碼塊內(nèi)般贼,否則你將得到一個(gè)隱藏變量愧哟。
Fails:

package main

func main() {  
    one := 0
    one := 1 //error
}

Compile Error:

/tmp/sandbox706333626/main.go:5: no new variables on left side of :=

Works:

package main

func main() {  
    one := 0
    one, two := 1,2

    one,two = two,one
}

偶然的變量隱藏Accidental Variable Shadowing

短式變量聲明的語(yǔ)法如此的方便(尤其對(duì)于那些使用過(guò)動(dòng)態(tài)語(yǔ)言的開發(fā)者而言),很容易讓人把它當(dāng)成一個(gè)正常的分配操作哼蛆。如果你在一個(gè)新的代碼塊中犯了這個(gè)錯(cuò)誤蕊梧,將不會(huì)出現(xiàn)編譯錯(cuò)誤,但你的應(yīng)用將不會(huì)做你所期望的事情腮介。

package main

import "fmt"

func main() {  
    x := 1
    fmt.Println(x)     //prints 1
    {
        fmt.Println(x) //prints 1
        x := 2
        fmt.Println(x) //prints 2
    }
    fmt.Println(x)     //prints 1 (bad if you need 2)
}

即使對(duì)于經(jīng)驗(yàn)豐富的Go開發(fā)者而言肥矢,這也是一個(gè)非常常見(jiàn)的陷阱。這個(gè)坑很容易挖叠洗,但又很難發(fā)現(xiàn)甘改。

你可以使用 vet命令來(lái)發(fā)現(xiàn)一些這樣的問(wèn)題。 默認(rèn)情況下灭抑, vet不會(huì)執(zhí)行這樣的檢查楼誓,你需要設(shè)置-shadow參數(shù):
go tool vet -shadow your_file.go

不使用顯式類型名挥,無(wú)法使用“nil”來(lái)初始化變量

nil標(biāo)志符用于表示interface疟羹、函數(shù)、maps禀倔、slices和channels的“零值”榄融。如果你不指定變量的類型,編譯器將無(wú)法編譯你的代碼救湖,因?yàn)樗虏怀鼍唧w的類型愧杯。
Fails:

package main

func main() {  
    var x = nil //error

    _ = x
}

Compile Error:

/tmp/sandbox188239583/main.go:4: use of untyped nil

Works:

package main

func main() {  
    var x interface{} = nil

    _ = x
}

使用“nil” Slices and Maps

在一個(gè)nil的slice中添加元素是沒(méi)問(wèn)題的,但對(duì)一個(gè)map做同樣的事將會(huì)生成一個(gè)運(yùn)行時(shí)的panic鞋既。

Works:

package main

func main() {  
    var s []int
    s = append(s,1)
}

Fails:

package main

func main() {  
    var m map[string]int
    m["one"] = 1 //error

}

Map的容量

你可以在map創(chuàng)建時(shí)指定它的容量力九,但你無(wú)法在map上使用cap()函數(shù)耍铜。

Fails:

package main

func main() {  
    m := make(map[string]int,99)
    cap(m) //error
}

Compile Error:

/tmp/sandbox326543983/main.go:5: invalid argument m (type map[string]int) for cap

字符串不會(huì)為nil

這對(duì)于經(jīng)常使用nil分配字符串變量的開發(fā)者而言是個(gè)需要注意的地方。

Fails:

package main

func main() {  
    var x string = nil //error

    if x == nil { //error
        x = "default"
    }
}

Compile Errors:

/tmp/sandbox630560459/main.go:4: cannot use nil as type string in assignment /tmp/sandbox630560459/main.go:6: invalid operation: x == nil (mismatched types string and nil)

Works:

package main

func main() {  
    var x string //defaults to "" (zero value)

    if x == "" {
        x = "default"
    }
}

Array函數(shù)的參數(shù)

如果你是一個(gè)C或則C++開發(fā)者跌前,那么數(shù)組對(duì)你而言就是指針棕兼。當(dāng)你向函數(shù)中傳遞數(shù)組時(shí),函數(shù)會(huì)參照相同的內(nèi)存區(qū)域抵乓,這樣它們就可以修改原始的數(shù)據(jù)伴挚。Go中的數(shù)組是數(shù)值,因此當(dāng)你向函數(shù)中傳遞數(shù)組時(shí)灾炭,函數(shù)會(huì)得到原始數(shù)組數(shù)據(jù)的一份復(fù)制茎芋。如果你打算更新數(shù)組的數(shù)據(jù),這將會(huì)是個(gè)問(wèn)題蜈出。

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])
}

如果你需要更新原始數(shù)組的數(shù)據(jù)田弥,你可以使用數(shù)組指針類型。

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 [7 2 3]
}

另一個(gè)選擇是使用slice铡原。即使你的函數(shù)得到了slice變量的一份拷貝皱蹦,它依舊會(huì)參照原始的數(shù)據(jù)。

package main

import "fmt"

func main() {  
    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]
}

在Slice和Array使用“range”語(yǔ)句時(shí)的出現(xiàn)的不希望得到的值

如果你在其他的語(yǔ)言中使用“for-in”或者“foreach”語(yǔ)句時(shí)會(huì)發(fā)生這種情況眷蜈。Go中的“range”語(yǔ)法不太一樣。它會(huì)得到兩個(gè)值:第一個(gè)值是元素的索引沈自,而另一個(gè)值是元素的數(shù)據(jù)酌儒。
Bad:

package main

import "fmt"

func main() {  
    x := []string{"a","b","c"}

    for v := range x {
        fmt.Println(v) //prints 0, 1, 2
    }
}

Good:

package main

import "fmt"

func main() {  
    x := []string{"a","b","c"}

    for _, v := range x {
        fmt.Println(v) //prints a, b, c
    }
}

Slices和Arrays是一維的

看起來(lái)Go好像支持多維的Array和Slice,但不是這樣的枯途。盡管可以創(chuàng)建數(shù)組的數(shù)組或者切片的切片忌怎。對(duì)于依賴于動(dòng)態(tài)多維數(shù)組的數(shù)值計(jì)算應(yīng)用而言,Go在性能和復(fù)雜度上還相距甚遠(yuǎn)酪夷。

你可以使用純一維數(shù)組榴啸、“獨(dú)立”切片的切片,“共享數(shù)據(jù)”切片的切片來(lái)構(gòu)建動(dòng)態(tài)的多維數(shù)組晚岭。

如果你使用純一維的數(shù)組鸥印,你需要處理索引、邊界檢查坦报、當(dāng)數(shù)組需要變大時(shí)的內(nèi)存重新分配库说。

使用“獨(dú)立”slice來(lái)創(chuàng)建一個(gè)動(dòng)態(tài)的多維數(shù)組需要兩步。首先片择,你需要?jiǎng)?chuàng)建一個(gè)外部的slice潜的。然后,你需要分配每個(gè)內(nèi)部的slice字管。內(nèi)部的slice相互之間獨(dú)立啰挪。你可以增加減少它們信不,而不會(huì)影響其他內(nèi)部的slice。

package main

func main() {  
    x := 2
    y := 4

    table := make([][]int,x)
    for i:= range table {
        table[i] = make([]int,y)
    }
}

使用“共享數(shù)據(jù)”slice的slice來(lái)創(chuàng)建一個(gè)動(dòng)態(tài)的多維數(shù)組需要三步亡呵。首先抽活,你需要?jiǎng)?chuàng)建一個(gè)用于存放原始數(shù)據(jù)的數(shù)據(jù)“容器”。然后政己,你再創(chuàng)建外部的slice酌壕。最后,通過(guò)重新切片原始數(shù)據(jù)slice來(lái)初始化各個(gè)內(nèi)部的slice歇由。

package main

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>
}

關(guān)于多維array和slice已經(jīng)有了專門申請(qǐng)卵牍,但現(xiàn)在看起來(lái)這是個(gè)低優(yōu)先級(jí)的特性。

訪問(wèn)不存在的Map Keys

這對(duì)于那些希望得到“nil”標(biāo)示符的開發(fā)者而言是個(gè)技巧(和其他語(yǔ)言中做的一樣)沦泌。如果對(duì)應(yīng)的數(shù)據(jù)類型的“零值”是“nil”糊昙,那返回的值將會(huì)是“nil”,但對(duì)于其他的數(shù)據(jù)類型是不一樣的谢谦。檢測(cè)對(duì)應(yīng)的“零值”可以用于確定map中的記錄是否存在释牺,但這并不總是可信(比如,如果在二值的map中“零值”是false回挽,這時(shí)你要怎么做)没咙。檢測(cè)給定map中的記錄是否存在的最可信的方法是,通過(guò)map的訪問(wèn)操作千劈,檢查第二個(gè)返回的值祭刚。

Bad:

package main

import "fmt"

func main() {  
    x := map[string]string{"one":"a","two":"","three":"c"}

    if v := x["two"]; v == "" { //incorrect
        fmt.Println("no entry")
    }
}

Good:

package main

import "fmt"

func main() {  
    x := map[string]string{"one":"a","two":"","three":"c"}

    if _,ok := x["two"]; !ok {
        fmt.Println("no entry")
    }
}

Strings無(wú)法修改

嘗試使用索引操作來(lái)更新字符串變量中的單個(gè)字符將會(huì)失敗。string是只讀的byte slice(和一些額外的屬性)墙牌。如果你確實(shí)需要更新一個(gè)字符串涡驮,那么使用byte slice,并在需要時(shí)把它轉(zhuǎn)換為string類型喜滨。

Fails:

package main

import "fmt"

func main() {  
    x := "text"
    x[0] = 'T'

    fmt.Println(x)
}

Compile Error:

/tmp/sandbox305565531/main.go:7: cannot assign to x[0]

Works:

package main

import "fmt"

func main() {  
    x := "text"
    xbytes := []byte(x)
    xbytes[0] = 'T'

    fmt.Println(string(xbytes)) //prints Text
}

需要注意的是:這并不是在文字string中更新字符的正確方式捉捅,因?yàn)榻o定的字符可能會(huì)存儲(chǔ)在多個(gè)byte中。如果你確實(shí)需要更新一個(gè)文字string虽风,先把它轉(zhuǎn)換為一個(gè)rune slice棒口。即使使用rune slice,單個(gè)字符也可能會(huì)占據(jù)多個(gè)rune辜膝,比如當(dāng)你的字符有特定的重音符號(hào)時(shí)就是這種情況陌凳。這種復(fù)雜又模糊的“字符”本質(zhì)是Go字符串使用byte序列表示的原因。

String和Byte Slice之間的轉(zhuǎn)換

當(dāng)你把一個(gè)字符串轉(zhuǎn)換為一個(gè)byte slice(或者反之)時(shí)内舟,你就得到了一個(gè)原始數(shù)據(jù)的完整拷貝合敦。這和其他語(yǔ)言中cast操作不同,也和新的slice變量指向原始byte slice使用的相同數(shù)組時(shí)的重新slice操作不同验游。

Go在[]bytestringstring[]byte的轉(zhuǎn)換中確實(shí)使用了一些優(yōu)化來(lái)避免額外的分配(在todo列表中有更多的優(yōu)化)充岛。

第一個(gè)優(yōu)化避免了當(dāng)[]byte keys用于在map[string]集合中查詢時(shí)的額外分配:m[string(key)]保檐。

第二個(gè)優(yōu)化避免了字符串轉(zhuǎn)換為[]byte后在for range語(yǔ)句中的額外分配:for i,v := range []byte(str) {...}

String和索引操作

字符串上的索引操作返回一個(gè)byte值崔梗,而不是一個(gè)字符(和其他語(yǔ)言中的做法一樣)夜只。

package main

import "fmt"

func main() {  
    x := "text"
    fmt.Println(x[0]) //print 116
    fmt.Printf("%T",x[0]) //prints uint8
}

如果你需要訪問(wèn)特定的字符串“字符”(unicode編碼的points/runes),使用for range蒜魄。官方的“unicode/utf8”包和實(shí)驗(yàn)中的utf8string包(golang.org/x/exp/utf8string)也可以用扔亥。utf8string包中包含了一個(gè)很方便的At()方法。把字符串轉(zhuǎn)換為rune的切片也是一個(gè)選項(xiàng)谈为。

字符串不總是UTF8文本

字符串的值不需要是UTF8的文本旅挤。它們可以包含任意的字節(jié)。只有在string literal使用時(shí)伞鲫,字符串才會(huì)是UTF8粘茄。即使之后它們可以使用轉(zhuǎn)義序列來(lái)包含其他的數(shù)據(jù)。

為了知道字符串是否是UTF8秕脓,你可以使用“unicode/utf8”包中的ValidString()函數(shù)柒瓣。

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
}

字符串的長(zhǎng)度

讓我們假設(shè)你是Python開發(fā)者,你有下面這段代碼:

data = u'?'  
print(len(data)) #prints: 1

當(dāng)把它轉(zhuǎn)換為Go代碼時(shí)吠架,你可能會(huì)大吃一驚芙贫。

package main

import "fmt"

func main() {  
    data := "?"
    fmt.Println(len(data)) //prints: 3
}

內(nèi)建的len()函數(shù)返回byte的數(shù)量,而不是像Python中計(jì)算好的unicode字符串中字符的數(shù)量傍药。

要在Go中得到相同的結(jié)果磺平,可以使用“unicode/utf8”包中的RuneCountInString()函數(shù)。

package main

import (  
    "fmt"
    "unicode/utf8"
)

func main() {  
    data := "?"
    fmt.Println(utf8.RuneCountInString(data)) //prints: 1
}

理論上說(shuō)RuneCountInString()函數(shù)并不返回字符的數(shù)量怔檩,因?yàn)閱蝹€(gè)字符可能占用多個(gè)rune。

package main

import (  
    "fmt"
    "unicode/utf8"
)

func main() {  
    data := "e?"
    fmt.Println(len(data))                    //prints: 3
    fmt.Println(utf8.RuneCountInString(data)) //prints: 2
}

在多行的Slice蓄诽、Array和Map語(yǔ)句中遺漏逗號(hào)

Fails:

package main

func main() {  
    x := []int{
    1,
    2 //error
    }
    _ = x
}

Compile Errors:

/tmp/sandbox367520156/main.go:6: syntax error: need trailing comma before newline in composite literal /tmp/sandbox367520156/main.go:8: non-declaration statement outside function body /tmp/sandbox367520156/main.go:9: syntax error: unexpected }

Works:

package main

func main() {  
    x := []int{
    1,
    2,
    }
    x = x

    y := []int{3,4,} //no error
    y = y
}

當(dāng)你把聲明折疊到單行時(shí)薛训,如果你沒(méi)加末尾的逗號(hào),你將不會(huì)得到編譯錯(cuò)誤仑氛。

log.Fatal和log.Panic不僅僅是Log

Logging庫(kù)一般提供不同的log等級(jí)乙埃。與這些logging庫(kù)不同,Go中l(wèi)og包在你調(diào)用它的Fatal*()Panic*()函數(shù)時(shí)锯岖,可以做的不僅僅是log介袜。當(dāng)你的應(yīng)用調(diào)用這些函數(shù)時(shí),Go也將會(huì)終止應(yīng)用 :-)

package main

import "log"

func main() {  
    log.Fatalln("Fatal Level: log entry") //app exits here
    log.Println("Normal Level: log entry")
}

內(nèi)建的數(shù)據(jù)結(jié)構(gòu)操作不是同步的

即使Go本身有很多特性來(lái)支持并發(fā)出吹,并發(fā)安全的數(shù)據(jù)集合并不是其中之一 :-)確保數(shù)據(jù)集合以原子的方式更新是你的職責(zé)遇伞。Goroutines和channels是實(shí)現(xiàn)這些原子操作的推薦方式,但你也可以使用“sync”包捶牢,如果它對(duì)你的應(yīng)用有意義的話鸠珠。

String在“range”語(yǔ)句中的迭代值

索引值(“range”操作返回的第一個(gè)值)是返回的第二個(gè)值的當(dāng)前“字符”(unicode編碼的point/rune)的第一個(gè)byte的索引巍耗。它不是當(dāng)前“字符”的索引,這與其他語(yǔ)言不同渐排。注意真實(shí)的字符可能會(huì)由多個(gè)rune表示炬太。如果你需要處理字符,確保你使用了“norm”包(golang.org/x/text/unicode/norm)驯耻。

string變量的for range語(yǔ)句將會(huì)嘗試把數(shù)據(jù)翻譯為UTF8文本亲族。對(duì)于它無(wú)法理解的任何byte序列,它將返回0xfffd runes(即unicode替換字符)可缚,而不是真實(shí)的數(shù)據(jù)霎迫。如果你任意(非UTF8文本)的數(shù)據(jù)保存在string變量中,確保把它們轉(zhuǎn)換為byte slice城看,以得到所有保存的數(shù)據(jù)女气。

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)
}

對(duì)Map使用“for range”語(yǔ)句迭代

如果你希望以某個(gè)順序(比如,按key值排序)的方式得到元素测柠,就需要這個(gè)技巧炼鞠。每次的map迭代將會(huì)生成不同的結(jié)果。Go的runtime有心嘗試隨機(jī)化迭代順序轰胁,但并不總會(huì)成功谒主,這樣你可能得到一些相同的map迭代結(jié)果。所以如果連續(xù)看到5個(gè)相同的迭代結(jié)果赃阀,不要驚訝霎肯。

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的游樂(lè)場(chǎng)(https://play.golang.org/),你將總會(huì)得到同樣的結(jié)果榛斯,因?yàn)槌悄阈薷拇a观游,否則它不會(huì)重新編譯代碼。

"switch"聲明中的失效行為

在“switch”聲明語(yǔ)句中的“case”語(yǔ)句塊在默認(rèn)情況下會(huì)break驮俗。這和其他語(yǔ)言中的進(jìn)入下一個(gè)“next”代碼塊的默認(rèn)行為不同懂缕。

package main

import "fmt"

func main() {  
    isSpace := func(ch byte) bool {
        switch(ch) {
        case ' ': //error
        case '\t':
            return true
        }
        return false
    }

    fmt.Println(isSpace('\t')) //prints true (ok)
    fmt.Println(isSpace(' '))  //prints false (not ok)
}

你可以通過(guò)在每個(gè)“case”塊的結(jié)尾使用“fallthrough”,來(lái)強(qiáng)制“case”代碼塊進(jìn)入王凑。你也可以重寫switch語(yǔ)句搪柑,來(lái)使用“case”塊中的表達(dá)式列表。

package main

import "fmt"

func main() {  
    isSpace := func(ch byte) bool {
        switch(ch) {
        case ' ', '\t':
            return true
        }
        return false
    }

    fmt.Println(isSpace('\t')) //prints true (ok)
    fmt.Println(isSpace(' '))  //prints true (ok)
}

自增和自減

許多語(yǔ)言都有自增和自減操作索烹。不像其他語(yǔ)言工碾,Go不支持前置版本的操作。你也無(wú)法在表達(dá)式中使用這兩個(gè)操作符百姓。
Fails:

package main

import "fmt"

func main() {  
    data := []int{1,2,3}
    i := 0
    ++i //error
    fmt.Println(data[i++]) //error
}

Compile Errors:

/tmp/sandbox101231828/main.go:8: syntax error: unexpected ++ /tmp/sandbox101231828/main.go:9: syntax error: unexpected ++, expecting :

Works:

package main

import "fmt"

func main() {  
    data := []int{1,2,3}
    i := 0
    i++
    fmt.Println(data[i])
}

按位NOT操作

許多語(yǔ)言使用 ~作為一元的NOT操作符(即按位補(bǔ)足)渊额,但Go為了這個(gè)重用了XOR操作符(^)。

Fails:

package main

import "fmt"

func main() {  
    fmt.Println(~2) //error
}

Compile Error:

/tmp/sandbox965529189/main.go:6: the bitwise complement operator is ^

Works:

package main

import "fmt"

func main() {  
    var d uint8 = 2
    fmt.Printf("%08b\n",^d)
}

Go依舊使用^作為XOR的操作符,這可能會(huì)讓一些人迷惑端圈。

如果你愿意焦读,你可以使用一個(gè)二元的XOR操作(如, 0x02 XOR 0xff)來(lái)表示一個(gè)一元的NOT操作(如舱权,NOT 0x02)矗晃。這可以解釋為什么^被重用來(lái)表示一元的NOT操作。

Go也有特殊的‘AND NOT’按位操作(&^)宴倍,這也讓NOT操作更加的讓人迷惑张症。這看起來(lái)需要特殊的特性/hack來(lái)支持 A AND (NOT B),而無(wú)需括號(hào)鸵贬。

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))
}

操作優(yōu)先級(jí)的差異

除了”bit clear“操作(&^)俗他,Go也一個(gè)與許多其他語(yǔ)言共享的標(biāo)準(zhǔn)操作符的集合。盡管操作優(yōu)先級(jí)并不總是一樣阔逼。

 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
}

未導(dǎo)出的結(jié)構(gòu)體不會(huì)被編碼

以小寫字母開頭的結(jié)構(gòu)體將不會(huì)被(json兆衅、xml、gob等)編碼嗜浮,因此當(dāng)你編碼這些未導(dǎo)出的結(jié)構(gòu)體時(shí)羡亩,你將會(huì)得到零值。

Fails:

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:""}
}

有活動(dòng)的Goroutines下的應(yīng)用退出

應(yīng)用將不會(huì)等待所有的goroutines完成危融。這對(duì)于初學(xué)者而言是個(gè)很常見(jiàn)的錯(cuò)誤畏铆。每個(gè)人都是以某個(gè)程度開始,因此如果犯了初學(xué)者的錯(cuò)誤也沒(méi)神馬好丟臉的 :-)

package main

import (  
    "fmt"
    "time"
)

func main() {  
    workerCount := 2

    for i := 0; i < workerCount; i++ {
        go doit(i)
    }
    time.Sleep(1 * time.Second)
    fmt.Println("all done!")
}

func doit(workerId int) {  
    fmt.Printf("[%v] is running\n",workerId)
    time.Sleep(3 * time.Second)
    fmt.Printf("[%v] is done\n",workerId)
}

你將會(huì)看到:

[0] is running 
[1] is running 
all done!

一個(gè)最常見(jiàn)的解決方法是使用“WaitGroup”變量吉殃。它將會(huì)讓主goroutine等待所有的worker goroutine完成辞居。如果你的應(yīng)用有長(zhǎng)時(shí)運(yùn)行的消息處理循環(huán)的worker,你也將需要一個(gè)方法向這些goroutine發(fā)送信號(hào)蛋勺,讓它們退出瓦灶。你可以給各個(gè)worker發(fā)送一個(gè)“kill”消息。另一個(gè)選項(xiàng)是關(guān)閉一個(gè)所有worker都接收的channel抱完。這是一次向所有g(shù)oroutine發(fā)送信號(hào)的簡(jiǎn)單方式贼陶。

package main

import (  
    "fmt"
    "sync"
)

func main() {  
    var wg sync.WaitGroup
    done := make(chan struct{})
    workerCount := 2

    for i := 0; i < workerCount; i++ {
        wg.Add(1)
        go doit(i,done,wg)
    }

    close(done)
    wg.Wait()
    fmt.Println("all done!")
}

func doit(workerId int,done <-chan struct{},wg sync.WaitGroup) {  
    fmt.Printf("[%v] is running\n",workerId)
    defer wg.Done()
    <- done
    fmt.Printf("[%v] is done\n",workerId)
}

如果你運(yùn)行這個(gè)應(yīng)用,你將會(huì)看到:

[0] is running 
[0] is done 
[1] is running 
[1] is done

看起來(lái)所有的worker在主goroutine退出前都完成了乾蛤。棒每界!然而捅僵,你也將會(huì)看到這個(gè):

fatal error: all goroutines are asleep - deadlock!

這可不太好 :-) 發(fā)送了神馬家卖?為什么會(huì)出現(xiàn)死鎖?worker退出了庙楚,它們也執(zhí)行了wg.Done()。應(yīng)用應(yīng)該沒(méi)問(wèn)題啊。

死鎖發(fā)生是因?yàn)楦鱾€(gè)worker都得到了原始的“WaitGroup”變量的一個(gè)拷貝檐涝。當(dāng)worker執(zhí)行wg.Done()時(shí),并沒(méi)有在主goroutine上的“WaitGroup”變量上生效叁征。

package main

import (  
    "fmt"
    "sync"
)

func main() {  
    var wg sync.WaitGroup
    done := make(chan struct{})
    wq := make(chan interface{})
    workerCount := 2

    for i := 0; i < workerCount; i++ {
        wg.Add(1)
        go doit(i,wq,done,&wg)
    }

    for i := 0; i < workerCount; i++ {
        wq <- i
    }

    close(done)
    wg.Wait()
    fmt.Println("all done!")
}

func doit(workerId int, wq <-chan interface{},done <-chan struct{},wg *sync.WaitGroup) {  
    fmt.Printf("[%v] is running\n",workerId)
    defer wg.Done()
    for {
        select {
        case m := <- wq:
            fmt.Printf("[%v] m => %v\n",workerId,m)
        case <- done:
            fmt.Printf("[%v] is done\n",workerId)
            return
        }
    }
}

現(xiàn)在它會(huì)如預(yù)期般工作 :-)

向無(wú)緩存的Channel發(fā)送消息,只要目標(biāo)接收者準(zhǔn)備好就會(huì)立即返回

發(fā)送者將不會(huì)被阻塞逛薇,除非消息正在被接收者處理捺疼。根據(jù)你運(yùn)行代碼的機(jī)器的不同,接收者的goroutine可能會(huì)或者不會(huì)有足夠的時(shí)間永罚,在發(fā)送者繼續(xù)執(zhí)行前處理消息啤呼。

package main

import "fmt"

func main() {  
    ch := make(chan string)

    go func() {
        for m := range ch {
            fmt.Println("processed:",m)
        }
    }()

    ch <- "cmd.1"
    ch <- "cmd.2" //won't be processed
}

向已關(guān)閉的Channel發(fā)送會(huì)引起Panic

從一個(gè)關(guān)閉的channel接收是安全的。在接收狀態(tài)下的ok的返回值將被設(shè)置為false呢袱,這意味著沒(méi)有數(shù)據(jù)被接收官扣。如果你從一個(gè)有緩存的channel接收,你將會(huì)首先得到緩存的數(shù)據(jù)羞福,一旦它為空惕蹄,返回的ok值將變?yōu)?code>false。

向關(guān)閉的channel中發(fā)送數(shù)據(jù)會(huì)引起panic治专。這個(gè)行為有文檔說(shuō)明卖陵,但對(duì)于新的Go開發(fā)者的直覺(jué)不同,他們可能希望發(fā)送行為與接收行為很像看靠。

package main

import (  
    "fmt"
    "time"
)

func main() {  
    ch := make(chan int)
    for i := 0; i < 3; i++ {
        go func(idx int) {
            ch <- (idx + 1) * 2
        }(i)
    }
    
    //get the first result
    fmt.Println(<-ch)
    close(ch) //not ok (you still have other senders)
    //do other work
    time.Sleep(2 * time.Second)
}

根據(jù)不同的應(yīng)用赶促,修復(fù)方法也將不同⌒妫可能是很小的代碼修改鸥滨,也可能需要修改應(yīng)用的設(shè)計(jì)。無(wú)論是哪種方法谤祖,你都需要確保你的應(yīng)用不會(huì)向關(guān)閉的channel中發(fā)送數(shù)據(jù)婿滓。

上面那個(gè)有bug的例子可以通過(guò)使用一個(gè)特殊的廢棄的channel來(lái)向剩余的worker發(fā)送不再需要它們的結(jié)果的信號(hào)來(lái)修復(fù)。

package main

import (  
    "fmt"
    "time"
)

func main() {  
    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)
    }

    //get first result
    fmt.Println("result:",<-ch)
    close(done)
    //do other work
    time.Sleep(3 * time.Second)
}

使用"nil" Channels

在一個(gè)nil的channel上發(fā)送和接收操作會(huì)被永久阻塞粥喜。這個(gè)行為有詳細(xì)的文檔解釋凸主,但它對(duì)于新的Go開發(fā)者而言是個(gè)驚喜。

package main

import (  
    "fmt"
    "time"
)

func main() {  
    var ch chan int
    for i := 0; i < 3; i++ {
        go func(idx int) {
            ch <- (idx + 1) * 2
        }(i)
    }

    //get first result
    fmt.Println("result:",<-ch)
    //do other work
    time.Sleep(2 * time.Second)
}

如果運(yùn)行代碼你將會(huì)看到一個(gè)runtime錯(cuò)誤:

fatal error: all goroutines are asleep - deadlock!

這個(gè)行為可以在select聲明中用于動(dòng)態(tài)開啟和關(guān)閉case代碼塊的方法额湘。

package main

import "fmt"  
import "time"

func main() {  
    inch := make(chan int)
    outch := make(chan int)

    go func() {
        var in <- chan int = inch
        var out chan <- int
        var val int
        for {
           select {
            case out <- val:
                out = nil
                in = inch
            case val = <- in:
                out = outch
                in = nil
            }
        }
    }()

    go func() {
        for r := range outch {
            fmt.Println("result:",r)
        }
    }()

    time.Sleep(0)
    inch <- 1
    inch <- 2
    time.Sleep(3 * time.Second)
}

傳值方法的接收者無(wú)法修改原有的值

方法的接收者就像常規(guī)的函數(shù)參數(shù)卿吐。如果聲明為值,那么你的函數(shù)/方法得到的是接收者參數(shù)的拷貝锋华。這意味著對(duì)接收者所做的修改將不會(huì)影響原有的值嗡官,除非接收者是一個(gè)map或者slice變量,而你更新了集合中的元素毯焕,或者你更新的域的接收者是指針衍腥。

package main

import "fmt"

type data struct {  
    num int
    key *string
    items map[string]bool
}

func (this *data) pmethod() {  
    this.num = 7
}

func (this data) vmethod() {  
    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.num,*d.key,d.items) 
    //prints num=7 key=key.1 items=map[]

    d.vmethod()
    fmt.Printf("num=%v key=%v items=%v\n",d.num,*d.key,d.items)
    //prints num=7 key=v.key items=map[vmethod:true]
}

中級(jí)

關(guān)閉HTTP的響應(yīng)

當(dāng)你使用標(biāo)準(zhǔn)http庫(kù)發(fā)起請(qǐng)求時(shí),你得到一個(gè)http的響應(yīng)變量。如果你不讀取響應(yīng)主體婆咸,你依舊需要關(guān)閉它竹捉。注意對(duì)于空的響應(yīng)你也一定要這么做。對(duì)于新的Go開發(fā)者而言尚骄,這個(gè)很容易就會(huì)忘掉块差。

一些新的Go開發(fā)者確實(shí)嘗試關(guān)閉響應(yīng)主體,但他們?cè)阱e(cuò)誤的地方做倔丈。

package main

import (  
    "fmt"
    "net/http"
    "io/ioutil"
)

func main() {  
    resp, err := http.Get("https://api.ipify.org?format=json")
    defer resp.Body.Close()//not ok
    if err != nil {
        fmt.Println(err)
        return
    }

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println(string(body))
}

這段代碼對(duì)于成功的請(qǐng)求沒(méi)問(wèn)題憾儒,但如果http的請(qǐng)求失敗,resp變量可能會(huì)是nil乃沙,這將導(dǎo)致一個(gè)runtime panic起趾。

最常見(jiàn)的關(guān)閉響應(yīng)主體的方法是在http響應(yīng)的錯(cuò)誤檢查后調(diào)用defer

package main

import (  
    "fmt"
    "net/http"
    "io/ioutil"
)

func main() {  
    resp, err := http.Get("https://api.ipify.org?format=json")
    if err != nil {
        fmt.Println(err)
        return
    }

    defer resp.Body.Close()//ok, most of the time :-)
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println(string(body))
}

大多數(shù)情況下警儒,當(dāng)你的http響應(yīng)失敗時(shí)训裆,resp變量將為nil,而err變量將是non-nil蜀铲。然而边琉,當(dāng)你得到一個(gè)重定向的錯(cuò)誤時(shí),兩個(gè)變量都將是non-nil记劝。這意味著你最后依然會(huì)內(nèi)存泄露变姨。

通過(guò)在http響應(yīng)錯(cuò)誤處理中添加一個(gè)關(guān)閉non-nil響應(yīng)主體的的調(diào)用來(lái)修復(fù)這個(gè)問(wèn)題。另一個(gè)方法是使用一個(gè)defer調(diào)用來(lái)關(guān)閉所有失敗和成功的請(qǐng)求的響應(yīng)主體厌丑。

package main

import (  
    "fmt"
    "net/http"
    "io/ioutil"
)

func main() {  
    resp, err := http.Get("https://api.ipify.org?format=json")
    if resp != nil {
        defer resp.Body.Close()
    }

    if err != nil {
        fmt.Println(err)
        return
    }

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println(string(body))
}

resp.Body.Close()的原始實(shí)現(xiàn)也會(huì)讀取并丟棄剩余的響應(yīng)主體數(shù)據(jù)定欧。這確保了http的鏈接在keepalive http連接行為開啟的情況下,可以被另一個(gè)請(qǐng)求復(fù)用怒竿。最新的http客戶端的行為是不同的】仇現(xiàn)在讀取并丟棄剩余的響應(yīng)數(shù)據(jù)是你的職責(zé)。如果你不這么做耕驰,http的連接可能會(huì)關(guān)閉爷辱,而無(wú)法被重用。這個(gè)小技巧應(yīng)該會(huì)寫在Go 1.5的文檔中朦肘。

如果http連接的重用對(duì)你的應(yīng)用很重要饭弓,你可能需要在響應(yīng)處理邏輯的后面添加像下面的代碼:

_, err = io.Copy(ioutil.Discard, resp.Body)

如果你不立即讀取整個(gè)響應(yīng)將是必要的,這可能在你處理json API響應(yīng)時(shí)會(huì)發(fā)生:

json.NewDecoder(resp.Body).Decode(&data)

關(guān)閉HTTP的連接

一些HTTP服務(wù)器保持會(huì)保持一段時(shí)間的網(wǎng)絡(luò)連接(根據(jù)HTTP 1.1的說(shuō)明和服務(wù)器端的“keep-alive”配置)媒抠。默認(rèn)情況下弟断,標(biāo)準(zhǔn)http庫(kù)只在目標(biāo)HTTP服務(wù)器要求關(guān)閉時(shí)才會(huì)關(guān)閉網(wǎng)絡(luò)連接。這意味著你的應(yīng)用在某些條件下消耗完sockets/file的描述符领舰。

你可以通過(guò)設(shè)置請(qǐng)求變量中的Close域的值為true夫嗓,來(lái)讓http庫(kù)在請(qǐng)求完成時(shí)關(guān)閉連接。

另一個(gè)選項(xiàng)是添加一個(gè)Connection的請(qǐng)求頭冲秽,并設(shè)置為close舍咖。目標(biāo)HTTP服務(wù)器應(yīng)該也會(huì)響應(yīng)一個(gè)Connection: close的頭。當(dāng)http庫(kù)看到這個(gè)響應(yīng)頭時(shí)锉桑,它也將會(huì)關(guān)閉連接排霉。

package main

import (  
    "fmt"
    "net/http"
    "io/ioutil"
)

func main() {  
    req, err := http.NewRequest("GET","http://golang.org",nil)
    if err != nil {
        fmt.Println(err)
        return
    }

    req.Close = true
    //or do this:
    //req.Header.Add("Connection", "close")

    resp, err := http.DefaultClient.Do(req)
    if resp != nil {
        defer resp.Body.Close()
    }

    if err != nil {
        fmt.Println(err)
        return
    }

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println(len(string(body)))
}

你也可以取消http的全局連接復(fù)用。你將需要為此創(chuàng)建一個(gè)自定義的http傳輸配置民轴。

package main

import (  
    "fmt"
    "net/http"
    "io/ioutil"
)

func main() {  
    tr := &http.Transport{DisableKeepAlives: true}
    client := &http.Client{Transport: tr}

    resp, err := client.Get("http://golang.org")
    if resp != nil {
        defer resp.Body.Close()
    }

    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println(resp.StatusCode)

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println(len(string(body)))
}

如果你向同一個(gè)HTTP服務(wù)器發(fā)送大量的請(qǐng)求攻柠,那么把保持網(wǎng)絡(luò)連接的打開是沒(méi)問(wèn)題的。然而后裸,如果你的應(yīng)用在短時(shí)間內(nèi)向大量不同的HTTP服務(wù)器發(fā)送一兩個(gè)請(qǐng)求瑰钮,那么在引用收到響應(yīng)后立刻關(guān)閉網(wǎng)絡(luò)連接是一個(gè)好主意。增加打開文件的限制數(shù)可能也是個(gè)好主意微驶。當(dāng)然浪谴,正確的選擇源自于應(yīng)用。

比較Structs, Arrays, Slices, and Maps

如果結(jié)構(gòu)體中的各個(gè)元素都可以用你可以使用等號(hào)來(lái)比較的話因苹,那就可以使用相號(hào), ==苟耻,來(lái)比較結(jié)構(gòu)體變量。

package main

import "fmt"

type data struct {  
    num int
    fp float32
    complex complex64
    str string
    char rune
    yes bool
    events <-chan string
    handler interface{}
    ref *byte
    raw [10]byte
}

func main() {  
    v1 := data{}
    v2 := data{}
    fmt.Println("v1 == v2:",v1 == v2) //prints: v1 == v2: true
}

如果結(jié)構(gòu)體中的元素?zé)o法比較扶檐,那使用等號(hào)將導(dǎo)致編譯錯(cuò)誤凶杖。注意數(shù)組僅在它們的數(shù)據(jù)元素可比較的情況下才可以比較。

package main

import "fmt"

type data struct {  
    num int                //ok
    checks [10]func() bool //not comparable
    doit func() bool       //not comparable
    m map[string] string   //not comparable
    bytes []byte           //not comparable
}

func main() {  
    v1 := data{}
    v2 := data{}
    fmt.Println("v1 == v2:",v1 == v2)
}

Go確實(shí)提供了一些助手函數(shù)款筑,用于比較那些無(wú)法使用等號(hào)比較的變量智蝠。

最常用的方法是使用reflect包中的DeepEqual()函數(shù)。

package main

import (  
    "fmt"
    "reflect"
)

type data struct {  
    num int                //ok
    checks [10]func() bool //not comparable
    doit func() bool       //not comparable
    m map[string] string   //not comparable
    bytes []byte           //not comparable
}

func main() {  
    v1 := data{}
    v2 := data{}
    fmt.Println("v1 == v2:",reflect.DeepEqual(v1,v2)) //prints: v1 == v2: true

    m1 := map[string]string{"one": "a","two": "b"}
    m2 := map[string]string{"two": "b", "one": "a"}
    fmt.Println("m1 == m2:",reflect.DeepEqual(m1, m2)) //prints: m1 == m2: true

    s1 := []int{1, 2, 3}
    s2 := []int{1, 2, 3}
    fmt.Println("s1 == s2:",reflect.DeepEqual(s1, s2)) //prints: s1 == s2: true
}

除了很慢(這個(gè)可能會(huì)也可能不會(huì)影響你的應(yīng)用)奈梳,DeepEqual()也有其他自身的技巧寻咒。

package main

import (  
    "fmt"
    "reflect"
)

func main() {  
    var b1 []byte = nil
    b2 := []byte{}
    fmt.Println("b1 == b2:",reflect.DeepEqual(b1, b2)) //prints: b1 == b2: false
}

DeepEqual()不會(huì)認(rèn)為空的slice與“nil”的slice相等。這個(gè)行為與你使用bytes.Equal()函數(shù)的行為不同颈嚼。bytes.Equal()認(rèn)為“nil”和空的slice是相等的毛秘。

package main

import (  
    "fmt"
    "bytes"
)

func main() {  
    var b1 []byte = nil
    b2 := []byte{}
    fmt.Println("b1 == b2:",bytes.Equal(b1, b2)) //prints: b1 == b2: true
}

DeepEqual()在比較slice時(shí)并不總是完美的。

package main

import (  
    "fmt"
    "reflect"
    "encoding/json"
)

func main() {  
    var str string = "one"
    var in interface{} = "one"
    fmt.Println("str == in:",str == in,reflect.DeepEqual(str, in)) 
    //prints: str == in: true true

    v1 := []string{"one","two"}
    v2 := []interface{}{"one","two"}
    fmt.Println("v1 == v2:",reflect.DeepEqual(v1, v2)) 
    //prints: v1 == v2: false (not ok)

    data := map[string]interface{}{
        "code": 200,
        "value": []string{"one","two"},
    }
    encoded, _ := json.Marshal(data)
    var decoded map[string]interface{}
    json.Unmarshal(encoded, &decoded)
    fmt.Println("data == decoded:",reflect.DeepEqual(data, decoded)) 
    //prints: data == decoded: false (not ok)
}

如果你的byte slice(或者字符串)中包含文字?jǐn)?shù)據(jù)阻课,而當(dāng)你要不區(qū)分大小寫形式的值時(shí)(在使用==叫挟,bytes.Equal(),或者bytes.Compare())限煞,你可能會(huì)嘗試使用“bytes”和“string”包中的ToUpper()或者ToLower()函數(shù)抹恳。對(duì)于英語(yǔ)文本,這么做是沒(méi)問(wèn)題的署驻,但對(duì)于許多其他的語(yǔ)言來(lái)說(shuō)就不行了奋献。這時(shí)應(yīng)該使用strings.EqualFold()bytes.EqualFold()健霹。

如果你的byte slice中包含需要驗(yàn)證用戶數(shù)據(jù)的隱私信息(比如,加密哈希瓶蚂、tokens等)糖埋,不要使用reflect.DeepEqual()bytes.Equal()窃这,或者bytes.Compare()瞳别,因?yàn)檫@些函數(shù)將會(huì)讓你的應(yīng)用易于被定時(shí)攻擊。為了避免泄露時(shí)間信息杭攻,使用'crypto/subtle'包中的函數(shù)(即祟敛,subtle.ConstantTimeCompare())。

從Panic中恢復(fù)

recover()函數(shù)可以用于獲取/攔截panic兆解。僅當(dāng)在一個(gè)defer函數(shù)中被完成時(shí)馆铁,調(diào)用recover()將會(huì)完成這個(gè)小技巧。

Incorrect:

package main

import "fmt"

func main() {  
    recover() //doesn't do anything
    panic("not good")
    recover() //won't be executed :)
    fmt.Println("ok")
}

Works:

package main

import "fmt"

func main() {  
    defer func() {
        fmt.Println("recovered:",recover())
    }()

    panic("not good")
}

recover()的調(diào)用僅當(dāng)它在defer函數(shù)中被直接調(diào)用時(shí)才有效锅睛。

Fails:

package main

import "fmt"

func doRecover() {  
    fmt.Println("recovered =>",recover()) //prints: recovered => <nil>
}

func main() {  
    defer func() {
        doRecover() //panic is not recovered
    }()

    panic("not good")
}

在Slice, Array, and Map "range"語(yǔ)句中更新引用元素的值

在“range”語(yǔ)句中生成的數(shù)據(jù)的值是真實(shí)集合元素的拷貝叼架。它們不是原有元素的引用。
這意味著更新這些值將不會(huì)修改原來(lái)的數(shù)據(jù)衣撬。同時(shí)也意味著使用這些值的地址將不會(huì)得到原有數(shù)據(jù)的指針乖订。

package main

import "fmt"

func main() {  
    data := []int{1,2,3}
    for _,v := range data {
        v *= 10 //original item is not changed
    }

    fmt.Println("data:",data) //prints data: [1 2 3]
}

如果你需要更新原有集合中的數(shù)據(jù),使用索引操作符來(lái)獲得數(shù)據(jù)具练。

package main

import "fmt"

func main() {  
    data := []int{1,2,3}
    for i,_ := range data {
        data[i] *= 10
    }

    fmt.Println("data:",data) //prints data: [10 20 30]
}

如果你的集合保存的是指針乍构,那規(guī)則會(huì)稍有不同。
如果要更新原有記錄指向的數(shù)據(jù)扛点,你依然需要使用索引操作哥遮,但你可以使用for range語(yǔ)句中的第二個(gè)值來(lái)更新存儲(chǔ)在目標(biāo)位置的數(shù)據(jù)。

package main

import "fmt"

func main() {  
    data := []*struct{num int} { {1},{2},{3} }

    for _,v := range data {
        v.num *= 10
    }

    fmt.Println(data[0],data[1],data[2]) //prints &{10} &{20} &{30}
}

在Slice中"隱藏"數(shù)據(jù)

當(dāng)你重新劃分一個(gè)slice時(shí)陵究,新的slice將引用原有slice的數(shù)組眠饮。如果你忘了這個(gè)行為的話,在你的應(yīng)用分配大量臨時(shí)的slice用于創(chuàng)建新的slice來(lái)引用原有數(shù)據(jù)的一小部分時(shí)铜邮,會(huì)導(dǎo)致難以預(yù)期的內(nèi)存使用仪召。

package main

import "fmt"

func get() []byte {  
    raw := make([]byte,10000)
    fmt.Println(len(raw),cap(raw),&raw[0]) //prints: 10000 10000 <byte_addr_x>
    return raw[:3]
}

func main() {  
    data := get()
    fmt.Println(len(data),cap(data),&data[0]) //prints: 3 10000 <byte_addr_x>
}

為了避免這個(gè)陷阱,你需要從臨時(shí)的slice中拷貝數(shù)據(jù)(而不是重新劃分slice)松蒜。

package main

import "fmt"

func get() []byte {  
    raw := make([]byte,10000)
    fmt.Println(len(raw),cap(raw),&raw[0]) //prints: 10000 10000 <byte_addr_x>
    res := make([]byte,3)
    copy(res,raw[:3])
    return res
}

func main() {  
    data := get()
    fmt.Println(len(data),cap(data),&data[0]) //prints: 3 3 <byte_addr_y>
}

Slice的數(shù)據(jù)“毀壞”

比如說(shuō)你需要重新一個(gè)路徑(在slice中保存)扔茅。你通過(guò)修改第一個(gè)文件夾的名字,然后把名字合并來(lái)創(chuàng)建新的路勁秸苗,來(lái)重新劃分指向各個(gè)文件夾的路徑召娜。

package main

import (  
    "fmt"
    "bytes"
)

func main() {  
    path := []byte("AAAA/BBBBBBBBB")
    sepIndex := bytes.IndexByte(path,'/')
    dir1 := path[:sepIndex]
    dir2 := path[sepIndex+1:]
    fmt.Println("dir1 =>",string(dir1)) //prints: dir1 => AAAA
    fmt.Println("dir2 =>",string(dir2)) //prints: dir2 => BBBBBBBBB

    dir1 = append(dir1,"suffix"...)
    path = bytes.Join([][]byte{dir1,dir2},[]byte{'/'})

    fmt.Println("dir1 =>",string(dir1)) //prints: dir1 => AAAAsuffix
    fmt.Println("dir2 =>",string(dir2)) //prints: dir2 => uffixBBBB (not ok)

    fmt.Println("new path =>",string(path))
}

結(jié)果與你想的不一樣。與"AAAAsuffix/BBBBBBBBB"相反惊楼,你將會(huì)得到"AAAAsuffix/uffixBBBB"玖瘸。這個(gè)情況的發(fā)生是因?yàn)閮蓚€(gè)文件夾的slice都潛在的引用了同一個(gè)原始的路徑slice秸讹。這意味著原始路徑也被修改了。根據(jù)你的應(yīng)用雅倒,這也許會(huì)是個(gè)問(wèn)題璃诀。

通過(guò)分配新的slice并拷貝需要的數(shù)據(jù),你可以修復(fù)這個(gè)問(wèn)題屯断。另一個(gè)選擇是使用完整的slice表達(dá)式。

package main

import (  
    "fmt"
    "bytes"
)

func main() {  
    path := []byte("AAAA/BBBBBBBBB")
    sepIndex := bytes.IndexByte(path,'/')
    dir1 := path[:sepIndex:sepIndex] //full slice expression
    dir2 := path[sepIndex+1:]
    fmt.Println("dir1 =>",string(dir1)) //prints: dir1 => AAAA
    fmt.Println("dir2 =>",string(dir2)) //prints: dir2 => BBBBBBBBB

    dir1 = append(dir1,"suffix"...)
    path = bytes.Join([][]byte{dir1,dir2},[]byte{'/'})

    fmt.Println("dir1 =>",string(dir1)) //prints: dir1 => AAAAsuffix
    fmt.Println("dir2 =>",string(dir2)) //prints: dir2 => BBBBBBBBB (ok now)

    fmt.Println("new path =>",string(path))
}

完整的slice表達(dá)式中的額外參數(shù)可以控制新的slice的容量÷屡担現(xiàn)在在那個(gè)slice后添加元素將會(huì)觸發(fā)一個(gè)新的buffer分配殖演,而不是覆蓋第二個(gè)slice中的數(shù)據(jù)。

陳舊的(Stale)Slices

多個(gè)slice可以引用同一個(gè)數(shù)據(jù)年鸳。比如趴久,當(dāng)你從一個(gè)已有的slice創(chuàng)建一個(gè)新的slice時(shí),這就會(huì)發(fā)生搔确。如果你的應(yīng)用功能需要這種行為彼棍,那么你將需要關(guān)注下“走味的”slice。

在某些情況下膳算,在一個(gè)slice中添加新的數(shù)據(jù)座硕,在原有數(shù)組無(wú)法保持更多新的數(shù)據(jù)時(shí),將導(dǎo)致分配一個(gè)新的數(shù)組涕蜂。而現(xiàn)在其他的slice還指向老的數(shù)組(和老的數(shù)據(jù))华匾。

import "fmt"

func main() {  
    s1 := []int{1,2,3}
    fmt.Println(len(s1),cap(s1),s1) //prints 3 3 [1 2 3]

    s2 := s1[1:]
    fmt.Println(len(s2),cap(s2),s2) //prints 2 2 [2 3]

    for i := range s2 { s2[i] += 20 }

    //still referencing the same array
    fmt.Println(s1) //prints [1 22 23]
    fmt.Println(s2) //prints [22 23]

    s2 = append(s2,4)

    for i := range s2 { s2[i] += 10 }

    //s1 is now "stale"
    fmt.Println(s1) //prints [1 22 23]
    fmt.Println(s2) //prints [32 33 14]
}

類型聲明和方法

當(dāng)你通過(guò)把一個(gè)現(xiàn)有(非interface)的類型定義為一個(gè)新的類型時(shí),新的類型不會(huì)繼承現(xiàn)有類型的方法机隙。

Fails:

package main

import "sync"

type myMutex sync.Mutex

func main() {  
    var mtx myMutex
    mtx.Lock() //error
    mtx.Unlock() //error  
}

Compile Errors:

/tmp/sandbox106401185/main.go:9: mtx.Lock undefined (type myMutex has no field or method Lock) /tmp/sandbox106401185/main.go:10: mtx.Unlock undefined (type myMutex has no field or method Unlock)

如果你確實(shí)需要原有類型的方法蜘拉,你可以定義一個(gè)新的struct類型,用匿名方式把原有類型嵌入其中有鹿。

Works:

package main

import "sync"

type myLocker struct {  
    sync.Mutex
}

func main() {  
    var lock myLocker
    lock.Lock() //ok
    lock.Unlock() //ok
}

interface類型的聲明也會(huì)保留它們的方法集合旭旭。
Works:

package main

import "sync"

type myLocker sync.Locker

func main() {  
    var lock myLocker = new(sync.Mutex)
    lock.Lock() //ok
    lock.Unlock() //ok
}

從"for switch"和"for select"代碼塊中跳出

沒(méi)有標(biāo)簽的“break”聲明只能從內(nèi)部的switch/select代碼塊中跳出來(lái)。如果無(wú)法使用“return”聲明的話葱跋,那就為外部循環(huán)定義一個(gè)標(biāo)簽是另一個(gè)好的選擇持寄。

package main

import "fmt"

func main() {  
    loop:
        for {
            switch {
            case true:
                fmt.Println("breaking out...")
                break loop
            }
        }

    fmt.Println("out!")
}

"goto"聲明也可以完成這個(gè)功能。娱俺。际看。

"for"聲明中的迭代變量和閉包

這在Go中是個(gè)很常見(jiàn)的技巧。for語(yǔ)句中的迭代變量在每次迭代時(shí)被重新使用矢否。這就意味著你在for循環(huán)中創(chuàng)建的閉包(即函數(shù)字面量)將會(huì)引用同一個(gè)變量(而在那些goroutine開始執(zhí)行時(shí)就會(huì)得到那個(gè)變量的值)仲闽。

Incorrect:

package main

import (  
    "fmt"
    "time"
)

func main() {  
    data := []string{"one","two","three"}

    for _,v := range data {
        go func() {
            fmt.Println(v)
        }()
    }

    time.Sleep(3 * time.Second)
    //goroutines print: three, three, three
}

最簡(jiǎn)單的解決方法(不需要修改goroutine)是,在for循環(huán)代碼塊內(nèi)把當(dāng)前迭代的變量值保存到一個(gè)局部變量中僵朗。

Works:

package main

import (  
    "fmt"
    "time"
)

func main() {  
    data := []string{"one","two","three"}

    for _,v := range data {
        vcopy := v //
        go func() {
            fmt.Println(vcopy)
        }()
    }

    time.Sleep(3 * time.Second)
    //goroutines print: one, two, three
}

另一個(gè)解決方法是把當(dāng)前的迭代變量作為匿名goroutine的參數(shù)赖欣。

Works:

package main

import (  
    "fmt"
    "time"
)

func main() {  
    data := []string{"one","two","three"}

    for _,v := range data {
        go func(in string) {
            fmt.Println(in)
        }(v)
    }

    time.Sleep(3 * time.Second)
    //goroutines print: one, two, three
}

下面這個(gè)陷阱稍微復(fù)雜一些的版本屑彻。

Incorrect:

package main

import (  
    "fmt"
    "time"
)

type field struct {  
    name string
}

func (p *field) print() {  
    fmt.Println(p.name)
}

func main() {  
    data := []field{ {"one"},{"two"},{"three"} }

    for _,v := range data {
        go v.print()
    }

    time.Sleep(3 * time.Second)
    //goroutines print: three, three, three
}

Works:

package main

import (  
    "fmt"
    "time"
)

type field struct {  
    name string
}

func (p *field) print() {  
    fmt.Println(p.name)
}

func main() {  
    data := []field{ {"one"},{"two"},{"three"} }

    for _,v := range data {
        v := v
        go v.print()
    }

    time.Sleep(3 * time.Second)
    //goroutines print: one, two, three
}

在運(yùn)行這段代碼時(shí)你認(rèn)為會(huì)看到什么結(jié)果?(原因是什么顶吮?)

package main

import (  
    "fmt"
    "time"
)

type field struct {  
    name string
}

func (p *field) print() {  
    fmt.Println(p.name)
}

func main() {  
    data := []*field{ {"one"},{"two"},{"three"} }

    for _,v := range data {
        go v.print()
    }

    time.Sleep(3 * time.Second)
}

Defer函數(shù)調(diào)用參數(shù)的求值

defer的函數(shù)的參數(shù)會(huì)在defer聲明時(shí)求值(而不是在函數(shù)實(shí)際執(zhí)行時(shí))社牲。
Arguments for a deferred function call are evaluated when the defer statement is evaluated (not when the function is actually executing).

package main

import "fmt"

func main() {  
    var i int = 1

    defer fmt.Println("result =>",func() int { return i * 2 }())
    i++
    //prints: result => 2 (not ok if you expected 4)
}

被Defer的函數(shù)調(diào)用執(zhí)行

被defer的調(diào)用會(huì)在包含的函數(shù)的末尾執(zhí)行,而不是包含代碼塊的末尾悴了。對(duì)于Go新手而言搏恤,一個(gè)很常犯的錯(cuò)誤就是無(wú)法區(qū)分被defer的代碼執(zhí)行規(guī)則和變量作用規(guī)則。如果你有一個(gè)長(zhǎng)時(shí)運(yùn)行的函數(shù)湃交,而函數(shù)內(nèi)有一個(gè)for循環(huán)試圖在每次迭代時(shí)都defer資源清理調(diào)用熟空,那就會(huì)出現(xiàn)問(wèn)題。

package main

import (  
    "fmt"
    "os"
    "path/filepath"
)

func main() {  
    if len(os.Args) != 2 {
        os.Exit(-1)
    }

    start, err := os.Stat(os.Args[1])
    if err != nil || !start.IsDir(){
        os.Exit(-1)
    }

    var targets []string
    filepath.Walk(os.Args[1], func(fpath string, fi os.FileInfo, err error) error {
        if err != nil {
            return err
        }

        if !fi.Mode().IsRegular() {
            return nil
        }

        targets = append(targets,fpath)
        return nil
    })

    for _,target := range targets {
        f, err := os.Open(target)
        if err != nil {
            fmt.Println("bad target:",target,"error:",err) //prints error: too many open files
            break
        }
        defer f.Close() //will not be closed at the end of this code block
        //do something with the file...
    }
}

解決這個(gè)問(wèn)題的一個(gè)方法是把代碼塊寫成一個(gè)函數(shù)搞莺。

package main

import (  
    "fmt"
    "os"
    "path/filepath"
)

func main() {  
    if len(os.Args) != 2 {
        os.Exit(-1)
    }

    start, err := os.Stat(os.Args[1])
    if err != nil || !start.IsDir(){
        os.Exit(-1)
    }

    var targets []string
    filepath.Walk(os.Args[1], func(fpath string, fi os.FileInfo, err error) error {
        if err != nil {
            return err
        }

        if !fi.Mode().IsRegular() {
            return nil
        }

        targets = append(targets,fpath)
        return nil
    })

    for _,target := range targets {
        func() {
            f, err := os.Open(target)
            if err != nil {
                fmt.Println("bad target:",target,"error:",err)
                return
            }
            defer f.Close() //ok
            //do something with the file...
        }()
    }
}

另一個(gè)方法是去掉defer語(yǔ)句 :-)

失敗的類型斷言

失敗的類型斷言返回?cái)嘌月暶髦惺褂玫哪繕?biāo)類型的“零值”息罗。這在與隱藏變量混合時(shí),會(huì)發(fā)生未知情況才沧。

Incorrect:

package main

import "fmt"

func main() {  
    var data interface{} = "great"

    if data, ok := data.(int); ok {
        fmt.Println("[is an int] value =>",data)
    } else {
        fmt.Println("[not an int] value =>",data) 
        //prints: [not an int] value => 0 (not "great")
    }
}

Works:

package main

import "fmt"

func main() {  
    var data interface{} = "great"

    if res, ok := data.(int); ok {
        fmt.Println("[is an int] value =>",res)
    } else {
        fmt.Println("[not an int] value =>",data) 
        //prints: [not an int] value => great (as expected)
    }
}

阻塞的Goroutine和資源泄露

Rob Pike在2012年的Google I/O大會(huì)上所做的“Go Concurrency Patterns”的演講上迈喉,說(shuō)道過(guò)幾種基礎(chǔ)的并發(fā)模式。從一組目標(biāo)中獲取第一個(gè)結(jié)果就是其中之一温圆。

func First(query string, replicas ...Search) Result {  
    c := make(chan Result)
    searchReplica := func(i int) { c <- replicas[i](query) }
    for i := range replicas {
        go searchReplica(i)
    }
    return <-c
}

這個(gè)函數(shù)在每次搜索重復(fù)時(shí)都會(huì)起一個(gè)goroutine挨摸。每個(gè)goroutine把它的搜索結(jié)果發(fā)送到結(jié)果的channel中。結(jié)果channel的第一個(gè)值被返回岁歉。

那其他goroutine的結(jié)果會(huì)怎樣呢油坝?還有那些goroutine自身呢?

First()函數(shù)中的結(jié)果channel是沒(méi)緩存的刨裆。這意味著只有第一個(gè)goroutine返回澈圈。其他的goroutine會(huì)困在嘗試發(fā)送結(jié)果的過(guò)程中。這意味著帆啃,如果你有不止一個(gè)的重復(fù)時(shí)瞬女,每個(gè)調(diào)用將會(huì)泄露資源。

為了避免泄露努潘,你需要確保所有的goroutine退出诽偷。一個(gè)不錯(cuò)的方法是使用一個(gè)有足夠保存所有緩存結(jié)果的channel。

func First(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
}

另一個(gè)不錯(cuò)的解決方法是使用一個(gè)有default情況的select語(yǔ)句和一個(gè)保存一個(gè)緩存結(jié)果的channel疯坤。default情況保證了即使當(dāng)結(jié)果channel無(wú)法收到消息的情況下报慕,goroutine也不會(huì)堵塞。

func First(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
}

你也可以使用特殊的取消channel來(lái)終止workers压怠。

func First(query string, replicas ...Search) Result {  
    c := make(chan Result)
    done := make(chan struct{})
    defer close(done)
    searchReplica := func(i int) { 
        select {
        case c <- replicas[i](query):
        case <- done:
        }
    }
    for i := range replicas {
        go searchReplica(i)
    }

    return <-c
}

為何在演講中會(huì)包含這些bug眠冈?Rob Pike僅僅是不想把演示復(fù)雜化。這么作是合理的菌瘫,但對(duì)于Go新手而言蜗顽,可能會(huì)直接使用代碼布卡,而不去思考它可能有問(wèn)題。

高級(jí)

使用指針接收方法的值的實(shí)例

只要值是可取址的雇盖,那在這個(gè)值上調(diào)用指針接收方法是沒(méi)問(wèn)題的忿等。換句話說(shuō),在某些情況下崔挖,你不需要在有一個(gè)接收值的方法版本贸街。

然而并不是所有的變量是可取址的。Map的元素就不是狸相。通過(guò)interface引用的變量也不是薛匪。

package main

import "fmt"

type data struct {  
    name string
}

func (p *data) print() {  
    fmt.Println("name:",p.name)
}

type printer interface {  
    print()
}

func main() {  
    d1 := data{"one"}
    d1.print() //ok

    var in printer = data{"two"} //error
    in.print()

    m := map[string]data {"x":data{"three"}}
    m["x"].print() //error
}

Compile Errors:

/tmp/sandbox017696142/main.go:21: cannot use data literal (type data) as type printer in assignment: data does not implement printer (print method has pointer receiver)
/tmp/sandbox017696142/main.go:25: cannot call pointer method on m["x"]
/tmp/sandbox017696142/main.go:25: cannot take the address of m["x"]

更新Map的值

如果你有一個(gè)struct值的map,你無(wú)法更新單個(gè)的struct值卷哩。

Fails:

package main

type data struct {  
    name string
}

func main() {  
    m := map[string]data {"x":{"one"}}
    m["x"].name = "two" //error
}

Compile Error:

/tmp/sandbox380452744/main.go:9: cannot assign to m["x"].name

這個(gè)操作無(wú)效是因?yàn)閙ap元素是無(wú)法取址的蛋辈。

而讓Go新手更加困惑的是slice元素是可以取址的属拾。

package main

import "fmt"

type data struct {  
    name string
}

func main() {  
    s := []data one
    s[0].name = "two" //ok
    fmt.Println(s)    //prints: [{two}]
}

注意在不久之前将谊,使用編譯器之一(gccgo)是可以更新map的元素值的,但這一行為很快就被修復(fù)了 :-)它也被認(rèn)為是Go 1.3的潛在特性渐白。在那時(shí)還不是要急需支持的尊浓,但依舊在todo list中。

第一個(gè)有效的方法是使用一個(gè)臨時(shí)變量纯衍。

package main

import "fmt"

type data struct {  
    name string
}

func main() {  
    m := map[string]data {"x":{"one"}}
    r := m["x"]
    r.name = "two"
    m["x"] = r
    fmt.Printf("%v",m) //prints: map[x:{two}]
}

另一個(gè)有效的方法是使用指針的map栋齿。

package main

import "fmt"

type data struct {  
    name string
}

func main() {  
    m := map[string]*data {"x":{"one"}}
    m["x"].name = "two" //ok
    fmt.Println(m["x"]) //prints: &{two}
}

順便說(shuō)下,當(dāng)你運(yùn)行下面的代碼時(shí)會(huì)發(fā)生什么襟诸?

package main

type data struct {  
    name string
}

func main() {  
    m := map[string]*data {"x":{"one"}}
    m["z"].name = "what?" //???
}

"nil" Interfaces和"nil" Interfaces的值

這在Go中是第二最常見(jiàn)的技巧瓦堵,因?yàn)閕nterface雖然看起來(lái)像指針,但并不是指針歌亲。interface變量?jī)H在類型和值為“nil”時(shí)才為“nil”菇用。

interface的類型和值會(huì)根據(jù)用于創(chuàng)建對(duì)應(yīng)interface變量的類型和值的變化而變化。當(dāng)你檢查一個(gè)interface變量是否等于“nil”時(shí)陷揪,這就會(huì)導(dǎo)致未預(yù)期的行為惋鸥。

package main

import "fmt"

func main() {  
    var data *byte
    var in interface{}

    fmt.Println(data,data == nil) //prints: <nil> true
    fmt.Println(in,in == nil)     //prints: <nil> true

    in = data
    fmt.Println(in,in == nil)     //prints: <nil> false
    //'data' is 'nil', but 'in' is not 'nil'
}

當(dāng)你的函數(shù)返回interface時(shí),小心這個(gè)陷阱悍缠。

Incorrect:

package main

import "fmt"

func main() {  
    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) //prints: good result: <nil>
        //'res' is not 'nil', but its value is 'nil'
    }
}

Works:

package main

import "fmt"

func main() {  
    doit := func(arg int) interface{} {
        var result *struct{} = nil

        if(arg > 0) {
            result = &struct{}{}
        } else {
            ret

棧和堆變量

你并不總是知道變量是分配到棧還是堆上卦绣。在C++中,使用new創(chuàng)建的變量總是在堆上飞蚓。在Go中滤港,即使是使用new()或者make()函數(shù)來(lái)分配,變量的位置還是由編譯器決定趴拧。編譯器根據(jù)變量的大小和“泄露分析”的結(jié)果來(lái)決定其位置蜗搔。這也意味著在局部變量上返回引用是沒(méi)問(wèn)題的劲藐,而這在C或者C++這樣的語(yǔ)言中是不行的。

如果你想知道變量分配的位置樟凄,在“go build”或“go run”上傳入“-m“ gc標(biāo)志(即聘芜,go run -gcflags -m app.go)。

GOMAXPROCS, 并發(fā), 和并行

默認(rèn)情況下缝龄,Go僅使用一個(gè)執(zhí)行上下文/OS線程(在當(dāng)前的版本)汰现。這個(gè)數(shù)量可以通過(guò)設(shè)置GOMAXPROCS來(lái)提高。

一個(gè)常見(jiàn)的誤解是叔壤,GOMAXPROCS表示了CPU的數(shù)量瞎饲,Go將使用這個(gè)數(shù)量來(lái)運(yùn)行g(shù)oroutine。而runtime.GOMAXPROCS()函數(shù)的文檔讓人更加的迷茫炼绘。GOMAXPROCS變量描述(https://golang.org/pkg/runtime/)所討論OS線程的內(nèi)容比較好嗅战。

你可以設(shè)置GOMAXPROCS的數(shù)量大于CPU的數(shù)量。GOMAXPROCS的最大值是256俺亮。

package main

import (  
    "fmt"
    "runtime"
)

func main() {  
    fmt.Println(runtime.GOMAXPROCS(-1)) //prints: 1
    fmt.Println(runtime.NumCPU())       //prints: 1 (on play.golang.org)
    runtime.GOMAXPROCS(20)
    fmt.Println(runtime.GOMAXPROCS(-1)) //prints: 20
    runtime.GOMAXPROCS(300)
    fmt.Println(runtime.GOMAXPROCS(-1)) //prints: 256
}

讀寫操作的重排順序

Go可能會(huì)對(duì)某些操作進(jìn)行重新排序驮捍,但它能保證在一個(gè)goroutine內(nèi)的所有行為順序是不變的。然而脚曾,它并不保證多goroutine的執(zhí)行順序东且。

package main

import (  
    "runtime"
    "time"
)

var _ = runtime.GOMAXPROCS(3)

var a, b int

func u1() {  
    a = 1
    b = 2
}

func u2() {  
    a = 3
    b = 4
}

func p() {  
    println(a)
    println(b)
}

func main() {  
    go u1()
    go u2()
    go p()
    time.Sleep(1 * time.Second)
}

如果你多運(yùn)行幾次上面的代碼,你可能會(huì)發(fā)現(xiàn)a和b變量有多個(gè)不同的組合:

1 
2

3 
4

0 
2

0 
0

1 
4

ab最有趣的組合式是"02"本讥。這表明ba之前更新了珊泳。

如果你需要在多goroutine內(nèi)放置讀寫順序的變化,你將需要使用channel拷沸,或者使用"sync"包構(gòu)建合適的結(jié)構(gòu)體色查。

優(yōu)先調(diào)度

有可能會(huì)出現(xiàn)這種情況,一個(gè)無(wú)恥的goroutine阻止其他goroutine運(yùn)行撞芍。當(dāng)你有一個(gè)不讓調(diào)度器運(yùn)行的for循環(huán)時(shí)秧了,這就會(huì)發(fā)生。

package main

import "fmt"

func main() {  
    done := false

    go func(){
        done = true
    }()

    for !done {
    }
    fmt.Println("done!")
}

for循環(huán)并不需要是空的勤庐。只要它包含了不會(huì)觸發(fā)調(diào)度執(zhí)行的代碼示惊,就會(huì)發(fā)生這種問(wèn)題。

調(diào)度器會(huì)在GC愉镰、“go”聲明米罚、阻塞channel操作、阻塞系統(tǒng)調(diào)用和lock操作后運(yùn)行丈探。它也會(huì)在非內(nèi)聯(lián)函數(shù)調(diào)用后執(zhí)行录择。

package main

import "fmt"

func main() {  
    done := false

    go func(){
        done = true
    }()

    for !done {
        fmt.Println("not done!") //not inlined
    }
    fmt.Println("done!")
}

要想知道你在for循環(huán)中調(diào)用的函數(shù)是否是內(nèi)聯(lián)的,你可以在“go build”或“go run”時(shí)傳入“-m” gc標(biāo)志(如, go build -gcflags -m)隘竭。

另一個(gè)選擇是顯式的喚起調(diào)度器塘秦。你可以使用“runtime”包中的Goshed()函數(shù)。

package main

import (  
    "fmt"
    "runtime"
)

func main() {  
    done := false

    go func(){
        done = true
    }()

    for !done {
        runtime.Gosched()
    }
    fmt.Println("done!")
}

原文
譯者

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末动看,一起剝皮案震驚了整個(gè)濱河市尊剔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌菱皆,老刑警劉巖须误,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異仇轻,居然都是意外死亡京痢,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門篷店,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)祭椰,“玉大人,你說(shuō)我怎么就攤上這事疲陕》接伲” “怎么了?”我有些...
    開封第一講書人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵鸭轮,是天一觀的道長(zhǎng)臣淤。 經(jīng)常有香客問(wèn)我橄霉,道長(zhǎng)窃爷,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任姓蜂,我火速辦了婚禮按厘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘钱慢。我一直安慰自己逮京,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開白布束莫。 她就那樣靜靜地躺著懒棉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪览绿。 梳的紋絲不亂的頭發(fā)上策严,一...
    開封第一講書人閱讀 51,482評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音饿敲,去河邊找鬼妻导。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的倔韭。 我是一名探鬼主播术浪,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼寿酌!你這毒婦竟也來(lái)了胰苏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤醇疼,失蹤者是張志新(化名)和其女友劉穎碟联,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體僵腺,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鲤孵,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了辰如。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片普监。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖琉兜,靈堂內(nèi)的尸體忽然破棺而出凯正,到底是詐尸還是另有隱情,我是刑警寧澤豌蟋,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布廊散,位于F島的核電站,受9級(jí)特大地震影響梧疲,放射性物質(zhì)發(fā)生泄漏允睹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一幌氮、第九天 我趴在偏房一處隱蔽的房頂上張望缭受。 院中可真熱鬧,春花似錦该互、人聲如沸米者。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蔓搞。三九已至,卻和暖如春随橘,著一層夾襖步出監(jiān)牢的瞬間喂分,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工太防, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留妻顶,地道東北人酸员。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像讳嘱,于是被迫代替她去往敵國(guó)和親幔嗦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354