- 空白標識符、匿名佔位符(blank identifier、anonymous placeholder),底線「_」
佔位置,不用對不須要的資料多設一個變數
例1:func test() (string, string) {
return "X", "Y"
}
func main() {
var a, _ = test()
fmt.Println(a) //X
}
例2:arr := [3]int{10, 20, 30}
sum := 0
for _, v := range arr {
sum += v
}
fmt.Println(sum)//60
- 不定數量參數,「...」
例1:陣列元素不定數量arr := [...]string{"A", "B", "C"}
fmt.Println(len(arr))//3
例2:function參數不定數量func test(args ...string) {
fmt.Println(args) //[A B C]
}
func main() {
test("A", "B", "C")
}
例3:slice打散sliceA := []string{"A", "B", "C"}
sliceB := []string{"D", "E"}
sliceC := append(sliceA, sliceB...)
fmt.Println(sliceC)//[A B C D E]
- [...]string{"A", "B", "C"} 和 []string{"A", "B", "C"} 的差異
[...]string{"A", "B", "C"} 產生的是陣列 array
[]string{"A", "B", "C"} 產生的是切片 slice (背後產生一個隱性陣列)
參考:Confusion with “…” operator in golang
https://stackoverflow.com/questions/28692410/confusion-with-operator-in-golang - 沒有 while,用 for 實現
例1:i := 0
for i < 10 {
i++
}
例2:無窮迴圈for {
//break
}
- 沒有 class,用結構(struct)實現。
沒有繼承,用嵌入組合、介面解決,避免繼承產生的維護難度(多用合成/聚合,少用繼承)。 - struct指標語法糖。
根據struct接受的參數型態,自動進行取址(reference &)或取值。(dereference *)進行操作
例1:指標自動轉換取值操作type aa struct {
m int
}
func main() {
var ptr = &aa{10}
fmt.Println(ptr) //&{10}
ptr.m = 20 //自動等同 (*ptr).m=10
fmt.Println(ptr) //&{20}
}
例2:指標自動轉換取址操作type aa struct {
m int
}
func (x *aa) add(v int) { //aa須為指標
x.m = x.m + v
}
func main() {
var p = aa{10}
fmt.Println(p) //{10}
p.add(20) //自動等同 (&p).add(20)
fmt.Println(p) //{30}
}
例3:指標自動轉換取值操作type aa struct {
m int
}
func (x aa) add(v int) { //aa須為值
x.m = x.m + v
fmt.Println(x) //{30}
}
func main() {
var p = &aa{10}
fmt.Println(p) //&{10}
p.add(20) //自動等同 (*p).add(20)
fmt.Println(p) //&{10} 傳值過去,所以原本的變數仍為10
}
- errors
errors.New()fmt.Printf("%#v\n", errors.New("test err"))
//&errors.errorString{s:"test err"}
fmt.Errorf()
fmt.Printf("%#v\n", fmt.Errorf("test err %d", 456))
//&errors.errorString{s:"test err 456"}
//使用 %w 包裹(嵌套)錯誤
fmt.Printf("%#v\n", fmt.Errorf("test err2 %w", errors.New("test err")))
//&fmt.wrapError{msg:"test err2 test err", err:(*errors.errorString)(0xc000032e70)}
errors.Unwrap() 解開包裹的錯誤,取得裡層的錯誤
fmt.Printf("%#v\n", errors.Unwrap(fmt.Errorf("test err2 %w", errors.New("test err"))))
//&errors.errorString{s:"test err"}
errors.Is(errA, errB) bool
判斷 errA 是否為 errB 的錯誤
如果 errA 是包裹的錯誤,會一層一層解開,跟 errB 比對,任一層相符即為truevar errB = errors.New("test err")
var errWrap1 = fmt.Errorf("w1 %w", errB) //包1層
var errWrap2 = fmt.Errorf("w2 %w", errWrap1) //包2層
fmt.Printf("%#v\n", errB) //&main.myErr{msg:"test err"}
fmt.Printf("%#v\n", errWrap1) //&fmt.wrapError{msg:"w1 test err", err:(*errors.errorString)(0xc000032210)}
fmt.Printf("%#v\n", errWrap2) //&fmt.wrapError{msg:"w2 w1 test err", err:(*fmt.wrapError)(0xc000004500)}
fmt.Printf("%t\n", errors.Is(errB, errWrap1)) //false
fmt.Printf("%t\n", errors.Is(errWrap1, errWrap2)) //false
fmt.Printf("%t\n", errors.Is(errWrap1, errB)) //true
fmt.Printf("%t\n", errors.Is(errWrap2, errB)) //true, errWrap2是包裹的錯誤,會一層一層解開比較
fmt.Printf("%t\n", errors.Is(errWrap2, errWrap1)) //true
errors.As(errA, targetB interface{}) bool
判斷 errA 是否為 targetB 類型的錯誤,如果 errA 是包裹的錯誤,會一層一層解開,跟 targetB 比對
將第一個比對符合的錯誤,指派到 targetB,並回傳 truetype myErr struct {
msg string
}
func (e *myErr) Error() string {
return e.msg
}
func main() {
var targetErr *myErr
errWrap1 := fmt.Errorf("w1 %w", &myErr{msg: "test err"}) //包1層
var errWrap2 = fmt.Errorf("w2 %w", errWrap1) //包2層
fmt.Printf("%#v\n", targetErr) //(*main.myErr)(nil)
fmt.Printf("%#v\n", errWrap1) //&mt.wrapError{msg:"w1 test err", err:(*main.myErr)(0xc0000321f0)}
fmt.Printf("%#v\n", errWrap2) //&fmt.wrapError{msg:"w2 w1 test err", err:(*fmt.wrapError)(0xc0000044a0)}
fmt.Printf("%t\n", errors.As(errWrap1, &targetErr)) //true
fmt.Printf("%#v\n", targetErr) //&main.myErr{msg:"test err"}
fmt.Printf("%t\n", errors.As(errWrap2, &targetErr)) //true
fmt.Printf("%#v\n", targetErr) //&main.myErr{msg:"test err"}
}
- defer 延遲執行,採LIFO後進先出的順序(Last In, First out)
通常可使用在解鎖互斥鎖,或關閉文件defer f.Close()避免最後忘記關閉原先開啟的文件。defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
//結果
3
2
1
- panic() 讓程式失敗中斷,類似其他語言的 throw exception
可使用在巢狀深層呼叫,發生錯誤時,便於向外傳播panic("發生錯誤")
fmt.Println("hello,world") //不會執行到這裡,程式在前一行已崩潰停止
//執行結果
panic: 發生錯誤
goroutine 1 [running]:
main.main()
d:/Go/test/test.go:29 +0x45
Process exiting with code: 0
- recover(),recover 搭配 defer 可捕捉 panic 產生的錯誤,避免程式中斷崩潰停止
recover() 回傳值為傳給 panic() 的值,但(所以)在以下情況會是 nil
A.panic's argument was nil;
B.the goroutine is not panicking;
C.recover was not called directly by a deferred function.
例1:捕捉錯誤,避免系統中斷崩潰停止,但後續程式仍不會執行
defer func() {
log.Println("AA")
err := recover()
fmt.Println(err)
log.Println("BB")
}()
panic("發生錯誤")
fmt.Println("hello,world") //雖然程式不會崩潰停止,但依然不會執行到這裡
//執行結果
2021/01/18 23:23:29 AA
2021/01/18 23:23:29 BB
發生錯誤
Process exiting with code: 0
例2:捕捉錯誤,避免系統中斷崩潰停止,後續程式仍可執行
參考 https://golang.org/ref/spec#Handling_panics
Handling panics
//定義一個 protect() 函式,可傳入另一個可能發生panic的函式執行,
func protect(g func()) {
defer func() {
log.Println("done") // Println executes normally even if there is a panic
if x := recover(); x != nil {
log.Printf("run time panic: %v", x)
}
}()
log.Println("start")
g()
}
func main() {
protect(func() {
panic("發生錯誤")
})
log.Println("hello,world")
//執行結果
2021/01/18 23:41:03 start
2021/01/18 23:41:03 done
2021/01/18 23:41:03 run time panic: 發生錯誤
2021/01/18 23:41:03 hello,world
Process exiting with code: 0
}
- 疑問:log.Printf()、fmt.Println() 穿插寫,每次執行輸出的順序可能不一樣?
log.Printf("A")
fmt.Println("B")
log.Printf("C")
//執行結果1
2021/01/15 23:38:59 A
B
2021/01/15 23:38:59 C
Process exiting with code: 0
//執行結果2
2021/01/15 23:38:36 A
2021/01/15 23:38:36 C
B
Process exiting with code: 0
- goroutine 併發
語法:go funciotn或method
- channel 通道
多個goroutine併發,之間溝通的管道
無緩衝通道,Ex:make(chan int)
有緩衝通道,Ex:make(chan int, 緩衝大小) - 通道 deadlock 死鎖
- 從沒資料的通道中,接收資料
ch := make(chan int, 1)
//ch <- 5
A := <-ch //
log.Print(A)
- 空的 select
select {}
- 無緩衝通道(或有緩衝通道滿了之後),再發資料給此通道,發送、接收須用goroutine併發分開處理,否則會發生deadlock死鎖
Ex1:未超出緩衝,不用另一個goroutine接收
ch := make(chan int, 1) //1個緩衝區
ch <- 5 //剛好用完緩衝區
A := <-ch
log.Print(A) //A
Ex2:緩衝滿了之後,發送和接收,須用goroutine併發處理
ch := make(chan int, 1) //1個緩衝區
ch <- 5 //剛好用完緩衝區
go func() {
ch <- 6 //緩衝用完,須用goroutine併發處理
ch <- 7
}()
A := <-ch
log.Print(A) //5
A = <-ch
log.Print(A) //6
A = <-ch
log.Print(A) //7
- select 若沒 default, case敘述句跟沒資料的通道取資料、或發送資料給無緩衝區的通道,會出現deadlock死鎖
ch := make(chan int, 1)
//ch <- 5
select {
case <-ch:
default:
log.Print("避免deaklock")
}
- select 通道中的有同時多個 case 條件都符合時,是隨機執行的
ch := make(chan int, 1)
//ch <- 5
select {
case ch <- 1:
case ch <- 2:
case ch <- 3:
default:
log.Print("避免deaklock")
}
A := <-ch
log.Print(A) //1、2、3 隨機出現
- sync.Mutex 加鎖,避免併發競爭問題
競爭檢測分析go run -race ./main.go
加入互斥鎖
var mux sync.Mutex
mux.Lock()
defer mux.Unlock()
唯讀鎖定
var rwMux sync.RWMutex
rwMux.Lock()
defer rwMux.Unlock()
rwMux.RLock() //唯讀鎖定
defer rwMux.RUnlock()
- sync.WaitGroup,等待所有任務完成,可避免併發尚未執行完畢,主程式已結束
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
....
}()
wg.Wait()
- reflect 反射
- Unit Test 單元測試
go test 指令會執行所有檔名後綴為「_test.go」檔案內的函式go test
go test -v
go test -v run 指定要執行的函式
「_test.go」檔案內的函式須為Test開頭
例如:func TestAdd(t *testing.T) { /* ... */ } //後面接英文,須大寫
func Test_add(t *testing.T) { /* ... */ }
範例:
add.go 專案程式檔案package main
import (
"fmt"
)
func add(a, b int) int {
return a + b
}
func main() {
fmt.Println(add(1, 2))
}
add_test.go 單元測試檔案package main
import "testing"
func TestAddA(t *testing.T) {
if add(1, 2) != 3 {
t.Error("1+2 err")
}
}
func TestAddB(t *testing.T) {
if add(20, 30) != 50 {
t.Error("20+30 err")
}
}
執行單元測試>go test
PASS
ok hello/UnitTest 0.216s
>go test -v
=== RUN TestAddA
--- PASS: TestAddA (0.00s)
=== RUN TestAddB
--- PASS: TestAddB (0.00s)
PASS
ok hello/UnitTest 0.203s
>go test -v -run TestAddB
=== RUN TestAddB
--- PASS: TestAddB (0.00s)
PASS
ok hello/UnitTest 0.214s