譯文地址:blog
原文:golang-cheat-sheet
簡要概括 Go 文法及特性。
目錄
- 基礎文法
運算子
- 聲明
函數
- 內建類型
- 類型轉換
- package
流程式控制制結構
- 條件判斷(if)
- 迴圈(for)
- 多條件分支(switch)
array, slice, range
- array
- slice
- array 和 slice 的操作函數
- map
- 結構體
- 指標
- 介面
- 結構體和介面的組合嵌入
- Errors
並發
- goroutine
- channel
- channel 開發原則
- 輸出
程式碼片段
前言
參考
文中大部分代碼都摘抄自 A Tour of Go,對新手來說是很好的參考資料。
Go 特性
- 命令式編程
- 靜態類型
- 類 C 文法(括弧使用頻率更少 & 無需分號),類 Oberon-2 的文法結構
- 代碼能編譯為本地可執行檔(無需 JVM 類的虛擬機器)
struct
和 method
取代類的概念
- 介面
- 類型組合 取代顯式繼承
- 有頭等函數
- 有回呼函數
- 函數可有多個傳回值
- 保留指標,但不能直接參与算術運算
- 內建並發原語:
goroutine
和 channel
基礎文法
Hello World
檔案 hello.go
:
package mainimport "fmt"func main() { fmt.Println("Hello Go")}
運行:$ go run hello.go
運算子
算術運算子
運算子 |
描述 |
+ |
加 |
- |
減 |
* |
乘 |
/ |
除 |
% |
取餘 |
& |
按位與 |
¦ |
按位或 |
^ |
按位異或 |
&^ |
按位清除(AND NOT) |
<< |
左移 |
>> |
右移 |
&^
即是 AND NOT(x, y) = AND(x, NOT(Y))
,如:
package mainimport "fmt"func main() { x := 0xDC // 11011100 y := 0xF0 // 11110000 z := x &^ y // 00001100 // y 中為 1 的位全部被清除為 0 fmt.Printf("%08b", z)}
比較子
運算子 |
描述 |
== |
相等 |
!= |
不等 |
< |
小於 |
<= |
小於等於 |
> |
大於 |
>= |
大於等於 |
邏輯運算子
運算子 |
描述 |
&& |
邏輯與 |
¦¦ |
邏輯或 |
! |
取反 |
其他
運算子 |
描述 |
& |
定址(產生指標) |
* |
擷取指標指向的資料 |
<- |
向 channel 中發送 / 接收資料 |
聲明
與 C 不同,類型放在標識符後面:
var foo int // 無初值的聲明var foo int = 42 // 帶初值的聲明var foo, bar int = 42, 1302 // 一次性聲明並初始化多個變數var foo = 42 // 類型推斷,由使用的上下文決定foo := 42 // 簡短聲明,只能用在函數內部const constant = "This is a constant"
函數
// 最簡單的函數func functionName() {}// 帶參數的函數(注意類型也是放在標識符之後的)func functionName(param1 string, param2 int) {}// 類型相同的多個參數func functionName(param1, param2 int) {}// 聲明傳回值的類型func functionName() int { return 42}// 一次返回多個值func returnMulti() (int, string) { return 42, "foobar"}var x, str = returnMulti()// 只使用 return 返回多個命名傳回值func returnMulti2() (n int, s string) { n = 42 s = "foobar" // n 和 s 會被返回 return}var x, str = returnMulti2()
函數作為值和回調使用
func main() { // 將函數作為值,賦給變數 add := func(a, b int) int { return a + b } // 使用變數直接調用函數 fmt.Println(add(3, 4))}// 回呼函數範圍:在定義回呼函數時能訪問外部函數的值func scope() func() int{ outer_var := 2 foo := func() int { return outer_var} return foo}func another_scope() func() int{ // 編譯錯誤,兩個變數不在此函數範圍內 // undefined: outer_var outer_var = 444 return foo}// 回呼函數不會修改外部範圍的資料func outer() (func() int, int) { outer_var := 2 inner := func() int { outer_var += 99 // 試著使用外部範圍的 outer_var 變數 return outer_var // 傳回值是 101,但只在 inner() 內部有效 } return inner, outer_var // 傳回值是 inner, 2 (outer_var 仍是 2)}inner, outer_var := outer(); // inner, 2inner(); // 返回 101inner(); // 返回 200 // 回呼函數的特性
可變參數函數
func main() { fmt.Println(adder(1, 2, 3)) // 6 fmt.Println(adder(9, 9)) // 18 nums := []int{10, 20, 30} fmt.Println(adder(nums...)) // 60}// 在函數的最後一個參數類型前,使用 ... 可表明函數還能接收 0 到多個此種類型的參數// 下邊的函數在調用時傳多少個參數都可以func adder(args ...int) int { total := 0 for _, v := range args { // 使用迭代器逐個訪問參數 total += v } return total}
內建類型
boolstringint int8 int16 int32 int64uint uint8 uint16 uint32 uint64 uintptrbyte // uint8 類型的別名 // 儲存 raw datarune // int32 類型的別名 // 一個 Unicode code point 字元float32 float64complex64 complex128
類型轉換
var i int = 42var f float64 = float64(i)var u uint = uint(f)// 簡化文法i := 42f := float64(i)u := uint(f)
package
- package 在源檔案開頭聲明
- main package 才是可執行檔
- 約定:package 名字與 import 路徑的最後一個單詞一致(如匯入 math/rand 則 package 叫 rand)
- 大寫開頭的標識符(變數名、函數名…):對其他 package 是可訪問的
- 小寫開頭的標識符:對其他 package 是不可見的
流程式控制制結構
if
func main() { // 一般的條件判斷 if x > 0 { return x } else { return -x } // 在條件判斷語句前可塞一條語句,使代碼更簡潔 if a := b + c; a < 42 { return a } else { return a - 42 } // 使用 if 做類型斷言 var val interface{} val = "foo" if str, ok := val.(string); ok { fmt.Println(str) }}
Loops
// Go 語言中迴圈結構只有 for,沒有 do、while、until、foreach 等等for i := 1; i < 10; i++ {}for ; i < 10; { // 等效於 while 迴圈}for i < 10 { // 只有一個判斷條件時可省去分號}for { // 無條件迴圈時,等效於 while(true)}
switch
// switch 分支語句switch operatingSystem { case "darwin": fmt.Println("Mac OS Hipster") // case 語句內建 break,想執行所有 case 需要手動 fallthrough case "linux": fmt.Println("Linux Geek") default: // Windows, BSD, ... fmt.Println("Other")}// 和 if、for 語句一樣,可在判斷變數之前加入一條指派陳述式switch os := runtime.GOOS; os { case "darwin": ...}// 在 switch 中還能做比較,相當於 switch (true) {...}number := 42switch { case number < 42: fmt.Println("Smaller") case number == 42: fmt.Println("Equal") case number > 42: fmt.Println("Greater")}// 多個 case 可使用逗號分隔統一處理var char byte = '?'switch char { case ' ', '?', '&', '=', '#', '+', '%': fmt.Println("Should escape")}
Arrays, Slices, Ranges
Arrays
var a [10]int // 聲明長度為 10 的 int 型數組,注意數群組類型 = (元素類型 int,元素個數 10)a[3] = 42 // 設定元素值i := a[3] // 讀取元素值// 聲明並初始化數組var a = [2]int{1, 2}a := [2]int{1, 2} // 簡短聲明a := [...]int{1, 2} // 數組長度使用 ... 代替,編譯器會自動計算元素個數
slices
var a []int // 聲明 slice,相當於聲明未指定長度的數組var a = []int {1, 2, 3, 4} // 聲明並初始化 slice (基於 {} 中給出的底層數組)a := []int{1, 2, 3, 4} // 簡短聲明chars := []string{0:"a", 2:"c", 1: "b"} // ["a", "b", "c"]var b = a[lo:hi] // 建立從 lo 到 hi-1 的 slice var b = a[1:4] // 建立從 1 到 3 的 slicevar b = a[:3] // 預設 start index 則預設為 0 var b = a[3:] // 預設 end index 則預設為 len(a)a = append(a,17,3) // 向 slice a 中追加 17 和 3c := append(a,b...) // 合并兩個 slice// 使用 make 建立 slicea = make([]byte, 5, 5) // 第一個參數是長度,第二個參數是容量a = make([]byte, 5) // 容量參數是可選的// 從數組建立 slicex := [3]string{"Лайка", "Белка", "Стрелка"}s := x[:] // slice s 指向底層數組 x
數組和 slice 的操作函數
// 迭代數組或 slicefor i, e := range a { // i 是索引 // e 是元素值}// 如果你只要值,可用 _ 來丟棄返回的索引for _, e := range a {}// 如果你只要索引for i := range a {}// 在 Go 1.4 以前的版本,如果 i 和 e 你都不用,直接 range 編譯器會報錯for range time.Tick(time.Second) { // 每隔 1s 執行一次}
map
var m map[string]intm = make(map[string]int)m["key"] = 42fmt.Println(m["key"])delete(m, "key")elem, ok := m["key"] // 檢查 m 中是否鍵為 key 的元素,如果有 ok 才為 true// 使用索引值對的形式來初始化 mapvar m = map[string]Vertex{ "Bell Labs": {40.68433, -74.39967}, "Google": {37.42202, -122.08408},}
結構體
Go 語言中沒有 class 類的概念,取而代之的是 struct,struct 的方法對應到類的成員函數。
// struct 是一種類型,也是欄位成員的集合體// 聲明 structtype Vertex struct { X, Y int}// 初始化 structvar v = Vertex{1, 2} // 欄位名有序對應值var v = Vertex{X: 1, Y: 2} // 欄位名對應值var v = []Vertex{{1,2},{5,2},{5,5}} // 初始化多個 struct 組成的 slice// 訪問成員v.X = 4// 在 func 關鍵字和函數名之間,聲明接收者是 struct// 在方法內部,struct 執行個體被複製,傳值引用func (v Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y)}// 調用方法(有接收者的函數)v.Abs()// 有的方法接收者是指向 struct 的指標// 此時在方法內調用執行個體,將是傳址引用func (v *Vertex) add(n float64) { v.X += n v.Y += n}
匿名結構體
使用 map[string]interface{}
開銷更小且更為安全。
point := struct { X, Y int}{1, 2}
指標
p := Vertex{1, 2} // p 是一個 Vertexq := &p // q 是指向 Vertex 的指標r := &Vertex{1, 2} // r 也是指向 Vertex 的指標var s *Vertex = new(Vertex) // new 返回的指向該執行個體指標
介面
// 聲明介面type Awesomizer interface { Awesomize() string}// 無需手動聲明 implement 介面type Foo struct {}// 自訂類型如果實現了介面的所有方法,那它就自動實現了該介面func (foo Foo) Awesomize() string { return "Awesome!"}
結構體和介面的組合嵌入
// 實現 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.Log(...) // 等同於調用 server.Logger.Log(...)// 欄位同理var logger *log.Logger = server.Logger
Errors
Go 中沒有異常處理機制,函數在調用時在有可能會產生錯誤,可返回一個 Error
類型的值,Error
介面:
type error interface { Error() string}
一個可能產生錯誤的函數:
func doStuff() (int, error) {}func main() { result, err := doStuff() if err != nil { // 錯誤處理 } // 使用 result 處理正常邏輯}
並發
goroutine
goroutine(協程)是輕量級的線程(Go runtime 自行管理,而不是作業系統),代碼 go f(a, b)
就開了一個運行 f
函數的協程。
func doStuff(s string) {}func main() { // 在協程中執行函數 go doStuff("foobar") // 在協程中執行匿名函數 go func (x int) { // 函數實現 }(42)}
Channels
ch := make(chan int) // 建立類型為 int 的 channelch <- 42 // 向 channel ch 寫資料 42v := <-ch // 從 channel ch 讀資料,此時 v 的值為 42 // 無緩衝的 channel 此時會阻塞 // 如果 channel 中無資料,則讀操作會被阻塞,直到有資料可讀// 建立帶緩衝的 channel// 向帶緩衝的 channel 寫資料不會被阻塞,除非該緩衝區已滿ch := make(chan int, 100)close(ch) // 寄件者主動關閉 channel// 在從 channel 讀資料的同時檢測其是否已關閉// 如果 ok 為 false,則 ch 已被關閉v, ok := <-ch // 從 channel 中讀資料直到它被關閉for i := range ch { fmt.Println(i)}// select 語句中 任一 channel 不阻塞則自動執行對應的 casefunc doStuff(channelOut, channelIn chan int) { select { case channelOut <- 42: fmt.Println("We could write to channelOut!") case x := <- channelIn: fmt.Println("We could read from channelIn") case <-time.After(time.Second * 1): fmt.Println("timeout") }}
channel 開發原則
1.向 nil channel 寫資料將卡死,一直阻塞
2.從 nil channel 讀資料將卡死,一直阻塞
3.向已關閉的 channel 寫資料將造成 panic
package mainfunc main() { var c = make(chan string, 1) c <- "Hello, World!" close(c) c <- "Hello, Panic!"}
運行:
4.從已關閉的 channel 讀資料將返回零值
package mainfunc main() { var c = make(chan int, 2) c <- 1 c <- 2 close(c) for i := 0; i < 3; i++ { println(<-c) }}
運行:
輸出
fmt.Println("Hello, 你好, नमस्ते, Привет, ᎣᏏᏲ") // 最基本的輸出,會自動加一個換行p := struct { X, Y int }{ 17, 2 }fmt.Println( "My point:", p, "x coord=", p.X ) // 輸出結構體欄位等s := fmt.Sprintln( "My point:", p, "x coord=", p.X ) // 組合字元串並返回fmt.Printf("%d hex:%x bin:%b fp:%f sci:%e",17,17,17,17.0,17.0) // 類 C 的格式化輸出s2 := fmt.Sprintf( "%d %f", 17, 17.0 ) // 格式化字串並返回hellomsg := ` "Hello" in Chinese is 你好 ('Ni Hao') "Hello" in Hindi is नमस्ते ('Namaste')` // 聲明多行字串,在前後均使用反引號 `
程式碼片段
HTTP Server
package mainimport ( "fmt" "net/http")// 定義響應的資料結構type Hello struct{}// Hello 實現 http.Handler 中定義的 ServeHTTP 方法func (h Hello) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello!")}func main() { var h Hello http.ListenAndServe("localhost:4000", h)}// http.ServeHTTP 在介面內的定義如下:// type Handler interface {// ServeHTTP(w http.ResponseWriter, r *http.Request)// }
運行:
總結
上邊十七個知識點簡要概括了常見文法,可複習使用,但涉及到的細節不多,細讀《Go 程式設計語言》 才是。