###### tags: `Go` # Go 學習筆記 [TOC] ## 型別 ### 型別名稱 #### 整數 - int8、int16、int32、int64、uint8、uint16、uint32、uint64 - 特殊 - int、uint:平台相依 - byte:uint8 的別名 - rune:int32 的別名 - uintptr - 跨型別運算不會自動轉型,需要手動轉型: ``` var x int16 = 10 var y int32 = 20 var z int32 z = int32(x) + y ``` #### 浮點數 - float32、float64 #### 字元 - rune:int32 的別名 #### 字串 - string (賦值後不可變) #### 陣列 - `var x [3]int`、`var x = [3]int{10, 20, 30}` - 省略元素數量:`var x = [...]int{10, 20, 30}` - 稀疏陣列:`var x = [20]int{1, 8:2, 13:3, 4, 19:1}` - `x[8]` 為 2、`x[13]` 為 3、`x[14]` 為 4,未指定處為零值。 - 陣列大小需在編譯期指定,無法使用變數指定大小 - 陣列大小為型別的一部分,`[3]int` 和 `[4]int` 為不同型別 - 不常使用陣列,而會使用 slice #### slice - `var x = []int{10, 20, 30}`、`var x = []int{1, 8:2, 13:3, 4, 19:1}` - `var x []int` 的 x 內容為 slice 的零值,即 `nil` ### literal #### 整數 - 預設型別為 int - prefix:`0b` (2進制)、`0o` (8進制)、`0x` (16進制) #### 浮點數 - 預設型別為 float64 #### 字元 - 型別預設為 rune - 一般用法:`'a'`、`'1'` - 特殊用法:`'\\'` (反斜線)、`'\''` (單引號)、`'\n'` (換行)、`'\141'` (8進位)、`'\x61'` (16進位)、`'\u0061'` (16-bit Unicode)、`'\U00000061'` (32-bit Unicode) #### 字串 - 雙引號包覆:`"abcdefg"`、`"abcd\nefgh"` - raw string literal:\`1234\` #### 布林值 - `true`、`false` ## 變數宣告 - 程式中有未使用變數的話會引發編譯錯誤 - Go 習慣使用 camel case 命名變數,如 `programCounter`,不使用 snake case,如 `program_counter` - 賦值宣告:`var x int = 10` - 零值宣告:`var x int` - 以 literal 的預設型別宣告:`var x = 10` - 多變數宣告:`var x, y int`、`var x, y int = 10, 20`、`var x, y = 10, "abc"` - 大量宣告 ``` var ( x int y = 10 z int = 20 a, b = 30, "abc" f string ) ``` - 短格式宣告 - `x := 10` 即 `var x = 10` - 可重定義既有變數的值而不引發編譯錯誤: ``` x := 10 x, y := 20, "abc" ``` 但要注意這種用法會遮蔽外層 block 的同名變數: ``` func main() { x := 10 if x > 5 { x, y := 5, 20 fmt.Println(x, y) } fmt.Println(x) } ``` 結果會是: ``` 5 20 10 ``` - 無法在 function 之外使用,因此 package-level 變數只能用 `var` 宣告 - 指定精確型別時,不建議寫 `x := byte(20)` 而是 `var x byte = 20` - package-level 的識別字 (如變數、函式) 首字母大寫才可被外界使用,首字母小寫外界不可見 ## 常數宣告 - 程式中即使有未使用的常數也不會引發編譯錯誤 - 常數只能存放可在編譯期求出的值 - array、slice、map、struct 不能宣告為常數 - 已定型別常數:`const x int64 = 10` - 未定型別常數:`const y = 10 * 10`,可賦值至所有數值型別 (含浮點數) - 串列宣告: ``` const ( x = 10 y = "abc" ) ``` ## 運算子 ### 數值 - 算術運算子:`+`、`-`、`*`、`/`、`%` (浮點數不可用)、`++` (postfix increment)、`--` (postfix decrement) - 位元運算子:`&`、`|`、`^`、`~`、`&^` (AND NOT、NAND)、`<<`、`>>` - 賦值運算子:`=`、算術運算子及位元運算子後方加 `=` - 比較運算子:`==`、`!=`、`>`、`>=`、`<`、`<=` - 邏輯運算子:`&&`、`||`、`!` ### 字串 - 比較運算子:`==`、`!=`、`>`、`>=`、`<`、`<=` - 連接運算子:`+` ### 陣列 - 比較運算子:`==`、`!=` - 相同型別的陣列才能比較,`[3]int` 和 `[4]int` 視為不同型別,且無法轉型 ### slice - 比較運算子:`==`、`!=`,只能和 `nil` 比較 ### map - 比較運算子:`==`、`!=`,只能和 `nil` 比較 ## slice - `var x = []int` 建立 nil slice - `var x = []int{1, 2, 3}` 建立內容為 `[1, 2, 3]` 的 slice - `var x = []int{}` 建立空 slice (不為 nil,轉換成 JSON 時使用) - 短格式:`x := []int{1, 2, 3}` - 附加元素,可附加一到多個元素 ``` var x = []int x = append(x, 1) x = append(x, 2, 3, 4) ``` x 的內容變成 `[1, 2, 3, 4]` - slice 為連續記憶體空間,附加元素超出原配置容量時,行為與 C++ 的 `std::vector` 類似 - `len()` 取長度,`cap()` 取容量 - make - make 造出的 slice 不等於 `nil` - `x := make([]int, 5)` 建立長度、容量均為 5 的 slice,各元素內容為零值 - `x := make([]int, 5, 10)` 建立長度為 5、容量為 10 的 slice,可用 `x := make([]int, 0, 10)` 建立長度為 0、容量為 10 的 slice - 切割 - `x[:2]`:取 x 的 `[0, 2)`,即 `x[0]` 和 `x[1]`,不含 `x[2]` - `x[1:]`:從 x[1] 取到最後一個元素 - `x[1:3]`:取 x 的 `[1, 3)`,即 `x[1]` 和 `x[2]`,不含 `x[3]` - `x[:]`:取整個 x - 切割出來的子 slice 與父 slice 共享記憶體空間,改變其內容會影響父 slice,不要對子 slice 使用 `append()` 以免造成非預期結果 - 可對陣列進行切割造出 slice,記憶體與陣列共用 - 要避免記憶體共用,可先用 `make()` 造出新 slice,再用 `copy()` 複製資料 - 複製 - `copy(dest, src)` 可複製 slice 內容,傳回值為複製的元素數量 ``` x := []int{1, 2, 3, 4} y := make([]int, 4) num := copy(y, x) ``` - 承上,`y := make([]int, 2)` 則只會複製前兩個元素,`copy()` 傳回 2 - 承上,`copy(y, x[2:])` 會複製 `x[2]` 及 `x[3]` 的內容至 `y` - `copy(x[:3], x[1:])` 會將 `x[1]` ~ `x[3]` 的內容覆蓋到 `x[0]` ~ `x[2]`,最後 x 的內容為 `[2, 3, 4, 4]` ## 字串 - 字串內的每個元素是 byte 不是 rune ``` var x string = "abcdefg" var y byte = x[3] ``` - `len()` 的結果為字串的 byte 數,處理 multi-byte 字串時要小心 - 可使用切片運算製作子字串 ``` var x string = "abcdefg" var y string = x[1:3] var z string = x[2:] ``` - 字串不可變,子字串無法修改,不用擔心記憶體共用問題 - 切割 multi-byte string 時要注意切割是以 byte 為單位,可能切到不完整的 multi-byte 字元 - 應使用 `strings` 和 `unicode/utf-8` 套件內的 functions 取子字串,幾乎不直接使用切片和索引運算式 - 應使用 for-range 敘述來 traverse 裡面的字元,這樣取出的每個成員會是 rune 而不是 byte - 轉型 - 單一 rune 可以轉為字串 ``` var r rune = 'a' var s string = string(a) ``` - 單一 byte 可以轉為字串 ``` var b byte = 'b' var s string = string(b) ``` - 字串可與 byte slice 和 rune slice 互相轉換,譬如 ``` var s string = "abcdefg" var bs []byte = []byte(s) var rs []rune = []rune(s) ``` 其中 byte slice 最常見,因為 Go 程式常讀寫 byte slice - 字串轉數字使用 `strconv.Atoi()` ``` str := "1" value, err := strconv.Atoi(str) ``` ## map - `var m map[string]int` 建立 key 為 string 型別、value 為 int 型別的 map,內容為零值 nil - 宣告空白 map (非 nil):`m := map[string]int` - 用 literal 宣告 map ``` m := map[string]int { "a": 1, "b": 2, } ``` ``` m := map[string][]string { "a": []string{"str1", "str2", "str3"}, "b": []string{"str4", "str5", "str6"}, } ``` 最後一個元素的後方也需要加上逗號 `,` - `m := make(map[string]int)`,知道預定大小的話,可以使用 `make()` 的第二個參數指定大小:`m := make(map[string]int, 5)` - key 必須為可比較的型別,因此 slice 和 map 無法成為 key - `len()` 可取得 key 的總數 - 新增元素內容 ``` m := map[string]int{} m["a"] = 1 m["b"] = 2 m["c"]++ ``` - 取得元素:`v = m["key"]`,key 不存在時傳回 value 型別的零值 - 取得元素並檢驗存在性:`v, ok = m["key"]`,ok 為 `false` 時代表該元素不存在 - 刪除元素:`delete(m, "key")`,元素不存在或 m 為 nil 時均不會發生錯誤,`delete()` 無傳回值 - 模擬 Go 不支援的 set 容器 ``` intSet := map[int]bool{} values := []int{2, 1, 3, 5, 4} for _, v := range values { intSet[v] = true } ``` 這是利用了 bool 零值為 false 的特性,查到不存在的 key 時,會得到 false ## struct - 定義具名 struct ``` type student struct { id int name string department string } ``` 成員之間沒有逗號分隔 - 定義具名 struct 變數 - 零值:`var s student` - 指定完整內容 ``` s := student { 1, "bob", "philosophy", } ``` - 指定部分內容 ``` s := student { id: 1, name: "bob" } ``` 欄位名稱不加任何括號,沒指定的欄位為零值 - 定義匿名 struct 變數 - 零值 ``` var person struct { id int name string job string } ``` - 帶有初值 ``` person := struct { id int name string job string }{ id: 1 name: "bob" } ``` - 不同的具名 struct 為不同型別,無法賦值和比較,但如果兩個具名 struct 的欄位順序、名稱、型別相同,可以進行型別轉換,之後就能賦值或比較 - 比較運算子的其中一方為匿名 struct 變數時,只要符合欄位順序、名稱、型別相同的條件,不需要進行型別轉換,也可以賦值或比較。進行測試時,常用到匿名 struct。 ## pointer - 零值為 nil,對 nil pointer 做 dereference 會引發 panic - 宣告:`var ptr *int`、`var ptr = new(int)` - 基本用法 ``` x := 10 pointerToX := &x *pointerToX = 20 fmt.Println(*pointerToX) ``` - 可對 struct literal 取位址,其它基本型別的 literal 不行 ``` type student struct { id int name string department string } ptr := &student{} ptr.int = 10 ``` `ptr.int = 10` 和 `(*ptr).int = 10` 有相同結果,一般不使用後者寫法 ## 控制結構 ### if ``` if x := foo(); x == 0 { ... } else if x >= 1 { ... } else { ... } ``` ### for - 標準 for 迴圈 ``` for i := 0; i < 10; i++ { if i == 5 { continue } ... } ``` - 只有條件式的 for 迴圈 ``` i := 1 for i < 100 { i++ } ``` - 無窮 for 迴圈 ``` i := 1 for { if i == 100 { break } i++ } ``` - for-range - 對 array 或 slice 做 traversal ``` values := []int{1, 2, 3, 4, 5} for i, v := range values { ... } ``` 對 array、slice、string 做 traversal 時,i 值為 index。如果要忽略,可用底線 `_`: ``` values := []int{1, 2, 3, 4, 5} for _, v := range values { ... } ``` - 對 string 做 traversal: ``` str := "abcdefg" for _, r := range str { ... } ``` 其中 r 的型別為 rune 而不是 byte,當字串內有 multi-byte 字元時,索引值會跳過該字元的 byte 數而不連號 - 對 map 做 traversal ``` m := map[string]int{"a": 1, "b": 2, "c", 3} for k, v := range m { ... } ``` 如果只要 key 不需要 value,可直接不寫,而不使用 `_`: ``` m := map[string]int{"a": 1, "b": 2, "c", 3} for k := range m { ... } ``` - for-range 取出的值是複本,對其做修改不會影響到原資料內容,如果要修改內容,可利用 index: ``` values := []int{1, 2, 3, 4, 5} for i, v := range values { values[i] = v * 2 } ``` - 帶 label 的 for 迴圈 ``` func main() { strs := []string{"abcdefg", "hijklm"} outer: for _, str := range strs { for _, r := str { if r == 'e' { continue outer } } } } ``` - 迴圈在處理到字串 "abcdefg" 的 'e' 時,`continue outer` 會對外迴圈作用,直接開始處理 slice 內的下個字串 "hijklm" - `continue` 和 `break` 都可使用 label ### switch - 典型用法 ``` str := "abcdefg" switch n := len(str); n { case 0, 1, 2: fmt.Println("len <= 2") case 3, 4, 5: fmt.Println("len >= 3 && len <= 5") case 6, 7, 8: fallthrough default: fmt.Println("len > 5") } ``` - 每個 case 不需要大括號也自成一個 block,內部宣告的變數 scope 不會跑到外面去 - case 之間不需要 `break`,但仍可使用 `break` - 沒寫 `fallthrough` 的話,控制流不會跑到下個 case 裡 - case 內如果有 for 迴圈,break 會對 switch statement 作用,此時應使用帶 label 的 for 迴圈,使 break 能正常作用在 for 迴圈上 - 可用 `==` 比較的型態都能丟進 switch 敘述,也就是除了 slice、map、channel、function,以及不包含這些型態欄位的 struct 均可 - blank switch ``` strs = []string{"abc", "abcd", "abcdefg"} for _, str := range strs { switch length := len(str) { case length < 4: fmt.Println("short") case length > 6: fmt.Println("long") default: fmt.Println("middle") } } ``` 也就是把條件式放到 case label 處。 ### goto - 不可跳過變數宣告式,不可跳到其它 block 內 ## function - 典型用法 ``` func mul(lhs int, rhs int) int { return lhs * rhs } ``` ``` func foo() { fmt.Println("foo is called") } ``` 不傳回值的 function 不用寫傳回型別和 return 敘述。 - multiple return values ``` func foo() (int, int) { return 1, 2 } func main() { a, b := foo() c, _ := foo() ... } ``` - named return value ``` func foo() (result int) { result = 10 return result } ``` - 只有一個回傳值也要用括號包住 - 如果把 `return result` 改成 `return 0`,最後結果仍是傳回 `0`,無論先前 `result` 被改成什麼值 - naked return ``` func foo() (result int) { result = 10 return } ``` - 只有在使用 named return value 時可以使用 naked return,上例結果是傳回 `10` - 因為資料流不易分析使得他人難以閱讀,並不建議使用 - variadic parameters ``` func foo(values ...int) { for _, v := range values { fmt.Println(v) } } func main() { foo() foo(1, 2, 3, 4) values := []int{5, 6, 7, 8} foo(values...) foo([]int{9, 10}...) ... } ``` - 和 C 一樣必須是最後一個參數,不同之處是它可以是唯一的參數 - 使用 for-range 敘述來 traverse - 實際上接收端是轉換成 slice 來處理 - 當作 value 使用時,其 type 為 function 本身的 signature,只要 signature 相同,即可視為相同 type ``` func add(lhs int, rhs int) int { return lhs + rhs} func sub(lhs int, rhs int) int { return lhs - rhs} var funcMap = map[string]func(int, int) int { "+": add, "-": sub, } ``` 亦可使用 type 定義型態名再使用: ``` type ArithOp func(int, int) int var funcMap = map[string]func(int, int) int { "+": add, "-": sub, } ``` - anonymous function ``` func foo() { func(lhs int, rhs int) { fmt.Println(lhs * rhs) }(2, 3) } ``` - closure - 當參數傳遞 ``` func foo(value int, f func(int) int) { result := f(value) fmt.Println(result) } func main() { foo(10, func(v int) int { return i * 3 }) } ``` - 當傳回值 ``` func makeMulFunc(multiplicand int) func(int) int { return func(multiplicator int) int { return multiplicand * multiplicator } } func main() { f = makeMulFunc(5) fmt.Println(f(10)) } ``` ### defer - 退出函式時必定執行其內容,按照 LIFO 原則,最後註冊的最先執行 ``` func main() { defer func() { fmt.Println("deferred closure 1 executed") }() defer func() { fmt.Println("deferred closure 2 executed") }() fmtPrintln("main function exited") } ``` 得到以下出輸出: ``` main function exited deferred closure 2 executed deferred closure 1 executed ``` - 可檢查 named return value 的內容 ``` func foo(x int) (result int) { defer func() { fmt.Println(result) }() return x * 10 } ``` - 如果 closure 帶有參數,敘述末端的小括號 `()` 裡可以把對應引數傳入 - deferred closure 不需要有傳回值,就算有也無法使用 - Go 只有 pass by value,但 slice 及 map 是以 pointer 實作,所以 callee 對 slice 及 map 參數進行的變更也會反應到 caller,但 callee 無法擴充 slice - callee 裡擴充 slice 無法改變 caller 端的原因,是 slice 被實作成一個包含三個欄位的 struct,一個代表長度,一個代表容量,一個是指向資料塊的 pointer,傳遞 slice 實際上是複製了這個 struct 的內容來傳遞,因此 callee 更動了 slice 的長度,只是對這個 struct 的複本進行修改 - 如果 callee 更動了長度導致超出原有容量,會引發重新配置,此時 caller 跟 callee 操作的資料塊就不再是同一個,此時即使 callee 修改內部元素的值,也完全不會影響到 caller ## Go Standard Library <https://pkg.go.dev/std>