###### 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>