Go CheatSheet 是對於 Go 學習/實踐過程當中的語法與技巧進行盤點,其屬於 Awesome CheatSheet 系列,致力於提高學習速度與研發效能,便可以將其當作速查手冊,也能夠做爲輕量級的入門學習資料。 本文參考了許多優秀的文章與代碼示範,統一聲明在了 Go Links;若是但願深刻了解某方面的內容,能夠繼續閱讀 Go 開發:語法基礎與工程實踐,或者前往 coding-snippets/go 查看使用 Go 解決常見的數據結構與算法、設計模式、業務功能方面的代碼實現。css
能夠前往這裏下載 Go SDK 安裝包,或者使用 brew 等包管理器安裝。go 命令依賴於 $GOPATH 環境變量進行代碼組織,多項目狀況下也可使用 ln 進行目錄映射以方便進行項目管理。GOPATH 容許設置多個目錄,每一個目錄都會包含三個子目錄:src 用於存放源代碼,pkg 用於存放編譯後生成的文件,bin 用於存放編譯後生成的可執行文件。java
環境配置完畢後,可使用 go get 獲取依賴,go run 運行程序,go build 來編譯項目生成與包名(文件夾名)一致的可執行文件。Golang 1.8 以後支持 dep 依賴管理工具,對於空的項目使用 dep init 初始化依賴配置,其會生成 Gopkg.toml Gopkg.lock vendor/
這三個文件(夾)。python
咱們可使用 dep ensure -add github.com/pkg/errors
添加依賴,運行以後,其會在 toml 文件中添加以下鎖:mysql
[[constraint]] name = "github.com/pkg/errors" version = "0.8.0"
簡單的 Go 中 Hello World 代碼以下:linux
package main import "fmt" func main() { fmt.Println("hello world") }
也可使用 Beego 實現簡單的 HTTP 服務器:git
package main import "github.com/astaxie/beego" func main() { beego.Run() }
Go 並無相對路徑引入,而是以文件夾爲單位定義模塊,譬如咱們新建名爲 math 的文件夾,而後使用 package math
來聲明該文件中函數所屬的模塊。github
import ( mongo "mywebapp/libs/mongodb/db" // 對引入的模塊重命名 _ "mywebapp/libs/mysql/db" // 使用空白下劃線表示僅調用其初始化函數 )
外部引用該模塊是須要使用工做區間或者 vendor 相對目錄,其目錄索引狀況以下:golang
cannot find package "sub/math" in any of: ${PROJECTROOT}/vendor/sub/math (vendor tree) /usr/local/Cellar/go/1.10/libexec/src/sub/math (from $GOROOT) ${GOPATH}/src/sub/math (from $GOPATH)
Go 規定每一個源文件的首部須要進行包聲明,可執行文件默認放在 main 包中;而各個包中默認首字母大寫的函數做爲其餘包可見的導出函數,而小寫函數則默認外部不可見的私有函數。web
做爲強類型靜態語言,Go 容許咱們在變量以後標識數據類型,也爲咱們提供了自動類型推導的功能。算法
// 聲明三個變量,皆爲 bool 類型 var c, python, java bool // 聲明不一樣類型的變量,而且賦值 var i bool, j int = true, 2 // 複雜變量聲明 var ( ToBe bool = false MaxInt uint64 = 1<<64 - 1 z complex128 = cmplx.Sqrt(-5 + 12i) ) // 短聲明變量 c, python, java := true, false, "no!" // 聲明常量 const constant = "This is a constant"
在 Go 中,若是咱們須要比較兩個複雜對象的類似性,可使用 reflect.DeepEqual 方法:
m1 := map[string]int{ "a":1, "b":2, } m2 := map[string]int{ "a":1, "b":2, } fmt.Println(reflect.DeepEqual(m1, m2))
Go 提供了加強型的 if 語句進行條件判斷:
// 基礎形式 if x > 0 { return x } else { return -x } // 條件判斷以前添加自定義語句 if a := b + c; a < 42 { return a } else { return a - 42 } // 經常使用的類型判斷 var val interface{} val = "foo" if str, ok := val.(string); ok { fmt.Println(str) }
Go 也支持使用 Switch 語句:
// 基礎格式 switch operatingSystem { case "darwin": fmt.Println("Mac OS Hipster") // 默認 break,不須要顯式聲明 case "linux": fmt.Println("Linux Geek") default: // Windows, BSD, ... fmt.Println("Other") } // 相似於 if,能夠在條件以前添加自定義語句 switch os := runtime.GOOS; os { case "darwin": ... } // 使用 switch 語句進行類型判斷: switch v := anything.(type) { case string: fmt.Println(v) case int32, int64: ... default: fmt.Println("unknown") }
Switch 中也支持進行比較:
number := 42 switch { case number < 42: fmt.Println("Smaller") case number == 42: fmt.Println("Equal") case number > 42: fmt.Println("Greater") }
或者進行多條件匹配:
var char byte = '?' switch char { case ' ', '?', '&', '=', '#', '+', '%': fmt.Println("Should escape") }
Go 支持使用 for 語句進行循環,不存在 while 或者 until:
for i := 1; i < 10; i++ { } // while - loop for ; i < 10; { } // 單條件狀況下能夠忽略分號 for i < 10 { } // ~ while (true) for { }
咱們也可使用 range 函數,對於 Arrays 與 Slices 進行遍歷:
// loop over an array/a slice for i, e := range a { // i 表示下標,e 表示元素 } // 僅須要元素 for _, e := range a { // e is the element } // 或者僅須要下標 for i := range a { } // 定時執行 for range time.Tick(time.Second) { // do it once a sec }
// 簡單函數定義 func functionName() {} // 含參函數定義 func functionName(param1 string, param2 int) {} // 多個相同類型參數的函數定義 func functionName(param1, param2 int) {} // 函數表達式定義 add := func(a, b int) int { return a + b }
Go 支持函數的最後一個參數使用 ... 設置爲不定參數,便可以傳入一個或多個參數值:
func adder(args ...int) int { total := 0 for _, v := range args { // Iterates over the arguments whatever the number. total += v } return total } adder(1, 2, 3) // 6 adder(9, 9) // 18 nums := []int{10, 20, 30} adder(nums...) // 60
咱們也可使用 Function Stub 做爲函數參數傳入,以實現回調函數的功能:
func Filter(s []int, fn func(int) bool) []int { var p []int // == nil for _, v := range s { if fn(v) { p = append(p, v) } } return p }
雖然 Go 不是函數式語言,可是也能夠用其實現柯里函數(Currying Function):
func add(x, y int) int { return x+ y } func adder(x int) (func(int) int) { return func(y int) int { return add(x, y) } } func main() { add3 := adder(3) fmt.Println(add3(4)) // 7 }
Go 支持多個返回值:
// 返回單個值 func functionName() int { return 42 } // 返回多個值 func returnMulti() (int, string) { return 42, "foobar" } var x, str = returnMulti() // 命名返回多個值 func returnMulti2() (n int, s string) { n = 42 s = "foobar" // n and s will be returned return } var x, str = returnMulti2()
Go 一樣支持詞法做用域與變量保留,所以咱們可使用閉包來訪問函數定義處外層的變量:
func scope() func() int{ outer_var := 2 foo := func() int { return outer_var} return foo }
閉包中並不可以直接修改外層變量,而是會自動重定義新的變量值:
func outer() (func() int, int) { outer_var := 2 inner := func() int { outer_var += 99 return outer_var // => 101 (but outer_var is a newly redefined } return inner, outer_var // => 101, 2 (outer_var is still 2, not mutated by inner!) }
Go 中提供了 defer 關鍵字,容許將某個語句的執行推遲到函數返回語句以前:
func read(...) (...) { f, err := os.Open(file) ... defer f.Close() ... return .. // f will be closed
Go 語言中並不存在 try-catch 等異常處理的關鍵字,對於那些可能返回異常的函數,只須要在函數返回值中添加額外的 Error 類型的返回值:
type error interface { Error() string }
某個可能返回異常的函數調用方式以下:
import ( "fmt" "errors" ) func main() { result, err:= Divide(2,0) if err != nil { fmt.Println(err) }else { fmt.Println(result) } } func Divide(value1 int,value2 int)(int, error) { if(value2 == 0){ return 0, errors.New("value2 mustn't be zero") } return value1/value2 , nil }
Go 還爲咱們提供了 panic 函數,所謂 panic,便是未得到預期結果,經常使用於拋出異常結果。譬如當咱們得到了某個函數返回的異常,殊不知道如何處理或者不須要處理時,能夠直接經過 panic 函數中斷當前運行,打印出錯誤信息、Goroutine 追蹤信息,而且返回非零的狀態碼:
_, err := os.Create("/tmp/file") if err != nil { panic(err) }
Go 中的 type 關鍵字可以對某個類型進行重命名:
// IntSlice 並不等價於 []int,可是能夠利用類型轉換進行轉換 type IntSlice []int a := IntSlice{1, 2}
可使用 T(v) 或者 obj.(T) 進行類型轉換,obj.(T) 僅針對 interface{} 類型起做用:
t := obj.(T) // if obj is not T, error t, ok := obj.(T) // if obj is not T, ok = false // 類型轉換與判斷 str, ok := val.(string);
interface {} // ~ java Object bool // true/false string int8 int16 int32 int64 int // =int32 on 32-bit, =int64 if 64-bit OS uint8 uint16 uint32 uint64 uintptr uint byte // alias for uint8 rune // alias for int32, represents a Unicode code point float32 float64
// 多行字符串聲明 hellomsg := ` "Hello" in Chinese is 你好 ('Ni Hao') "Hello" in Hindi is नमस्ते ('Namaste') `
格式化字符串:
fmt.Println("Hello, 你好, नमस्ते, Привет, ᎣᏏᏲ") // basic print, plus newline p := struct { X, Y int }{ 17, 2 } fmt.Println( "My point:", p, "x coord=", p.X ) // print structs, ints, etc s := fmt.Sprintln( "My point:", p, "x coord=", p.X ) // print to string variable fmt.Printf("%d hex:%x bin:%b fp:%f sci:%e",17,17,17,17.0,17.0) // c-ish format s2 := fmt.Sprintf( "%d %f", 17, 17.0 ) // formatted print to string variable
Array 與 Slice 均可以用來表示序列數據,兩者也有着必定的關聯。
其中 Array 用於表示固定長度的,相同類型的序列對象,可使用以下形式建立:
[N]Type [N]Type{value1, value2, ..., valueN} // 由編譯器自動計算數目 [...]Type{value1, value2, ..., valueN}
其具體使用方式爲:
// 數組聲明 var a [10]int // 賦值 a[3] = 42 // 讀取 i := a[3] // 聲明與初始化 var a = [2]int{1, 2} a := [2]int{1, 2} a := [...]int{1, 2}
Go 內置了 len 與 cap 函數,用於獲取數組的尺寸與容量:
var arr = [3]int{1, 2, 3} arr := [...]int{1, 2, 3} len(arr) // 3 cap(arr) // 3
不一樣於 C/C++ 中的指針(Pointer)或者 Java 中的對象引用(Object Reference),Go 中的 Array 只是值(Value)。這也就意味着,當進行數組拷貝,或者函數調用中的參數傳值時,會複製全部的元素副本,而非僅僅傳遞指針或者引用。顯而易見,這種複製的代價會較爲昂貴。
Slice 爲咱們提供了更爲靈活且輕量級地序列類型操做,可使用以下方式建立 Slice:
// 使用內置函數建立 make([]Type, length, capacity) make([]Type, length) // 聲明爲不定長度數組 []Type{} []Type{value1, value2, ..., valueN} // 對現有數組進行切片轉換 array[:] array[:2] array[2:] array[2:3]
不一樣於 Array,Slice 能夠看作更爲靈活的引用類型(Reference Type),它並不真實地存放數組值,而是包含數組指針(ptr),len,cap 三個屬性的結構體。換言之,Slice 能夠看作對於數組中某個段的描述,包含了指向數組的指針,段長度,以及段的最大潛在長度,其結構以下圖所示:
// 建立 len 爲 5,cap 爲 5 的 Slice s := make([]byte, 5) // 對 Slice 進行二次切片,此時 len 爲 2,cap 爲 3 s = s[2:4] // 恢復 Slice 的長度 s = s[:cap(s)]
須要注意的是, 切片操做並不會真實地複製 Slice 中值,只是會建立新的指向原數組的指針,這就保證了切片操做和操做數組下標有着相同的高效率。不過若是咱們修改 Slice 中的值,那麼其會 真實修改底層數組中的值,也就會體現到原有的數組中:
d := []byte{'r', 'o', 'a', 'd'} e := d[2:] // e == []byte{'a', 'd'} e[1] = 'm' // e == []byte{'a', 'm'} // d == []byte{'r', 'o', 'a', 'm'}
Go 提供了內置的 append 函數,來動態爲 Slice 添加數據,該函數會返回新的切片對象,包含了原始的 Slice 中值以及新增的值。若是原有的 Slice 的容量不足以存放新增的序列,那麼會自動分配新的內存:
// len=0 cap=0 [] var s []int // len=1 cap=2 [0] s = append(s, 0) // len=2 cap=2 [0 1] s = append(s, 1) // len=5 cap=8 [0 1 2 3 4] s = append(s, 2, 3, 4) // 使用 ... 來自動展開數組 a := []string{"John", "Paul"} b := []string{"George", "Ringo", "Pete"} a = append(a, b...) // equivalent to "append(a, b[0], b[1], b[2])" // a == []string{"John", "Paul", "George", "Ringo", "Pete"}
咱們也可使用內置的 copy 函數,進行 Slice 的複製,該函數支持對於不一樣長度的 Slice 進行復制,其會自動使用最小的元素數目。同時,copy 函數還可以自動處理使用了相同的底層數組之間的 Slice 複製,以免額外的空間浪費。
func copy(dst, src []T) int // 申請較大的空間容量 t := make([]byte, len(s), (cap(s)+1)*2) copy(t, s) s = t
var m map[string]int m = make(map[string]int) m["key"] = 42 // 刪除某個鍵 delete(m, "key") // 測試該鍵對應的值是否存在 elem, has_value := m["key"] // map literal var m = map[string]Vertex{ "Bell Labs": {40.68433, -74.39967}, "Google": {37.42202, -122.08408}, }
Go 語言中並不存在類的概念,只有結構體,結構體能夠看作屬性的集合,同時能夠爲其定義方法。
// 聲明結構體 type Vertex struct { // 結構體的屬性,一樣遵循大寫導出,小寫私有的原則 X, Y int z bool } // 也能夠聲明隱式結構體 point := struct { X, Y int }{1, 2} // 建立結構體實例 var v = Vertex{1, 2} // 讀取或者設置屬性 v.X = 4; // 顯示聲明鍵 var v = Vertex{X: 1, Y: 2} // 聲明數組 var v = []Vertex{{1,2},{5,2},{5,5}}
方法的聲明也很是簡潔,只須要在 func 關鍵字與函數名之間聲明結構體指針便可,該結構體會在不一樣的方法間進行復制:
func (v Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } // Call method v.Abs()
對於那些須要修改當前結構體對象的方法,則須要傳入指針:
func (v *Vertex) add(n float64) { v.X += n v.Y += n }
var p *Person = new(Person) // pointer of type Person
// p 是 Vertex 類型 p := Vertex{1, 2} // q 是指向 Vertex 的指針 q := &p // r 一樣是指向 Vertex 對象的指針 r := &Vertex{1, 2} // 指向 Vertex 結構體對象的指針類型爲 *Vertex var s *Vertex = new(Vertex)
Go 容許咱們經過定義接口的方式來實現多態性:
// 接口聲明 type Awesomizer interface { Awesomize() string } // 結構體並不須要顯式實現接口 type Foo struct {} // 而是經過實現全部接口規定的方法的方式,來實現接口 func (foo Foo) Awesomize() string { return "Awesome!" }
type Shape interface { area() float64 } func getArea(shape Shape) float64 { return shape.area() } type Circle struct { x,y,radius float64 } type Rectangle struct { width, height float64 } func(circle Circle) area() float64 { return math.Pi * circle.radius * circle.radius } func(rect Rectangle) area() float64 { return rect.width * rect.height } func main() { circle := Circle{x:0,y:0,radius:5} rectangle := Rectangle {width:10, height:5} fmt.Printf("Circle area: %f\n",getArea(circle)) fmt.Printf("Rectangle area: %f\n",getArea(rectangle)) } //Circle area: 78.539816 //Rectangle area: 50.000000
慣用的思路是先定義接口,再定義實現,最後定義使用的方法:
package animals type Animal interface { Speaks() string } // implementation of Animal type Dog struct{} func (a Dog) Speaks() string { return "woof" } /** 在須要的地方直接引用 **/ package circus import "animals" func Perform(a animal.Animal) { return a.Speaks() }
Go 也爲咱們提供了另外一種接口的實現方案,咱們能夠不在具體的實現處定義接口,而是在須要用到該接口的地方,該模式爲:
func funcName(a INTERFACETYPE) CONCRETETYPE
定義接口:
package animals type Dog struct{} func (a Dog) Speaks() string { return "woof" } /** 在須要使用實現的地方定義接口 **/ package circus type Speaker interface { Speaks() string } func Perform(a Speaker) { return a.Speaks() }
Go 語言中並無子類繼承這樣的概念,而是經過嵌入(Embedding)的方式來實現類或者接口的組合。
// ReadWriter 的實現須要同時知足 Reader 與 Writer type ReadWriter interface { Reader Writer } // Server 暴露了全部 Logger 結構體的方法 type Server struct { Host string Port int *log.Logger } // 初始化方式並未受影響 server := &Server{"localhost", 80, log.New(...)} // 卻能夠直接調用內嵌結構體的方法,等價於 server.Logger.Log(...) server.Log(...) // 內嵌結構體的名詞便是類型名 var logger *log.Logger = server.Logger
Goroutines 是輕量級的線程,能夠參考併發編程導論一文中的進程、線程與協程的討論;Go 爲咱們提供了很是便捷的 Goroutines 語法:
// 普通函數 func doStuff(s string) { } func main() { // 使用命名函數建立 Goroutine go doStuff("foobar") // 使用匿名內部函數建立 Goroutine go func (x int) { // function body goes here }(42) }
信道(Channel)是帶有類型的管道,能夠用於在不一樣的 Goroutine 之間傳遞消息,其基礎操做以下:
// 建立類型爲 int 的信道 ch := make(chan int) // 向信道中發送值 ch <- 42 // 從信道中獲取值 v := <-ch // 讀取,而且判斷其是否關閉 v, ok := <-ch // 讀取信道,直至其關閉 for i := range ch { fmt.Println(i) }
譬如咱們能夠在主線程中等待來自 Goroutine 的消息,而且輸出:
// 建立信道 messages := make(chan string) // 執行 Goroutine go func() { messages <- "ping" }() // 阻塞,而且等待消息 msg := <-messages // 使用信道進行併發地計算,而且阻塞等待結果 c := make(chan int) go sum(s[:len(s)/2], c) go sum(s[len(s)/2:], c) x, y := <-c, <-c // 從 c 中接收
如上建立的是無緩衝型信道(Non-buffered Channels),其是阻塞型信道;當沒有值時讀取方會持續阻塞,而寫入方則是在無讀取時阻塞。咱們能夠建立緩衝型信道(Buffered Channel),其讀取方在信道被寫滿前都不會被阻塞:
ch := make(chan int, 100) // 發送方也能夠主動關閉信道 close(ch)
Channel 一樣能夠做爲函數參數,而且咱們能夠顯式聲明其是用於發送信息仍是接收信息,從而增長程序的類型安全度:
// ping 函數用於發送信息 func ping(pings chan<- string, msg string) { pings <- msg } // pong 函數用於從某個信道中接收信息,而後發送到另外一個信道中 func pong(pings <-chan string, pongs chan<- string) { msg := <-pings pongs <- msg } func main() { pings := make(chan string, 1) pongs := make(chan string, 1) ping(pings, "passed message") pong(pings, pongs) fmt.Println(<-pongs) }
同步,是併發編程中的常見需求,這裏咱們可使用 Channel 的阻塞特性來實現 Goroutine 之間的同步:
func worker(done chan bool) { time.Sleep(time.Second) done <- true } func main() { done := make(chan bool, 1) go worker(done) // 阻塞直到接收到消息 <-done }
Go 還爲咱們提供了 select 關鍵字,用於等待多個信道的執行結果:
// 建立兩個信道 c1 := make(chan string) c2 := make(chan string) // 每一個信道會以不一樣時延輸出不一樣值 go func() { time.Sleep(1 * time.Second) c1 <- "one" }() go func() { time.Sleep(2 * time.Second) c2 <- "two" }() // 使用 select 來同時等待兩個信道的執行結果 for i := 0; i < 2; i++ { select { case msg1 := <-c1: fmt.Println("received", msg1) case msg2 := <-c2: fmt.Println("received", msg2) } }
package main import ( "fmt" "net/http" ) // define a type for the response type Hello struct{} // let that type implement the ServeHTTP method (defined in interface http.Handler) func (h Hello) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello!") } func main() { var h Hello http.ListenAndServe("localhost:4000", h) } // Here's the method signature of http.ServeHTTP: // type Handler interface { // ServeHTTP(w http.ResponseWriter, r *http.Request) // }
利用 Beego 官方推薦的 bee 命令行工具,咱們能夠快速建立 Beego 項目,其目錄組織方式以下:
quickstart ├── conf │ └── app.conf ├── controllers │ └── default.go ├── main.go ├── models ├── routers │ └── router.go ├── static │ ├── css │ ├── img │ └── js ├── tests │ └── default_test.go └── views └── index.tpl
在 main.go 文件中,咱們能夠啓動 Beego 實例,而且調用路由的初始化配置文件:
package main import ( _ "quickstart/routers" "github.com/astaxie/beego" ) func main() { beego.Run() }
而在路由的初始化函數中,咱們會聲明各個路由與控制器之間的映射關係:
package routers import ( "quickstart/controllers" "github.com/astaxie/beego" ) func init() { beego.Router("/", &controllers.MainController{}) }
也能夠手動指定 Beego 項目中的靜態資源映射:
beego.SetStaticPath("/down1", "download1") beego.SetStaticPath("/down2", "download2")
在具體的控制器中,能夠設置返回數據,或者關聯的模板名:
package controllers import ( "github.com/astaxie/beego" ) type MainController struct { beego.Controller } func (this *MainController) Get() { this.Data["Website"] = "beego.me" this.Data["Email"] = "astaxie@gmail.com" this.TplNames = "index.tpl" // version 1.6 use this.TplName = "index.tpl" }
import ( "io/ioutil" ) ... datFile1, errFile1 := ioutil.ReadFile("file1") if errFile1 != nil { panic(errFile1) } ...
VSCode 能夠爲函數自動生成基礎測試用例,而且提供了方便的用例執行與調試的功能。
/** 交換函數 */ func swap(x *int, y *int) { x, y = y, x } /** 自動生成的測試函數 */ func Test_swap(t *testing.T) { type args struct { x *int y *int } tests := []struct { name string args args }{ // TODO: Add test cases. } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { swap(tt.args.x, tt.args.y) }) } }