閉包 Closure
常見使用時機:延長區域變數的生命週期,不會被 GC 吃掉。
// 經典範例
var foo = func() func() {
var i int
return func() {
i++
fmt.Println(i)
}
}() foo() // 1
foo() // 2
foo() // 3
在閉包的使用上,不只可以讓函式回傳函式,也可以讓函式回傳 struct 等其他型態。
(To be honest, closure 的使用我還不是很熟悉 QAQ)
套件 Package
話風一轉,休息一下,來點比較沒那麼複雜的東西醒個腦!套件就是通常你在 Go 程式碼中最上方會看到的東西,名稱為 main 的套件就是程式的進入點。
如欲使用某個套件中的成員,就必須匯入那個套件。有兩種方式,單行匯入和多行匯入:
// single line import
import "fmt"
import "math"// multiple lines import
import (
"fmt"
"math"
)
如果在一個 package 中,想要存取另一個 package 的成員,像是變數、函式、結構等,就必須匯出成員,export 的方法即將成員名稱開頭設定為大寫。
package mypkg
var myVar = 100 // can only be used in mypkg
const MyConst = "hello" // can be used outside from mypkg
例外處理 Exception Handling
例外處理是指當程式發生意外事件時的處理程序,也可以稱為錯誤處理。Go 有提供基本的錯誤結構型別,定義在 errors 套件裡,用 New 可以快速建立一個錯誤實體。話不多說,直接用下方例子來理解。
Go 的錯誤語法包含以下:defer, panic, recover。
defer 是延遲執行,可以將其後面的程式碼延後至函式結束時執行。Defer 可以 stack,執行順序會是最後被 deferred 的 function 先被執行 (LIFO)。
func main() {
fmt.Println("defer begin")
defer fmt.Println("Hello World")
defer fmt.Println("Hey World")
defer fmt.Println("Hi World")
fmt.Println("defer end")
}
/* print out:
defer begin
defer end
Hi World
Hey World
Hello World
*/
使用 panic 會直接系統崩潰、服務中斷。不過,若在 panic 之前就 defer 的程式碼,則會在崩潰前執行。使用 panic 時機為:當這個函式回傳的錯誤不是 nil 時直接讓系統崩潰,防止之後造成更大的錯誤。
func main() {
defer fmt.Println("deferred msg")
panic("oops! System crashed!")
fmt.Println("hello,world")
}
/* print out:
deferred msg
oops! System crashed!
*/
recover 可捕捉系統自動產生或手動設定產生的 panic 錯誤,回復系統以避免程序崩潰。recover 必須和 defer 配合使用!recover 必須和 defer 配合使用!recover 必須和 defer 配合使用!(很重要)
這段用文字說明有點難,不如直接聽講解:
Check more about defer / panic / recover:
格式化輸出
格式化可將不同型別的資料用 fmt.Printf()
印出,或是用 fmt.Sprintf()
轉成字串,跟 C 概念完全ㄧ樣。
package main
import "fmt"type Person struct {
Name string
Age int
}func main() {
haren := &Person{"Haren Lin", 22}
fmt.Printf("%v \n", haren) // &{Haren Lin 22}
fmt.Printf("%+v\n", haren) // &{Name:Haren Lin Age:22}
fmt.Printf("%#v\n", haren) // &main.Person{Name:"Haren Lin", Age:22}
fmt.Printf("%T \n", haren) // *main.Person
}
Base64 Encoding
Base64 是一種常見的字元編碼方式,常用於網路傳輸,如電子郵件。Go 標準函式庫就有提供 base64 套件 encoding/base64
。
JSON Encoding
JSON 是現在做 Data 最常用的資料交換格式,Go 標準函式庫也有提供 json 套件 encoding/json
。對於 struct 轉乘 json 字串輸出,需注意欄位名稱開頭必須大寫,才會被輸出。
檔案操作
(1) https://gobyexample.com/reading-files
(2) https://ithelp.ithome.com.tw/articles/10221244
網路操作
HTTP Client:我們可以使用 http.Get +URL 來取的你想抓的網頁內容,除果成功的話,你的狀態會是 200 OK。接著,我們可以用 ioutil.ReadAll 把我們抓回來的東西的 Body 全讀取出來,印出來看長什麼樣。
或是使用 bufio.NewScanner 把 response 的 body 餵進去,讓 scanner 用 line by line 的方式讀取。
HTTP Server:
Go-Routine
Go-Routine 可視為 lightweight thread,只要在你想做的事情前面加上 go 就可以達成。此外,避免讓機器跑到出問題,可以用 runtime.GOMAXPROCS(number) 來設定執行緒上限。
執行結果如下:
hello from: main - 1
hello from: C - 1
hello from: B - 1
hello from: A - 1
hello from: main - 2
hello from: A - 2
hello from: C - 2
hello from: B - 2
hello from: B - 3
hello from: C - 3
hello from: A - 3
hello from: main - 3
hello from: main - 4
hello from: B - 4
hello from: A - 4
hello from: C - 4
參考影片:
通道 Channel
Go-Routine 可實現 Concurrency 提高運算效能,但需要配合 Channel 的使用才能發揮最大效益。 Channel 是一個 Message Queue 的概念,FIFO,可以在多個 Go-Routine 之間傳送資料或等待訊息。
Chennel 也是一種型別,預設值為 nil,宣告時要用 chan 關鍵字,以及定義傳送資料的型別;在建立實體時要用 make。
package main
import "fmt"
func main() {
var chan_eg chan int // channel declaration
fmt.Println(chan_eg) // <nil>
chan_eg = make(chan int)
fmt.Println(chan_eg) // 0xc000068060
}
如何傳送資料以及接收資料?
// send data via channel
channel_name <- val // receive data from channel
rcvr_variable <- channel_name
當一個 Go-Routine 發送資料時,就必須有另一個 Go-Routine 接收資料,不然程式會有錯誤。因為發送端正在等待接收端取得資料,沒有接收端的話就會一直等下去,形成 Deadlock。
除了用 x := <-chn
方式接收資料外,也可以用 for range 循環接收多筆資料,但記得 channel 用完後要 close channel:close(channel_name)。
上例輸出為: 1 9 25 49 81
上面舉例說明如果還有疑惑,可以參考小馬中文講解影片:
其實,Channel 也有種類之分。第一個,有緩衝的通道 Buffered Channel:當一個有緩衝通道被傳入資料時,不會發生錯誤,資料會先暫存 buffer 裡,直到被接收端取出。但如果 buffer 已經滿了,這時候又再傳入就會發生錯誤,跳出 fatal error: all goroutines are asleep — deadlock!;反之,如果接收的通道是空 buffer,也會發生錯誤。
chn := make(chan int, 2) // declare a channel with buffer len = 2
fmt.Println(len(chn)) // use len(chn) get the # of data in the buffer
chn <- 45
fmt.Println(len(chn)) // 1
chn <- 50
fmt.Println(len(chn)) // 2
第二種,單向通道。在沒有緩衝的通道下,Go-Routine 只能做發送或接收其中一種,必須要有另一個 Go-Routine 配合才不會阻塞。但在有緩衝通道時,Go-Routine 可自己發送或接收,因此我們能限制通道是只能發送或接收。
var channel_name chan<- data_type // only sending 發送通道
var channel_name <-chan data_type // only receive 接收通道
以下使用剛才的平方數來舉例,建立單向通道來取值。
延伸閱讀:
不得不說,Go-Routine 以及 Channel 這塊東西第一次接觸會覺得有點小複雜,需要時間來回複習慢慢感受,共勉之!
參考資料 / Reference
1. A Tour of Go
2. Golang 30 天
3. Golang Crash Course
4. Learn Go Programming — Golang Tutorial for Beginners
5. 小馬技術
This article will be updated at any time! Thanks for your reading. If you like the content, please click the “clap” button. You can also press the follow button to track new articles at any time. Feel free to contact me via LinkedIn or email.