---
# System prepended metadata

title: Golang 筆記

---

# Golang 筆記

[TOC]

## Go 的設計理念
> 主要以解決實務工程問題，而非設計出一個在語法機制上很漂亮的語言。

## Go 的特性
* open source
* 靜態型別的編譯語言；語法類似腳本語言
* 跨平台
* 內建 GC (可手動調整觸發時機)
* 內建 concurrency
* 內建 functional programming
* lightweight object system
* coding style 強制統一
* 快速編譯
* 豐富的標準函式庫
* 內建相關開發工具
    * compiler
    * 套件管理 (package management)
    * 語法重排 (syntax formatting)
    * 語法檢查 (syntax checking)

## Go 受批評的特質
* 缺乏泛型
* 缺乏函式重載
* 缺乏運算子重載

## Go 的使用情境
> 網頁程式和其他伺服器程式是 Go 的強項，是目前最適合的開發項目 (sweet spot)。

## Go 程式
### 執行
> 使用 go run 執行後，Go 編譯器會偷偷在系統暫存區建立相對應的執行檔；在未修改程式碼時，第二次執行的速度會比較快。
``` cmd
    go run main.go
```
### 組成
> 每個 Go 程式都要在一開始設置套件 (package) 名稱，套件名稱實際上就是 namespace 的概念；套件的命名規則，application 一律採用 main 這個套件名稱，library 則開法者自訂。
``` go
// Set current package.
package main

// Import some package.
import "fmt"

// Enter main function.
func main() {
	// Print out string `"Hello World"` on stand output.
	fmt.Println("Hello World")
}
```
### 專案的命名模式
> Go 專案不需要專案設定檔，完全依靠專案的內隱知識來自動設置專案；Go 編譯器利用根目錄的名稱來自動命名編譯出來的命令列工具，故可省略專案設定檔。
* 在建立函式庫套件時，我們會讓專案目錄名稱和套件名稱一致，套件使用者才不會搞混。
 
### 專案架構
#### GOROOT 
> 路徑內存放的是 Go 語言內建的工具，我們不應該去動該路徑內的東西，以免造成 Go 開發軟體的毀損。

* Go 編譯器
* Go 相關工具
* 標準函式庫

#### GOPATH
> 路徑內存放的第三方應用程式和函式庫；自己所開發的 Go 程式。
* bin :存放執行檔
* lib :存放二進胃函式庫檔
* src :存放 Go 專案原始碼

## Go 資料型別
### 數字
> 另外有提供大數函式庫，由於大數是用軟體模擬的，會比內建來的慢。
#### 複數
* complex64
* complex128

### 字串
> 在預設情形下，應該優先使用 string 型別，除非有明確的理由，才會使用另外兩種型別。
* string : 是以 UTF-8 編碼來處理的字串，string 的值視為單值而非字元陣列。
* byte : 則保持字串原始的內容，不處理編碼。
* rune : 則是將字串以 Unicode code point 切開時所用的型別。

### Array 和 Slice
> 兩者的差別主要在於陣列的長度是固定的，而切片可以伸縮長度。

### Channel 
> 通道用於共時性 (concurrency) 程式中，在 goroutine 之間傳遞資料。

## Go Variable
### 識別字規範
* 第一個字母須是字母或底線
* 不可使用保留字
> 使用單一底線做為識別字時，代表捨棄接收到的值。
``` go
package main

import (
	"fmt"
	"log"
)

func main() {
	_, err := fmt.Println("Hello World")

	if err != nil {
		log.Fatal(err)
	}
}
```
### 保留字
> golang 的保留字目前只有 25個

![](https://i.imgur.com/osbDPWE.png)

### 識別字撰碼
* 當識別字使用 PascalCase 時，表示該識別字是公開的 (public)。
* 當使用 camelCase 時，表示該識別字是私有的 (private)。

### 型別轉換
> 在 Go 中不能直接把不同型別的資料相結合，例如整數和浮點數不能直接相加。

## 使用 defer 優雅地處理系統資源
> defer 敘述會自動延遲到 defer 所在的函式結束時才觸發指令，不需要手動控制程式流程。
``` go
f, err := os.Create("file.txt")
if err != nil {
	log.Fatal(err)
}
defer f.Close()
```

## 使用 Array 和 Slice
### Array
> 使用迭代器時，對陣列元素的修改是沒有效果的。
``` go
package main
 
import "fmt"
 
func main() {
    arr := [5]int{1, 2, 3, 4, 5}
 
    for _, e := range arr {
        e = e * e
    }
 
    for _, e := range arr {
        fmt.Println(e)
    }
}
```
### Slice
> 切片的內部，其實也是陣列，切片本身不儲存值，而是儲存到陣列的參考 (reference)，簡單地說，切片和陣列內部儲存同一份資料，但透過兩個不同的變數來處理。
* 可在 Runtime 動態產生，這時須使用 make 關鍵字


``` go
package main
 
import (
    "fmt"
)
 
func main() {
    slice := make([]int, 5)
 
    for i := 0; i < len(slice); i++ {
        n := i + 1
        slice[i] = n * n
    }
 
    for _, e := range slice {
        fmt.Println(e)
    }
}
```

## 使用 Map
> 要注意的是，Map 是無序的，我們多執行幾次，就會發現每次的順序都不一樣，見下例。

``` go
package main
 
import (
    "fmt"
)
 
func main() {
    m := make(map[string]string)
 
    m["Go"] = "Beego"
    m["Python"] = "Django"
    m["Ruby"] = "Rails"
    m["PHP"] = "Laravel"
 
    for i := 0; i < 10; i++ {
        for k, v := range m {
            fmt.Println(fmt.Sprintf("%s: %s", k, v))
        }
 
        fmt.Println("")
    }
}
```

## 使用指標
> 指標本身存的值是指向另一個值的記憶體位置 (memory address)，我們通常不會直接使用指標的值，而會透過指標間接操作另一個值。

### 動態配置記憶體
``` go
package main
 
import (
    "fmt"
)
 
type Point struct {
    x float64
    y float64
}
 
func main() {
    p := new(Point)
 
    p.x = 3.0
    p.y = 4.0
 
    fmt.Println(fmt.Sprintf("(%.2f, %.2f)", p.x, p.y))
}
```
> make 函式其實也是動態分配記憶體的一種語法，但 make 回傳的不是指標，而是該型別本身。

## 撰寫函式
### 多個回傳值
> Go 函式允許多個回傳值，多回傳值時常用於錯誤處理 (error handling) 。
``` go 
package main
 
import (
    "log"
)
 
func divmod(a int, b int) (int, int) {
    return a / b, a % b
}
 
func main() {
    m, n := divmod(5, 3)
 
    if !(m == 1) {
        log.Fatal("Wrong value m")
    }
 
    if !(n == 2) {
        log.Fatal("Wrong value n")
    }
}
```
### 不定長度參數
> 不定參數傳入函式後，參數本身是一個切片，在函式中透過此切片即可取得個別的參數值。

``` go
package main
 
import (
    "log"
)
 
func sum(args ...float64) float64 {
    sum := 0.0
 
    for _, e := range args {
        sum += e
    }
 
    return sum
}
 
func main() {
    s := sum(1, 2, 3, 4, 5)
 
    if !(s == 15.0) {
        log.Fatal("Wrong value")
    }
}
```

### 預設變數、函式重載
> Go 的函式本身不支援預設變數；Go 也不支援函式重載。

### 傳值呼叫
> Go 的所有函式都是傳值呼叫 (call by value)，即使是傳遞指標，也是拷貝指標的位址，但不會拷貝指標所指向的值；當值很大時，傳遞指標比傳整個值有效率。

### init 函式
> init 函式是一個特殊的函式，若程式碼內有 init 會在程式一開始執行的時候呼叫該函式，順序在 main 函式之前。

## 使用 Class 和 Object
> 嚴格來說，Go 只能撰寫基於物件的程式 (object-based programming)，無法撰寫物件導向程式 (object-oriented programming)，因為 Go 僅支援一部分的物件導向特性，像是 Go 不支援繼承。

### 靜態方法 (Static Method)
> 由於 Go 語言沒有將物件導向的概念直接加在語法中，不需要用這種語法，直接用頂層函式即可。

### 使用嵌入 (Embedding) 取代繼承 (Inheritance)
>我們重用 Point 的方法，再加入 Point3D 特有的方法，實際上的效果等同於繼承；
>然而，Point 和 Point3D 兩者在類別關係上卻是不相干的獨立物件。
>在 Go 語言中，需要使用介面 (interface) 來解決這個議題。

### 用介面 (Interface)實踐繼承和多型
> 只有方法宣告，但缺乏方法實作的型別。
``` go 
type IPoint interface {
    X() float64
    Y() float64
    SetX(float64)
    SetY(float64)
}
```
* 嵌入是介面用來繼承介面的手法

#### 多型
>若 Point 類別實作 IPoint 介面，而 Point3D 內嵌 Point，則符合多型。
``` go 
type IPoint interface {
    X() float64
    Y() float64
}

type Point struct {
    x float64
    y float64
}

func (p *Point) X() float64 {
    return p.x
}

func (p *Point) Y() float64 {
    return p.y
}

type Point3D struct {
    Point
    z float64
}

func (p3 *Point3D) Z() float64 {
    return p3.z
}

func main() {
    points:=make([]IPoint,0)
    p1:=&Point {
    }
    p2:=&Point3D {
    }
    points = append(points, p1, p2)
}

```
#### 用空介面當萬用型別
> 寫做 interface{}，空介面也是一種特殊型別，該型別可放入任何值；基本上，空介面和介面使用的時機不同，要將其視為兩種不同的概念。


## Funtional Programming
### 函式物件
> 函數式程式的基本前提是函式為一級物件，簡單地說，函式也是值。
### 閉包
> 帶有狀態的函式，稱為閉包 (closure)。
### 嚴格求值 (Strict Evaluation)
> 給函式的實際參數總是在應用這個函式之前求值；主流的程式語言大抵上都是 strict evaluation。

``` go
//此範例會錯誤
package   main   
import ( "fmt" ) 
func   main() { 
	fmt.Println(len([]int{1, 2, 3/0, 4})) 
}

```

## 錯誤處理
> 在 Go 中，錯誤物件是一個 interface。
### 一般錯誤
> 拋出錯誤物件，交由後續的程式自行處理
* 回傳一般錯誤物件
	``` go
	package main
	
	import(
		"fmt"
		"errors"
	)
	func main(){
		err:= errors.New("something error")
		if err != nil {
			fmt.Println(err)
		}
		
		fmt.Println("More message")
	}
	```
* 回傳布林值
	``` go
	package main
	
	import(
		"log"
	)
	
	func main(){
		m := map[int]string{
			1: "one",
			2: "two",
			3: "three",
		}

		v, ok := m[1]
		if !ok {
			log.Fatal("Unable to retrieve key/value pair")
		}

		if !(v == "one") {
			log.Fatal("Wrong value")
		}
	}
	
	```
### 嚴重錯誤
> 引發 panic 事件，將程式提早結束；如果想要從嚴重錯誤中回復，要用 recover 函式，並且要搭配特定的語法。濫用 recover 會使得程式可讀性較差，不會將其常態性使用。

``` go
package main
 
import (
    "fmt"
)
 
func main() {
    defer func() {
        err := recover()
        if err != nil {
            fmt.Println("Recover from panic")
        }
    }()
 
    panic("Some error")
 
    // It didn't occur.
    fmt.Println("More message")
}
```
## 撰寫共時性 (Concurrency) 程式

### goroutine 是輕量級執行緒 (lightweight thread)
> Go 程式以 goroutine 做為並行執行的程式碼區塊，goroutine 類似於執行緒，但更輕量，一次啟動數百甚至數千個以上的 goroutine 也不會占用太多記憶體；並時性程式和傳統的循序式程式的思維不太一樣，執行並時性程式時無法保證程式運行的先後順序，需注意。

* waitGroup : 一般來說 gorotine 和主程式並時執行，若沒有使用，會在主程式結束時即提早結束。
``` go
package main
 
import (
    "log"
    "os"
    "sync"
)
 
func main() {
    // A goroutine-safe console printer.
    logger := log.New(os.Stdout, "", 0)
 
    // Sync between goroutines.
    var wg sync.WaitGroup
 
    // Add goroutine 1.
    wg.Add(1)
    go func() {
        defer wg.Done()
        logger.Println("Print from goroutine 1")
    }()
 
    // Add goroutine 2.
    wg.Add(1)
    go func() {
        defer wg.Done()
        logger.Println("Print from goroutine 2")
    }()
     
    logger.Println("Print from main")
 
    // Wait all goroutines.
    wg.Wait()
}
```
### 利用 channel 在 goroutine 間傳遞資料
> Go 用 channel 在不同並行程式間傳遞資料；由於通道在傳輸時，會阻塞 (blocking) 程式的行進，在此處，我們不需要另外設置 WaitGroup。

``` go
package main
 
import "fmt"
 
func main() {
    // Create a channel
    message := make(chan string)
 
    // Init a goroutine.
    go func() {
        // Send some data into the channel.
        message <- "Hello from channel"
    }()
 
    // Receive the data from the channel.
    msg := <-message
    fmt.Println(msg)
}
```
#### channel 緩衝
>在 goroutine 數量小於 buffered 大小時，可以直接傳送資料，無需等待。
``` go
package main
 
import (
    "log"
    "os"
    "sync"
)
 
func main() {
    // A goroutine-safe console printer.
    logger := log.New(os.Stdout, "", 0)
 
    // Sync among all goroutines.
    var wg sync.WaitGroup
 
    // Make a buffered channel.
    ch := make(chan int, 10)
 
    for i := 1; i <= 10; i++ {
        ch <- i
        wg.Add(1)
        go func() {
            defer wg.Done()
            logger.Println("Print from goroutine ", <-ch)
        }()
    }
 
    logger.Println("Print from main")
    wg.Wait()
}
```
#### 關閉 channel
> 若不用 channel 時，可用 close 函式將 channel 關閉。
``` go
package main
 
import "fmt"
 
func main() {
    ch := make(chan int, 4)
    ch <- 2
    ch <- 4
    close(ch)
    // ch <- 6 // panic, send on closed channel
 
    fmt.Println(<-ch)
    fmt.Println(<-ch)
    fmt.Println(<-ch) // closed, returns zero value for element
}
```
### 使用 select 敘述在多個 channel 間做選擇
> 透過 select，我們可以在多個 channel 中做選擇。
``` go
package main
 
import "time"
import "fmt"
 
func main() {
    // For our example we'll select across two channels.
    c1 := make(chan string)
    c2 := make(chan string)
 
    // Each channel will receive a value after some amount
    // of time, to simulate e.g. blocking RPC operations
    // executing in concurrent goroutines.
    go func() {
        time.Sleep(time.Second * 1)
        c1 <- "one"
    }()
    go func() {
        time.Sleep(time.Second * 1)
        c2 <- "two"
    }()
 
    // We'll use `select` to await both of these values
    // simultaneously, printing each one as it arrives.
    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-c1:
            fmt.Println("received", msg1)
        case msg2 := <-c2:
            fmt.Println("received", msg2)
        }
    }
}
```

#### 利用 mutex 將共時性程式同步化
> Go 也提供較傳統的 Mutex；在共時性程式中，mutex 會將某一段程式暫時鎖住，避免 race condition。

### 參考
[為什麼使用共時性](https://kennyliblog.nctu.me/2019/10/08/Golang-concurrency/#%E7%82%BA%E4%BB%80%E9%BA%BC%E4%BD%BF%E7%94%A8%E5%85%B1%E6%99%82%E6%80%A7)