- 型別轉換,只支持顯示轉換
[須為互相兼容的型別資料]
int(兼容的型別資料)、uint8(兼容的型別資料)、float64(兼容的型別資料).....
[數字、字串間的轉換,可以用 strconv 套件]//數字轉字串 Itoa var i int = 100 var str1 string = strconv.Itoa(i) fmt.Printf("%T %v \n", str1, str1) //string 100 //字串轉數字Atoi var str2 string = "123" ii, err := strconv.Atoi(str2) if err != nil { // 發生錯誤 fmt.Printf("%s\n", err.Error()) } fmt.Printf("%T %v \n", ii, ii) //int 123
- 空白標識符、匿名佔位符(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)實現。
沒有繼承,用嵌入組合、介面解決,避免繼承產生的維護難度(多用合成/聚合,少用繼承)。//猶如名稱為 Abc 的 class type Abc struct { X int } //猶如 Abc class 中有一個 SetX 方法 func (a *Abc) SetX(v int) { a.X = v } func main() { var tt = &Abc{10} fmt.Println(tt) //&{10} tt.SetX(20) fmt.Println(tt) //&{20} }
- 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
- new():根據傳入的資料類型,分配一個記憶體空間,回傳此指標。被分配的空間會清空。
make():初始化 slice、map、channelpackage main import "fmt" func main() { //new() 返回的是指针 p1 := new(int) fmt.Printf("p1=%#v\n", p1) //p1=(*int)(0xc0000ac058) fmt.Printf("*p1=%#v\n", *p1) //*p1=0 //等同new(int) var p2 *int i := 0 p2 = &i fmt.Printf("p2=%#v\n", p2) //p2=(*int)(0xc0000ac080) fmt.Printf("*p2=%#v\n", *p2) //*p2=0 type Student struct { Name string Age int } //new() 返回的是指针 x1 := new(Student) fmt.Printf("x1=%#v\n", x1) //x1=&main.Student{Name:"", Age:0} //等同new(Student) x2 := &Student{} //等同new(Student) fmt.Printf("x2=%#v\n", x2) //x2=&main.Student{Name:"", Age:0} //make使用於slice、map、channel,返回的不是指標 y := make([]Student, 2) fmt.Printf("y=%#v\n", y) //y=[]main.Student{main.Student{Name:"", Age:0}, main.Student{Name:"", Age:0}} }
參考:
- https://www.mdeditor.tw/pl/2dWs/zh-tw
一看就懂系列之Golang的goroutine和通道_Go語言中文網 - MdEditor - https://ithelp.ithome.com.tw/articles/10218923
Channel, goroutine之間的溝通橋樑 - iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天 - https://ithelp.ithome.com.tw/articles/10223617
day26 - 同步 - iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天 - https://wizardforcel.gitbooks.io/gopl-zh/content/ch11/ch11-02.html
Go 语言圣经 中文版 11.2. 測試函數 - https://studygolang.com/articles/25143
Golang中make和new初始化对象的区别 - Go语言中文网 - Golang中文社区 - https://draveness.me/golang/docs/part2-foundation/ch05-keyword/golang-make-and-new/
Go 语言中的 make 和 new - https://wangdaming.gitbooks.io/golang/content/
Introduction | GoLang
沒有留言:
張貼留言