go語(yǔ)言常見(jiàn)坑

1.main包的唯一性

傳統(tǒng)語(yǔ)言中對(duì)主入口的要求是main函數(shù),如c++/java等,只需要保證這點(diǎn)即可,但是在go中還需要保證main包的唯一性。

如下,在一個(gè)main包定義如下函數(shù)

package main

import "fmt"

func func1(){
    fmt.Println("test func1")
}

然后在另一個(gè)main包的main函數(shù)中如下調(diào)用

package main

import "fmt"

func main(){
    //嘗試1-嘗試調(diào)用同目錄下另一個(gè)main包中的函數(shù)
    //func1()
}

報(bào)錯(cuò)如下:

common_point\test_main1.go:63:2: undefined: func1

可以看到,兩個(gè)main包實(shí)際上是相互不可見(jiàn)的,對(duì)于自己來(lái)說(shuō)都是唯一的。需要提下的是,實(shí)際項(xiàng)目中,可以同一目錄下包含多個(gè)main包,只要不相互調(diào)用,go build/run xxx指明對(duì)應(yīng)main包文件編譯運(yùn)行即可,這樣處理的目的在于使項(xiàng)目結(jié)構(gòu)都清晰,同時(shí)兼容一個(gè)項(xiàng)目存在多個(gè)入口的情況。

2.如何跳出for select 循環(huán)

通常在for循環(huán)中,使用break可以跳出循環(huán),但是注意在go語(yǔ)言中,for select配合時(shí),break并不能跳出循環(huán)。

如下代碼:

func testSelectFor(chExit chan bool){
    for  {
        select {
        case v, ok := <-chExit:
            if !ok {
                fmt.Println("close channel 1", v)
                break
            }

            fmt.Println("ch1 val =", v)
        }

    }

    fmt.Println("exit testSelectFor")
}

如下調(diào)用:

//嘗試2 select for 跳出循環(huán)
c := make(chan bool)
go testSelectFor(c)

c <- true
c <- false
close(c)

time.Sleep(time.Duration(2) * time.Second)

運(yùn)行結(jié)果如下,可以看到break無(wú)法跳出循環(huán):

...
close channel 1 false
close channel 1 false
close channel 1 false
close channel 1 false
...

為了解決這個(gè)問(wèn)題,需要設(shè)置標(biāo)簽,break 標(biāo)簽或goto 便簽即可跳出循環(huán),如下兩種方法均可。

func testSelectFor2(chExit chan bool){
    EXIT:
    for  {
        select {
        case v, ok := <-chExit:
            if !ok {
                fmt.Println("close channel 2", v)
                break EXIT//goto EXIT2
            }

            fmt.Println("ch2 val =", v)
        }
    }

    //EXIT2:
    fmt.Println("exit testSelectFor2")
}

同樣調(diào)用,輸出結(jié)果如下:

ch2 val = true
ch2 val = false
close channel 2 false
exit testSelectFor2

3.如何在切片中查找

go中使用sort.searchXXX方法在排序好的切片中查找指定的方法,但是其返回結(jié)果很奇怪,返回是對(duì)應(yīng)的查找元素不存在時(shí)待插入的位置下標(biāo)(元素插入在返回下標(biāo)前)。如下調(diào)用:

    //嘗試3 search查找返回值
    s := []string{"ab", "ac", "ac", "bb", "bb", "ee"}
    fmt.Println("s=", s)

    fmt.Println(sort.SearchStrings(s, "aa"))
    fmt.Println(sort.SearchStrings(s, "ac"))
    fmt.Println(sort.SearchStrings(s, "ad"))
    fmt.Println(sort.SearchStrings(s, "ff"))

返回結(jié)果如下:

s= [ab ac ac bb bb ee]
0
1
3
6

可以看到,單獨(dú)根據(jù)返回值沒(méi)法判斷對(duì)應(yīng)元素是否存在,封裝如下函數(shù):

func IsExist(s []string, t string) (int, bool) {
    iIndex := sort.SearchStrings(s, t)
    bExist := iIndex!=len(s) && s[iIndex]==t

    return iIndex, bExist
}

這里用返回的下標(biāo)取值再和原來(lái)值對(duì)比下,即可判斷對(duì)應(yīng)元素是否存在。

fmt.Println(IsExist(s, "aa"))
    fmt.Println(IsExist(s, "ac"))
    fmt.Println(IsExist(s, "ad"))
    fmt.Println(IsExist(s, "ff"))
    
    /*out
    0 false
    1 true
    3 false
    6 false*/

4.如何初始化帶嵌套結(jié)構(gòu)的結(jié)構(gòu)體

go的哲學(xué)是組合優(yōu)于繼承,使用struct嵌套即可完成組合,內(nèi)嵌的結(jié)構(gòu)體屬性就像外層結(jié)構(gòu)的屬性即可,可以直接調(diào)用;但是注意初始化外層結(jié)構(gòu)體時(shí)必須指定內(nèi)嵌結(jié)構(gòu)體名稱的結(jié)構(gòu)體初始化,如下看到s1方式報(bào)錯(cuò),s2方式正確。

type stPeople struct {
    Gender bool
    Name string
}

type stStudent struct {
    stPeople
    Class int
}

//嘗試4 嵌套結(jié)構(gòu)的初始化表達(dá)式
//var s1 = stStudent{false, "JimWen", 3}
var s2 = stStudent{stPeople{false, "JimWen"}, 3}
fmt.Println(s2.Gender, s2.Name, s2.Class)

5.切片和數(shù)組

go中沒(méi)有特別復(fù)雜的數(shù)據(jù)結(jié)構(gòu),核心就是數(shù)組(array)和map,切片(slice)是基于數(shù)組的。很多人容易混淆array和slice的關(guān)系,這里著重說(shuō)下兩者的聯(lián)系和區(qū)別以及應(yīng)用場(chǎng)合。

先看二者的初始化方法,如下:

    //slice和數(shù)組初始化方法
    var a0 [5]int
    a1 := [5]int{}
    a2 := [5]int{1,2,3}
    a3 := [...]int{1,2,3}
    a4 := [5]int{1,2,3,4,5}
    fmt.Println(a0, a1, a2, a3, a4)

    var s0 []int
    s1 := a4[0:3]
    s2 := []int{1,2,3}
    s3 := make([]int, 2, 3)
    fmt.Println(s0==nil, s1, s2, s3)

輸出如下

[0 0 0 0 0] [0 0 0 0 0] [1 2 3 0 0] [1 2 3] [1 2 3 4 5]
true [1 2 3] [1 2 3] [0 0]

可以看到,array可以如下初始化:

1.單獨(dú)聲明長(zhǎng)度,會(huì)自動(dòng)填充0值,如 var a0 [5]int

2.也可以初始化時(shí)賦值,末尾沒(méi)有填滿的填充0值,如a2 := [5]int{1,2,3}

3.如果不指定長(zhǎng)度還可以自動(dòng)計(jì)算,如a3 := [...]int{1,2,3}

而slice不關(guān)聯(lián)數(shù)組是即為nil(如var s0 []int),稱為nil切片。slice要想不為空,必須和數(shù)組關(guān)聯(lián),有如下幾種方法:

1.引用已有數(shù)組,如s1 := a4[0:3]

2.初始化表達(dá)式賦值,會(huì)默認(rèn)生成底層數(shù)組,然后引用它,如s2 := []int{1,2,3}

3.使用make生成底層數(shù)組,數(shù)組填充0值,然后引用它,如s3 := make([]int, 2, 3)

除了引用數(shù)組的情況,slice和array初始化很容易區(qū)別,array必須指定長(zhǎng)度(具體數(shù)字或...),而slice不指定長(zhǎng)度。

前面說(shuō)了slice必須關(guān)聯(lián)數(shù)組,實(shí)際上看下二者的內(nèi)存結(jié)構(gòu)就都明白了,如下:

array在內(nèi)存中是連續(xù)的塊,而slice底層也是數(shù)組,只不過(guò)加了一個(gè)頭,分別包含數(shù)組起始地址、slice長(zhǎng)度、slice容量,數(shù)組起始地址和slice長(zhǎng)度決定了slice引用的數(shù)組范圍,slice容量決定了append操作時(shí)是否開辟新的底層數(shù)組。

如下操作:

    //slice以數(shù)組為基準(zhǔn)
    v0 := [4]int{-10, 20, 30, 40}
    s := v0[1:3]
    fmt.Println(v0, s, &v0[1], &s[0])

    //s[3] = 10 panic

    s = append(s, 80)
    fmt.Println(v0, s, &v0[1], &s[0])

    s = append(s, 90)
    fmt.Println(v0, s, &v0[1], &s[0])

結(jié)果:

[-10 20 30 40] [20 30] 0xc04200a488 0xc04200a488
[-10 20 30 80] [20 30 80] 0xc04200a488 0xc04200a488
[-10 20 30 80] [20 30 80 90] 0xc04200a488 0xc042008330

一開始s指向v0的第一個(gè)元素,包含兩個(gè)元素,slice長(zhǎng)度為2,slice容量剩余數(shù)組元素長(zhǎng)度3。打印結(jié)果可以看到,此時(shí)s-0和v0-1是同一個(gè)元素,即slice是指向已有array的。

然后append一個(gè)80,此時(shí)可以看到底層數(shù)組也發(fā)生了改變,這是因?yàn)閟lice的容量為3,當(dāng)前數(shù)組還夠用,所以直接用了當(dāng)前數(shù)組,此時(shí)s-0和v0-1是同一個(gè)元素,即slice還是指向已有array的。

然后再append一個(gè)90,此時(shí)可以看到此時(shí)s-0和v0-1不再是同一個(gè)元素,即slice指向了新的底層數(shù)組,因?yàn)樵袛?shù)組已經(jīng)不夠用了,所以新生成數(shù)組。

一定要注意此處的邏輯,否則很容易不小心修改了底層數(shù)組或想修改底層數(shù)組而生成了新數(shù)組。

那么為什么要同時(shí)又?jǐn)?shù)組和切片呢,在我看來(lái),數(shù)組是為了提供一種底層C數(shù)組的能力,而切片相當(dāng)于一種容器迭代器,可以很方便的實(shí)現(xiàn)動(dòng)態(tài)語(yǔ)言的易操作特性,同時(shí)結(jié)合二者可以實(shí)現(xiàn)高性能和易用性兼得。

明白了這些,看下如下問(wèn)題,如下給函數(shù)changeValue1傳遞一個(gè)數(shù)組,修改數(shù)組元素值,然后打印,發(fā)現(xiàn)并沒(méi)有變化。實(shí)際上go中變量賦值都是拷貝,要想實(shí)現(xiàn)改變可以使用數(shù)組指針changeValue2或切片changeValue3,他們依然是拷貝,只不過(guò)拷貝的是地址,但是指向的依然是底層數(shù)組,所以能夠成功改變數(shù)組元素值,這樣就保證了go中邏輯的一致性。

//數(shù)組
func changeValue1(v [5]int)  {
    v[0] = 100
}

//數(shù)組指針
func changeValue2(v *[5]int)  {
    (*v)[0] = 100
}

//切片
func changeValue3(v []int)  {
    v[0] = 100
}

// 傳遞數(shù)組參數(shù)
v1 := [5]int{-10, 20, 30, 40, 50}
changeValue1(v1)

v2 := [5]int{-10, 20, 30, 40, 50}
changeValue2(&v2)

v3 := [5]int{-10, 20, 30, 40, 50}
changeValue3(v3[0:])

fmt.Println(v1, v2, v3)

//輸出
[-10 20 30 40 50] [100 20 30 40 50] [100 20 30 40 50]

同樣需要注意的是for range遍歷切片的時(shí)候,返回的是拷貝,要想改變對(duì)應(yīng)的元素值必須使用索引來(lái)改變?cè)瓉?lái)的值,如下:

    //for range 對(duì)數(shù)組/切片為拷貝
    v4 := [5]int{-10, 20, 30, 40, 50}
    for _,v := range v4{
        v = v*2 //不會(huì)改變?cè)瓉?lái)值
    }
    for k,v := range v4{
        fmt.Println(k,v)
    }

    for k,v := range v4{
        v4[k] = v*2 //改變?cè)瓉?lái)值
    }
    for k,v := range v4{
        fmt.Println(k,v)
    }

5.map結(jié)構(gòu)查找是否存在鍵值

在其他語(yǔ)言中,鍵值不存在直接應(yīng)用會(huì)報(bào)異常,但是在go語(yǔ)言中會(huì)返回一個(gè)0值,因此可以如下兩種方法判斷鍵值是否存在:

package main

import "fmt"

func main(){
    m := map[int]string{1:"aaa", 2:"bbb", 3:"ccc", 4:"ddd", 5:"eee"}
    fmt.Println(m)

    v0, exist0 := m[5]
    if exist0{
        fmt.Println("exist key 5", v0)
    } else{
        fmt.Println("not exist key 5")
    }

    v1 := m[5]
    if v1!=""{
        fmt.Println("exist key 5", v0)
    } else{
        fmt.Println("not exist key 5")
    }
}

演示代碼下載鏈接

文章來(lái)源:http://blog.csdn.net/wenzhou1219
感謝作者:wenzhou1219

添加小編微信:grey0805,加入知識(shí)學(xué)習(xí)小分隊(duì)~!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容