Go語言學習筆記

LaneHylton 8年前發布 | 21K 次閱讀 Go語言 Google Go/Golang開發

來自: http://xidui.github.io/2016/03/06/Go語言學習筆記/

這兩年go語言比較火,尤其是在服務器并發方面表現很好。一直想學一下,但是拖到現在。最近正好有這個機會,把go語言算是入了個門吧。

概括

學了兩周,總體感覺如下:

  1. 和C++在一些語法有點像,但少了很多東西,比如說分號,for循環以及if語句的括號(在go語言中是可有可無的)
  2. 沒有了引用類型,都使用指針替代,指針獲取變量也是采用點分的形式
  3. 強類型語言,類型有點強到吐血,比C++還強
  4. 不支持函數重載、函數默認參數
  5. 編譯限制太多,有未被使用的變量或者導入了未使用的庫,都會編譯失敗
  6. 簡化了異常處理,只有 panic 和 defer
  7. 縮進,默認的8格縮進真是挺難看的
  8. go語言的資源在天朝是被墻著的

一些坑

這里記錄的一些坑,都是我在使用的過程中遇到過的已經解決的或者是沒有完美解決的。

1.array類型與slice類型

使用一門語言的時候數據結構肯定是一個必修課題,其中的數組又是所有語言中最基礎的數據結構。

對于以下這兩個類型 []int , [3]int 起初我以為是一樣的,于是寫了以下這樣的代碼:

// package, imports 略
// error msg: cannot use [3]int literal (type [3]int) as type []int in return argument
func return_array_but_slice_needed() []int {
    return [3]int{1, 2, 3}
}

</div>

結果可想而知,編譯錯誤,原來在go中數組有兩種(準確的說是無窮多種,因為 [2]int 和 [3]int 也是不同的類型),分別是array和slice。可以從C++的角度去理解:

  1. Go中的 var array [3]int 相當于是C++中的 int array[3] ,數組的長度是定長的,不能擴展,但能修改數組中的值。
  2. Go中的 var slice []int 相當于是C++中的 std::vector<int> slice ,數組長度可伸縮,可修改,可索引。

值得一提的是slice的內存分配和C++的vector也很相似,它有長度和容量兩個概念,容量總是大于長度的。對于slice作append操作時,如果還有剩余容量,則直接附加,否則重新開辟一塊長度是2倍的內存(如果slice本身數據特別多,可能不到2倍),把原來數據復制進去,再作附加操作。

其實slice是一個包含了array指針的數據結構,還有長度,容量這兩個屬性。slice賦值給另外一個slice的時候,不同的僅僅是兩個slice對象在內存中的位置,而它們指向的array是相同的,仔細體會一下下面這個例子就可以理解,注釋中寫得很明確。

func main(){
    a := make([]int, 3, 6)
    var b = a                 // 賦值,但a,b底層所指的數組是同一份,在堆中

    b[0] = 1                  // 修改b中的值同時也會修改a中的值
    fmt.Printf("%p\n", &a)    // 0x820246000
    fmt.Printf("%p\n", &b)    // 0x820246020
    fmt.Println(a)            // [1 0 0]
    fmt.Println(b)            // [1 0 0]

    b = append(b, 4)          // 把4填到剩下三個空格中的第一格,并把b的長度設為4
    b[1] = 2
    fmt.Printf("%p\n", &a)    // 0x820246000
    fmt.Printf("%p\n", &b)    // 0x820246020
    fmt.Println(a)            // [1 2 0] 其實和b是同一份數據,因為a的長度還是3,所以只輸出了3個
    fmt.Println(b)            // [1 2 0 4]

    a = append(a, 5)          // a的長度變成4, 值填到三個空格中的第一格(會覆蓋掉b中的第四個數字)
    a = append(a, 6)          // a的長度變成5, 值填到剩下兩個空格的第一格
    a = append(a, 7, 8)       // 超過了a的容量,重新分配a,至此,a和b已經不會再互相影響
    fmt.Printf("%p\n", &a)    // 0x820246000 a對象中的array指針地址已變,指向了更長的數組,但a變量本身并不會更改位置
    fmt.Printf("%p\n", &b)    // 0x820246020
    fmt.Println(a)            // [1 2 0 5 6 7 8]
    fmt.Println(b)            // [1 2 0 5]

    var c = b[2: 6]           // b的第三個數字到第六個數字作為slice分配給c,b和c共享一段內存
    fmt.Println(c)            // [0 5 6 0] 雖然b沒有顯示這個6,因為b的長度沒到那里,但這個6確實在那段內存,由c打印出來
}

</div>

2.安裝外部依賴包

在編譯go的時候總是會被它嚴格的編譯限制弄得很頭疼,比如我在調試的時候需要 fmt 這個庫來打印一些東西,有時會注釋某個打印,此時又不得不注釋 import "fmt" ,等到再打印的時候,又要把那個注釋釋放出來,很麻煩。stackoverflow上的 這個鏈接 跟我是同樣的煩惱。答案中推薦了 goimports 這樣一個工具,編譯的時候會自動去掉不需要的依賴,使用下來還覺得挺不錯的,但安裝的過程確實感覺稍微有點坑。

官網說安裝需要執行這樣的命令:

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

</div>

但是 http://golang.org在中國是被墻了的,當然可以從中國的某些鏡像下載,但是還需要手動改一些文件夾的名字,很麻煩。因為`go get`命令,支持從官網的庫中下載,也支持從github下載,所以我決定從github下載。

但不管我采用以下的哪種,都無法下載:

go get https://github.com/bradfitz/goimports.git
go get git@github.com:bradfitz/goimports.git

</div>

一通亂試,終于成功了:

go get github.com/bradfitz/goimports

</div>

在看了 GOPATH 目錄的文件結構后我大概終于終于明白了這種個形式的意義,因為命令中的路徑其實也表達了文件夾中的路徑,不管是官網的命令還是最后我采用的命令,它會下載到對應的文件夾:

# 執行官網的命令,會下載到以下路徑
$GOPATH/golang.org/x/tools/cmd/goimports
# github下載會下載到以下路徑
$GOPATH/github.com/bradfitz/goimports

</div>

3.如何實現泛型

Go語言是強類型語言,簡直強到不能再強,不能像javascript或者python那樣隨心所欲地傳值,又不能像C++中那樣做函數重載。所以如何實現一個泛型的接口在go語言中是一個較為麻煩的事情。

好在Go語言并沒有趕盡殺絕,他還有 interface{} 這樣一個類型的參數,可以接受所有類型的其它參數。只不過有這樣參數的接口,如果需要對類型作判斷并且執行相應的操作的話還需要 reflect 庫的幫助。這個庫很強大,接口也有很多,有時間有精力的話推薦讀一下它的源碼。

interface{}的泛型能力還是很強的,他可以接受所有類型的參數,剛開始我還小瞧了它,寫出了這樣的代碼:

// cannot use a (type []int) as type []interface {} in argument to array_of_interface
func array_of_interface(ai []interface{}){
    fmt.Println(ai)
}

</div>

結果編譯出錯。其實參數不需要定義成slice,直接 i interface{} 就夠了。如果在接口中需要判斷類型,我推薦一個庫的源碼 encoding/json ,這個庫就是go語言中對json數據的序列化反序列化操作,可想而知,它需要實現大量的泛型操作(也是采用了 reflect 庫),貼一點關鍵部分的代碼:

// newTypeEncoder constructs an encoderFunc for a type.
// The returned encoder only checks CanAddr when allowAddr is true.
func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc {
    // 一部分代碼略去,有興趣的讀者可以直接去閱讀源碼
    switch t.Kind() {
    case reflect.Bool:
        return boolEncoder
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
        return intEncoder
    case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
        return uintEncoder
    case reflect.Float32:
        return float32Encoder
    case reflect.Float64:
        return float64Encoder
    case reflect.String:
        return stringEncoder
    case reflect.Interface:
        return interfaceEncoder
    case reflect.Struct:
        return newStructEncoder(t)
    case reflect.Map:
        return newMapEncoder(t)
    case reflect.Slice:
        return newSliceEncoder(t)
    case reflect.Array:
        return newArrayEncoder(t)
    case reflect.Ptr:
        return newPtrEncoder(t)
    default:
        return unsupportedTypeEncoder
    }
}

</div>

4.其它

待補充:

nil之坑

pointer receiver VS value receiver

有用的鏈接

當然,官方文檔是非常棒的,基本上所有的問題在 官網文檔 或者 github項目的wiki 中都有多提到,如果提前能夠把這里的一些注意事項以及best practise看完,會非常有幫助的。只是內容比較多,另外自己動手操作以后再看文檔的效果會比在還沒有足夠了解的情況下看文檔好很多。

下面這些鏈接是我在學習過程中個人覺得挺有幫助的一些問答或博客,也一起分享出來吧。

</div>

 本文由用戶 LaneHylton 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!