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

### 識別字撰碼
* 當識別字使用 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)