# go 學習筆記 併發 https://blogtitle.github.io/go-advanced-concurrency-patterns-part-1/ https://openhome.cc/Gossip/Go/HelloWorld.html https://learnku.com/users/1 https://ithelp.ithome.com.tw/articles/10214513 Golang 是編譯語言,但和其他編譯語言比起來,Golang 的效能沒有很好。此外,Golang 內建垃圾回收機制,我們無法移除 Golang 的垃圾回收器, Go在Array的賦值和傳參數都是value type,靠複製整個Array的 修養之道 https://www.yuque.com/aceld/golang/ssesoe ## slince 切片複製問題 限制切片容量 https://juejin.cn/post/7064938788824285215 ## str https://juejin.cn/post/7032302353407705119 ## lock mutex解釋 https://zhuanlan.zhihu.com/p/632908377 ## GMP https://juejin.cn/post/6944925506340913188 ## string https://juejin.cn/post/7287783890512003109 ## func https://juejin.cn/post/7287773353309487159 ## type func 如何使用type func的東西 https://www.youtube.com/watch?v=pR5nQ6N6-YA ## channel vs mutex 两个方案,一个是显式地将消息从一个线程传给另一个线程(goroutine+channel),另一个是多线程加锁共同访问同一个变量,从 Golang 语言支持的角度,我们都可以实现,那么到底哪个性能更好?分别适合什么场合呢? https://juejin.cn/post/7125215236512022565 ## string ## mod ![](https://i.imgur.com/SJAWwQ3.png) 可以看版本 ![](https://i.imgur.com/PALU3Xa.png) 可以看誰依賴這個間接依賴的 修改版本 ![](https://i.imgur.com/BIZdSqN.png) ## ip ![](https://i.imgur.com/B8N2Wgw.png) ## go判断实现接口的方法 https://loesspie.com/2021/11/09/go-assert-type-implement-interface/ ![](https://i.imgur.com/RuGnXLm.png) ## makefile ![](https://i.imgur.com/RGSgjvK.png) 可以組合 可以寫一堆參數 簡化用 ## 別名 ![](https://i.imgur.com/0sv9dIw.png) ![](https://i.imgur.com/odRQ1Hf.png) ## 零值 https://zhuanlan.zhihu.com/p/458162403 ## 規範 https://ithelp.ithome.com.tw/articles/10220811 ## clean code https://datapool.tw/2022/09/13/%e8%89%af%e5%a5%bd%e7%9a%84%e4%b9%be%e6%b7%a8%e7%9a%84-golang-%e7%b7%a8%e7%a2%bc-clean-go-code2/ ## GOPATH vs GOROOT GOROOT設定路徑就是你當初安裝Golang語言的路徑,而GOPATH通常預設會是使用者目錄下的go資料夾 https://medium.com/%E4%BC%81%E9%B5%9D%E4%B9%9F%E6%87%82%E7%A8%8B%E5%BC%8F%E8%A8%AD%E8%A8%88/golang-goroot-gopath-go-modules-%E4%B8%89%E8%80%85%E7%9A%84%E9%97%9C%E4%BF%82%E4%BB%8B%E7%B4%B9-d17481d7a655 ## bin ![](https://i.imgur.com/lEN2lRT.png) 一般加上這幾個 export的話是暫時 ## error ## 用sync.Pool優化內存 https://pandaychen.github.io/2020/03/11/GOLANG-SYNC-POOL-USAGE/ ## roject Layout https://www.youtube.com/watch?v=jApleGS2hQY&list=PLKhlVcCW5bA2WGUs4Y7cRhQ8SSBAGVOTR&index=35 ## 基礎 所有go都屬於一個package 所以你開頭要定義它屬於誰 go的起始點在名稱叫main 的package 一般都會把main的main fnc當作起始點 就ex index一樣 為了方便,通常會設定 GOPATH,例如,指向目前的工作目錄: set GOPATH=c:\workspace\go-exercise 如果沒有設定 GOPATH 的話,Go 預設會是使用者目錄的 go 目錄,雖然目前 GOPATH 中只一個目錄,不過 GOPATH 中可以設定數個目錄,現在我的 go-exercise 目錄底下會有這些東西: ``` go-exercise └─src ├─hello │ hello.go │ └─main main.go ``` 接著在 go 目錄中執行指令 go run src/main/main.go 的話,你就會看到 Hello, World 了。 ## go path https://myapollo.com.tw/zh-tw/golang-hello-world-gopath/ 要有三個資料夾 bin pkg src ## go vscode 額外的要裝 >go ins 全選 ## nil https://ithelp.ithome.com.tw/articles/10272542?sc=rss.iron ## package https://github.com/jincheng9/go-tutorial/tree/main/workspace/lesson27 規則 https://opensourcedoc.com/golang-programming/package/ 模块里名字第一个字符大写可以被其他模块引用,而如果首字符是小写则不行 ## go build go build 如果想編譯原始碼為可執行檔,那麼可以使用 go build,例如,直接在 go 目錄中執行 go build src/main/main.go,就會在執行指令的目錄下,產生一個名稱為 main.exe 的可執行檔,可執行檔的名稱是來自己指定的原始碼檔案主檔名,執行產生出來的可執行檔就會顯示 Hello, World。 你也可以建立一個 bin 目錄,然後執行 `go build -o bin/main.exe src/main/main.go`,這樣產生出來的可執行檔,就會被放在 bin 底下。 ## go install go install 每次使用 go build,都是從原始碼編譯為可執行檔,這比較沒有效率,如果想要編譯時更有效率一些,可以使用 go install,例如,在目前既有的目錄與原始碼架構之下,於 go 目錄中執行 go install hello 的話,你就會發現有以下的內容: ``` go-exercise ├─bin │ main.exe │ ├─pkg │ └─windows_amd64 │ hello.a │ └─src ├─hello │ hello.go │ └─main main.go ``` go install packageName 表示要安裝指定名稱的套件,如果是 main 套件,那麼會在 bin 中產生可執行檔,如果是公用套件,那麼會在 pkg 目錄的 $GOOS_$GOARCH 目錄中產生 .a 檔案,你可以使用 go env 來查看 Go 使用到的環境變數 檔案是編譯過後的套件,因此,你看到的 hello.a,就是 hello.go 編譯之後的結果,如果編譯時需要某個套件,而對應的 .a 檔案存在,且原始碼自上次編譯後未曾經過修改,那麼就會直接使用 .a 檔案,而不是從原始碼開始編譯起。 ## init https://www.youtube.com/watch?v=WXMUWMsRTjQ&list=PLKhlVcCW5bA2WGUs4Y7cRhQ8SSBAGVOTR&index=51 初始化的 如果要用別的package 的init ![](https://i.imgur.com/WUsLmJx.png) ## go Module path module簡單介紹 必看 https://www.youtube.com/watch?v=qnwEsXN4CGc https://www.youtube.com/watch?v=n1CvIb2-D8s ![](https://i.imgur.com/uSBsbzC.png) 放在跟main一樣地方 https://github.com/jincheng9/go-tutorial/tree/main/workspace/lesson27 一句话总结:import的是路径,访问用package名称。最佳实践就是让两者保持一致。 ![](https://i.imgur.com/l0tY1ex.png) ![](https://i.imgur.com/Yr5n9hv.png) ### init init初始化後再install init 要路徑 ![](https://i.imgur.com/N3Sqddh.png) ### go mod tity 自動找對應的依賴去丟到go.mod裡面 ### go mod 記錄你的套件 有點像 composer.json ### go.sum 有點像 composer.lock那個 紀錄下載的詳細 ### 更新版本 看pkg底下的版本 用 go -u url去換 pkg底下有座版本控制 ## 枚舉 枚舉如果再json解析那些 會有小問題 因為沒有對應的key value 會預設nil 就是第一個 要小心 ## 指標 (Pointer) https://github.com/jincheng9/go-tutorial/tree/main/workspace/senior/p3 https://stackoverflow.com/questions/23542989/pointers-vs-values-in-parameters-and-return-values point使用時機 ![Uploading file..._m4pv9eoaf]() https://pjchender.dev/golang/pointers/ 在 Go 裡面,指標比較簡化,不僅沒有指標運算,也不需要手動控制記憶體釋放。 指標有兩種含義,一是作為資料類型,二是作為實體。前者如字元指標、浮點指標等等;後者如指標物件、指標變數等。 指標作為資料類型,可以從一個函式類型、一個物件類型或者一個不完備類型中匯出。從中匯出的資料類型稱之為**被參照類型**(referenced type)。指標類型描述了一類的物件,物件值為對被參照類型的實體的參照。[1] C++標準中規定,「指標」概念不適用於成員指標(不包含指向靜態成員的指標)。[2]C++標準規定,指標分為兩類:[3] * object pointer type:指向void或物件類型,表示物件在記憶體中的位元組位址或空指標。 * function pointer type:指代一個函式 在高階語言中,指標有效的取代了在低階語言(如組合語言與機器碼)直接使用記憶體位址。但它可能只適用於合法位址之中。因為指標更貼近硬體,編譯器能夠很容易的將指標翻譯為機器碼,這使指標操作時的負擔較少,因此能夠提高程式的運作速度。 使用指標能夠簡化許多資料結構的實作,例如在遍歷字串,查取表格,控制表格及樹狀結構上。對指標進行複製,之後再解參照指標以取出資料,無論在時間或空間上,都比直接複製及存取資料本身來的經濟快速。指標表示法較為直覺,使程式的表達更為簡潔,同時也能夠提供動態機制來建立新的節點。 ### 宣告 一个指针变量指向了一个值的内存地址。 类似于变量和常量,在使用指针前你需要声明指针。指针声明格式如下: `var var_name *var-ty`pe var-type 为指针类型,var_name 为指针变量名,* 号用于指定变量是作为一个指针。以下是有效的指针声明: ``` var ip *int /* 指向整型*/ var fp *float32 /* 指向浮点型 * ``` ### TL;DR 會需要「mutate」原本資料的 methods 就需要傳入的是 pointer 單純是「顯示」原本資料用的 methods 就不需要傳入 pointer ``` // *T 是一種型別,指的是能夠指向該 T 的值的指標,它的 zero value 是 nil // *T means pointer to value of type T var p *int // nil // &variable 會產生該 variable 的 pointer i := 42 p := &i // & 稱作 address of pointer fmt.Println(p) // 0xc0000b4008 fmt.Println(*p) // 透過 pointer 來讀取到 i ``` ``` // 當 function receiver 這裡使用了 *type 時 // 這裡拿到的 p 會變成 pointer,指的是存放 p 的記憶體位址 func (p *person) updateNameFromPointer(newFirstName string) { // *variable 表示把該指摽對應的值取出 (*p).firstName = newFirstName } // 當沒有使用 *type 時 // 每次傳進來的 p 都會是複製一份新的(by value) func (p person) updateName(newFirstName string) { p.firstName = newFirstName } func main() { jim := { firstName: "Jim" } jim.updateNameFromPointer("Aaron") // It works as expected jim.updateName("Aaron") // It doesn't work as expected } ``` ### why use `指標(Pointer)是用來存放記憶體位置(Memory Address)`。 Go 是一個 **pass by value** 的程式語言,也就是每當我們把值放入函式中時,Go 會把這個值完整的複製一份,並放到新的記憶體位址 可看文章 上面的 ### 指標運算子(Pointers Operation)​ & (ampersand) 和 *(Asterisk) 的使用 ``` func main() { // var p *int // nil i, j := 42, 2701 p := &i // point to i fmt.Println(p) // 0xc0000b4008 fmt.Println(*p) // 透過 pointer 來讀取到 i *p = 21 // 透過 pointer 來設定 i 的值 fmt.Println(i) // 21 p = &j // 將 p 的值設為 j 的 pointer *p = *p / 37 // 透過 pointer 來除 j fmt.Println(j) // 73 } ``` 當我們使用 &variable 時,會回傳該變數 value 的 address,表示給我這個變數值的「記憶體位置」。 當我們使用 *pointer 時,會回傳該 address 的 value,表示給我這個記憶體位置指稱到的「值」。 ⚠️ 但若 * 是放在 type 前面,那個這麼 * 並不是運算子,而是對於該 type 的描述(type description)。因此在 func 中使用的 *type 是指要做事的對象是指稱到該型別(type)的指標(pointer),也就是這個 function 只能被 person 的指標(pointer to a person)給呼叫。 ![](https://i.imgur.com/jolS3NQ.png) **縮寫的使用** 當我們在 function receiver 中使用 *type 後,這個函式將會自動把帶入的參數變成 pointer of the type: ![](https://i.imgur.com/8BfVYS9.png) 因此,原本的程式碼可以不用先把它變成 pointer(可省略 jimPointer := &jim),直接縮寫成: ``` // 因為這裡有指稱要帶入的是 *person func (p *person) updateName(newFirstName string) { (*p).firstName = newFirstName } func main() { jim := person{ firstName: "Jim", lastName: "Party", } // 原本是這樣 // jimPointer := &jim // jimPointer.updateName("Aaron") // 所以,可以縮寫成,該 function 會自動去取 jim 的指標(記憶體位址) jim.updateName("Aaron") jim.print() // Current person is: {firstName:Aaron lastName:Party} } ``` ### 指標是 Reference Types 的變數 實際上指標(Pointer)本身和 Slice 一樣,都是屬於 Reference Types 的變數。從下面的例子中可以看到: 使用 &name 取出 name 的 pointer 後,不論是 main 或 printPointer 裡面的 namePointer 都指稱到同一個記憶體位置 但由於 Go 本質上仍然是 Pass by Value,因此在 main 中的 &namePointer 會和 printPointer 的 &namePointer 指向到不同的記憶體位址 也就是說,當我把 Pointer 丟到函式的參數中時,實際上這個 Pointer 也被複製了一份新的(原本的 Pointer 的位址是 0xc0000ae018,複製到函式後是 0xc0000ae028,但這兩個記憶體位址,實際上都對應回 0xc00008e1e0。 ``` func main() { name := "bill" namePointer := &name fmt.Println("1", namePointer) // 0xc00008e1e0 fmt.Println("2", &namePointer) // 0xc0000ae018 printPointer(namePointer) } func printPointer(namePointer *string) { fmt.Println("3", namePointer) // 0xc00008e1e0 fmt.Println("4", &namePointer) // 0xc0000ae028 } ``` ### new(T)​ new 是 golang 中內建的函式,使用 new(T) 分配這個 Type 所需的記憶體,**並回傳一個可以指稱到它的 pointer**,概念上和 : = T{} 差不多: ## new vs make https://www.yuque.com/aceld/golang/xu2t51 https://github.com/jincheng9/go-tutorial/tree/main/workspace/senior/p4 ![](https://i.imgur.com/ZK6jMM0.png) new 比較是new strut這樣 然後你跑他的function不會有錯誤 我覺得可以想成oop 的NEW make 是初始化一個東西 int, array channel之類的 ## 分號 首先,在一行中,寻找成对的符号,比如一对字符串的引号.一对圆括号,一对大括号 上述任务完成后,在一行中没有其他成对的标示,然后就在行尾追加分号; 所以一行一个语句,结尾不用加分号. ## Error Handling ## _ https://stackoverflow.com/questions/27764421/what-is-underscore-comma-in-a-go-declaration 簡單來說 需要兩個變量 但你只需要一個 另外一個不重要就用_ 去放 因為變數宣告一定要用 ![](https://i.imgur.com/a8KBssY.png) ## 變數 ![](https://i.imgur.com/e4f3Agf.png) ![](https://i.imgur.com/dYKXghQ.png) ![](https://i.imgur.com/NLUsKVU.png) ![](https://i.imgur.com/gH1ZC6v.png) 補充: 正常來說,不能對已經宣告過的變數,再使用短變數宣告賦值,除非短變數左側有多重變數,且其中有一個或以上是之前沒有的新變數,那就可以對已經宣告過的變數,再使用短變數宣告賦值呦! ![](https://i.imgur.com/TEokZSS.png) ### scope block -> function內 package -> 同一個資料可以共用(但如果是不同資料夾 要用 請用要用 請大寫) ### 顯性跟隱性 就是 eX var member = 260 這樣 沒有type 讓他去猜測 ``` / Bool类型 var my_bool bool = true // 字符串类型 var my_string string = "hello, world!" // int: 有符号整形,根据系统架构自动判断是int8,int16,int32还是int64 // 比如当前系统是64位系统,则为int64 var my_int_min int = -9223372036854775808 var my_int_max int = 9223372036854775807 // int8: 有符号 8 位整型 (-128 到 127) var my_int8_min int8 = -128 var my_int8_max int8 = 127 // int16: 有符号 16 位整型 (-32768 到 32767) var my_int16_min int16 = -32768 var my_int16_max int16 = 32767 // int32: 有符号 32 位整型 (-2147483648 到 2147483647) var my_int32_min int32 = -2147483648 var my_int32_max int32 = 2147483647 // int64: 有符号 64 位整型 (-9223372036854775808 到 9223372036854775807) var my_int64_min int64 = -9223372036854775808 var my_int64_max int64 = 9223372036854775807 // uint: 无符号整形,根据系统架构自动判断是uint8,uint16,uint32还是uint64 // 比如当前系统是64位系统,则为uint64 var my_uint_min uint = 0 var my_uint_max uint = 18446744073709551615 // uint8: 无符号 8 位整型 (0 到 255) var my_uint8_min uint8 = 0 var my_uint8_max uint8 = 255 // uint16: 无符号 16 位整型 (0 到 65535) var my_uint16_min uint16 = 0 var my_uint16_max uint16 = 65535 // uint32: 无符号 32 位整型 (0 到 4294967295) var my_uint32_min uint32 = 0 var my_uint32_max uint32 = 4294967295 // uint64: 无符号 64 位整型 (0 到 18446744073709551615) var my_uint64_min uint64 = 0 var my_uint64_max uint64 = 18446744073709551615 // uintptr: 无符号整型,用于存放一个指针,可以足够保存指针的值的范围。和uint范围相同,根据系统架构自动判断 var my_uintptr_min uintptr = 0 var my_uintptr_max uintptr = 18446744073709551615 // byte: uint8的别名 var my_byte_min byte = 0 var my_byte_max byte = 255 // rune: int32的别名。代表1个unicode码 var my_rune_min rune = -2147483648 var my_rune_max rune = 2147483647 // float32: 单精度浮点数,在C语言里等同于float // float64: 双精读浮点数,在C语言里等同于double // 如果不写类型,则为float64(暂时不知道这个是根据系统架构判断还是默认就是float64) // 从结果可以看出: // float32只能容纳8位数字(包括小数点前后数字,不包括小数点,超过8位会将四舍五入保留8位) // float64可以容纳比较多的数字(具体暂时还没测),而且这种双精度我也一直没搞懂,很复杂 // 当符合要求时候会自动用科学计数法来表示,要注意 var my_float32 float32 = 10086.141592653 var my_float64 float64 = 10086.141592653 var my_float_auto = 10086.141592653 ``` ### int https://openhome.cc/Gossip/Go/PreDeclaredType.html 有正反 還有不同int ![](https://i.imgur.com/IrBWWQU.png) 無號與有號整數,名稱分別為 uint 與 int int 長度會與 uint 相同,而 uint 長度視平台實作而異,可能是 32 位元或是 64 位元。 ``` uint8 : 0 ~ 255 uint16 : 0 ~ 65535 uint32 : 0 ~ 4294967295 uint64 : 0 ~ 18446744073709551615 int8 : -128 ~ 127 int16 : -32768 ~ 32767 int32 : -2147483648 ~ 2147483647 int64 : -9223372036854775808 ~ 9223372036854775807 ``` ## 宣告 ### short declaration​ 使用 := 宣告,表示之前沒有進行宣告過。這是在 go 中最常使用的變數宣告的方式,因為它很簡潔。但因為在 package scope 的變數都是以 keyword 作為開頭,因此不能使用縮寫的方式定義變數(foo := bar),只能在 function 中使用,具有區域性(local variable): ``` // 第一種宣告方式 function main() { foo := "Hello" bar := 100 // 也可以簡寫成 foo, bar := "Hello", 100 } // 等同於 function main() { var foo string foo = "Hello" } ``` ### variable declaration​ ![](https://i.imgur.com/D4iZY3A.png) 使用時機主要是: 當你不知道變數的起始值 需要在 package scope 宣告變數 當為了程式的閱讀性,將變數組織在一起時 ⚠️ 留意:在 package scope 宣告的變數會一直保存在記憶體中,直到程式結束才被釋放,因此應該減少在 package scopes 宣告變數 ``` // 第二種宣告方式,在 main 外面宣告(全域變數),並在 main 內賦值 var foo string var bar int // 可以簡寫成 var ( foo string bar int ) function main() { foo = "Hello" bar = 100 } ⚠️ 不建議把變數宣告在全域環境 如果變數型別一樣的話,也可以簡寫成這樣: func main() { var c, python, java bool fmt.Println(c, python, java) } ``` ### 第三種宣告方式​ 直接宣告並賦值: ``` // 第三種宣告方式,直接賦值 var ( foo string = "Hello" bar int = 100 ) ``` ### 三種方式是一樣的​ 下面這兩種寫法是完全一樣的: ``` var <name> <type> = <value> var <name> := <value> // var card string = "Ace of Spades" card := "Ace of Spades" // var pi float = 3.14 pi := 3.14 ``` 只有在宣告變數的時候可以使用 := 的寫法,如果要重新賦值的話只需要使用 `=。` ### 注意 正確:我們可以在 main 函式外宣告變數,但無法在 main 函式外賦值​ ``` // 正確:我們可以在 main 函式外宣告變數,但無法在 main 函式外賦值 package main import "fmt" var deckSize int func main() { deckSize = 50 fmt.Println(deckSize) } ``` 錯誤:無法在 main 函式外賦值​ ``` // 錯誤:但無法在 main 函式外賦值 package main import "fmt" // syntax error: non-declaration statement outside function body deckSize := 20 func main() { fmt.Println(deckSize) } ``` ## switch 不需要break 有隱式指定 ## 變數 ![](https://i.imgur.com/Ap2LHFW.png) ### 越界繞回 ![](https://i.imgur.com/fbGygbI.png) ![](https://i.imgur.com/rdySvQs.png) ### const 比較重要在iota 會自動計算 value 有點像eumn ![](https://i.imgur.com/DAkYePZ.png) 當然可以不用從0開始 ![](https://i.imgur.com/cMPLlrO.png) ### 宣告地雷 ![](https://i.imgur.com/abhYjtO.png) ![](https://i.imgur.com/iZFvaYP.png) https://github.com/jincheng9/go-tutorial/tree/main/workspace/senior/p26 ### var 用 := 或 var 所宣告的會是變數,若需要宣告常數,需要使用 st 可用表達式賦值 ![](https://i.imgur.com/eOEFink.png) 注意喔tyep都會有預設 只有宣告就會有預設 ex uint 是 0 bool 是 false ![](https://i.imgur.com/SvejpQp.png) ### array 如果圖片宣告int 改成 String 他不會出來 ![](https://i.imgur.com/ljrSk5f.png) 也可以預設 ![](https://i.imgur.com/7ZzRHQY.png) ## 單雙引號 go语法中,双引号是常用的来表达字符串 单引号只能包含一个字符,例如’b’ ,程序会输出98表示字符b的ascii码。 ## utf-8 Go 的創建者之一也是 UTF-8 的創建者,因此,Go 可以直接處理多國語言,只要你確定編輯器編碼為 UTF-8 就可以了,如果你使用 vim,可以在 vim 的命令模式下輸入 :set encoding=utf-8,或者是在 .vimrc 之中增加一行 set encoding=utf-8。 ## package ``` package main import "hello" func main() { hello.HelloWorld() } ``` 現在顯然地,main.go 中要用到方才建立的 hello 套件中的 HelloWorld 函式,這時 package 的設定就會發揮一下效用,你得將 hello.go 移到 src/hello 目錄之中,也就是目錄名稱必須符合 package 設定之名稱。 同樣地,你可以將 main.go 移到 src/main 目錄之中,以符合 package 的設定。 而 src 的位置,必須是在 GOROOT 或者是 GOPATH 的路徑中可以找到,當 Go 需要某套件中的元素時,會分別到這兩個環境變數的目錄之中,查看 src 中是否有相應於套件的原始碼存在。 為了方便,通常會設定 GOPATH,例如,指向目前的工作目錄: `set GOPATH=c:\workspace\go-exercise` 如果沒有設定 GOPATH 的話,Go 預設會是使用者目錄的 go 目錄,雖然目前 GOPATH 中只一個目錄,不過 GOPATH 中可以設定數個目錄,現在我的 go-exercise 目錄底下會有這些東西: ``` go-exercise └─src ├─hello │ hello.go │ └─main main.go ``` 接著在 go 目錄中執行指令 go run src/main/main.go 的話,你就會看到 Hello, World 了。 ## go cmd https://github.com/jincheng9/go-tutorial/tree/main/workspace/std/02 ## 四則運算 同TYPE才能運算 就算你知道 1+ 1.5 = 2.5 也沒用 一定要同型態 還有你4/3 小數點會刪掉一樣 那如果不同型態怎辦 真的要用 可以()轉(int) (float)之類的 ![](https://i.imgur.com/pNDivAE.png) 如果需要算數 可引入 math 像別的語言一樣 string比較會判斷裡面全部大小寫 ![](https://i.imgur.com/QxHp97f.png) 浮點數運算可能會產生誤差,故我們在比較浮點數的運算結果時,不會直接用相等 == 來比較,而會確認運算結果的誤差在許可範圍內。我們使用 math 套件的 Abs 函式取得誤差的絕對值 (absolute value),以消除正負號所帶來的誤判。 ## 数值类型的自动推导 https://github.com/jincheng9/go-tutorial/tree/main/workspace/senior/p27 ### 二元運算子 二元運算子 (Bitwise Operators) 二元運算子也是代數運算子。但二元運算的概念和一般的代數運算有一些差異,故我們將其分開。以下是二元運算子: ``` &:bitwise AND |:bitwise OR ^:bitwise XOR &^:bit clear <<:左移 (left shift) >>:右移 (right shift) ``` ### 接收運算子 (Receive Operator) 接收運算子有以下符號: `<-` 接收運算子用在通道。基礎的財經運算用不到共時性程式,故不會用到接收運算子。 ## fnc ,package 中定義的函式,名稱必須是以大寫開頭,其他套件外的程式,才能進行呼叫,若函式名稱是小寫,那麼會是套件中才可以使用的函式。 https://www.youtube.com/watch?v=w2tauAykFZM&list=PLujhHB_uAFJws6Vv5q1KDoaQ4YcpS9UOm&index=5 閉包 內累加算 變成func ![](https://i.imgur.com/PgiOoUi.png) ## interface 類似python 使用 duck typing的方式 如果都實現interface 裡面的規定func 那他一定屬於這個interface :Go 1.18(2022 年第一季度)確實重命名interface{}為any(別名為interface{})。 https://blog.kennycoder.io/2020/02/03/Golang-%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3interface%E5%B8%B8%E8%A6%8B%E7%94%A8%E6%B3%95/ ### 使用時機 ![](https://i.imgur.com/4P8QctP.png) https://github.com/jincheng9/go-tutorial/blob/main/workspace/senior/p28/15-go-interface-pollution.md ### 面相對象思維 https://www.yuque.com/aceld/golang/uh0124 ## type switch ![](https://i.imgur.com/7h8r3R8.png) 同一個interface去實現 但每個鴨子可能執行的動作不同 才會有type switch ### return ![](https://i.imgur.com/BtSr9yz.png) 返回类型必须指定 如果声明的多个参数类型是相同的,可以只写最后一个参数类型,比如上面的x int, y int也可以写成x, y int 入参和出参的变量不能重复声明 ![](https://i.imgur.com/Eho4YaY.png) ![](https://i.imgur.com/rGYmrjN.png) ## if ![](https://i.imgur.com/wkegyQA.png) if可以初始化跟 c++一樣 ## for and range ### range Go 语言中 range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对。 不同種類的range表達式結果值,for語句的變數,數量可以不一樣。 number1是一個array,那變數就可以有兩個,第一個變數是索引值(i),第二個變數是元素值(e)。 ``` numbers1 := [...]int{1, 2, 3, 4, 5, 6} for i := range numbers1 ``` 在這個狀況下,如果只寫一個變數,得到的就會是索引值(i)。 **slice為引用類型,array為值類型** * 值類型:變數直接儲存值。(同屬於值類型的包含,基礎數據類型、結構體類型。) * 引用類型:變數儲存的是一個地址,而這個地址儲存最終的值。(同屬於引用類型的包含,字典、通道、函數類型。) **注意注意** 使用迭代器時,對陣列元素的修改是沒有效果的 修改陣列中的元素,要以索引走訪陣列,再直接修改陣列的元素的值 雖然range可以抓取每個value 但 不能更動 要改動value 只能用 array[index]這樣去改 ### for ![](https://i.imgur.com/TLGMmh8.png) for 可以用自訂條件 ![](https://i.imgur.com/cFycIjF.png) **goto 是必然之惡** goto 是終極的控制結構,因為可以使用 goto 任意移動到同函式中其他位置。像是以下範例用 goto 模擬 break: ``` package main import "fmt" func main() { i := 1 for i <= 10 { if i > 5 { goto END } fmt.Println(i) i++ } END: } ``` 在這個例子中,當 i 大於 5 時,觸發 goto 敘述,跳到 END 標籤所在的位置。整體的效果如同 break。 ### goto 以下例子則用 goto 模擬 continue: ``` package main import "fmt" func main() { i := 1 LOOP: for i <= 10 { if i%2 == 0 { i++ goto LOOP } fmt.Println(i) i++ } } ``` 雖然有些程式人視 goto 為邪惡的語法特性,甚至有些程式語言直接封印 goto。但適當地使用 goto,會讓程式碼更簡潔。Go 原始碼中也有用到 goto,像是 gamma.go 中的片段: ``` for x < 0 { if x > -1e-09 { goto small } z = z / x x = x + 1 } for x < 2 { if x < 1e-09 { goto small } z = z / x x = x + 1 } if x == 2 { return z } x = x - 2 p = (((((x*_gamP[0]+_gamP[1])*x+_gamP[2])*x+_gamP[3])*x+_gamP[4])*x+_gamP[5])*x + _gamP[6] q = ((((((x*_gamQ[0]+_gamQ[1])*x+_gamQ[2])*x+_gamQ[3])*x+_gamQ[4])*x+_gamQ[5])*x+_gamQ[6])*x + _gamQ[7] return z * p / q small: if x == 0 { return Inf(1) } return z / ((1 + Euler*x) * x) ``` **多層break再用這個** ### label ![](https://i.imgur.com/AqTfZRG.png) 可以選擇跳出哪一個loop ## defer for 用defer要注意 ![](https://hackmd.io/_uploads/S18T6FUWp.png) ![](https://hackmd.io/_uploads/S1eRaYL-6.png) https://www.yuque.com/aceld/golang/qnubsg defer 最大的功能是 panic 后依然有效 所以defer可以保证你的一些资源一定会被关闭,从而避免一些异常出现的问题 用 defer 優雅地處理系統資源 C 程式可用 goto 來處理系統資源的釋放。但在 Go 語言中,可用 defer 取代 goto 來處理系統資源的釋放。參考以下 Go 程式片段: ``` f, err := os.Create("file.txt") if err != nil { log.Fatal(err) } defer f.Close() ``` 在本片段中,我們建立一個檔案物件 f,之後用 defer 來觸發關閉 f 物件的指令。defer 敘述會自動延遲到 defer 所在的函式結束時才觸發指令,不需要手動控制程式流程,所以會比直接用 goto 敘述簡單得多。 defer 讓function 等到main最後才執行 https://www.cnblogs.com/failymao/p/15097197.html defer要注意 ![](https://i.imgur.com/SELXqIS.png) ![](https://i.imgur.com/Lxf7sFl.png) ![](https://i.imgur.com/k30vEFx.png) ## recover 因為panic會讓中斷以外會讓程式結束,所以為了避免程式直接結束會用recover來讓程式繼續執行,並輸出error log ## array ![](https://i.imgur.com/6s2uBiC.png) 重點一 acopy做改動不會影響到a ,跟js其他不一定變數是直接copy 不是傳址 ## 切片 (Slice) 它的底层的内存是连续分配的 https://www.youtube.com/watch?v=fF68HELl78E 當參數會改變原本的 array不會 https://kitecloud-backend.coderbridge.io/2020/08/15/golang-slice-%E4%BD%9C%E7%82%BA%E5%8F%83%E6%95%B8%E5%82%B3%E9%81%9E%E6%99%82%E7%9A%84%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A0%85 對於資料量很多的情境下, 如果很頻繁的插入或是刪除, List的成本低到幾乎可以不計算. 但如果頻繁的新增或是走訪查找, Slice的效能高過List許多. https://opensourcedoc.com/golang-programming/array-slice/ 由於陣列長度在建立後就不能更動,Go 提供切片 (slice) 這種容器。切片和陣列相似,同樣也是線性的、以數字為索引,索引值同樣從 0 開始。 建立切片 以下例子建立一個切片: ``` package main import "fmt" func main() { langs := []string{"Go", "Python", "Ruby", "PHP"} for _, e := range langs { fmt.Println(e) } } ``` 建立切片時,不需預設其長度,因切片會動態改變其長度。 如果你不是切片 要寫長度 上面有連結 切片也可以由已有的陣列來建立,如下例: ``` package main import ( "fmt" "log" "reflect" ) func main() { langs := [4]string{"Go", "Python", "Ruby", "PHP"} slice := langs[0:4] // Upper bound excluded. // Print out the types of these variables fmt.Println(reflect.TypeOf(langs)) fmt.Println(reflect.TypeOf(slice)) if !(langs[3] == "PHP") { log.Fatal("Wrong value") } slice[3] = "Perl" if !(langs[3] == "Perl") { log.Fatal("Wrong value") } } ``` 切片的內部,其實也是陣列,切片本身不儲存值,而是儲存到陣列的參考 (reference),簡單地說,切片和陣列內部儲存同一份資料,但透過兩個不同的變數來處理;在我們的這個例子中,我們修改切片的值,原本陣列的值也一併修改了。 切片也可以在執行期動態產生,這時候會使用 make 做為關鍵字 切片也可以在執行期動態產生,這時候會使用 make 做為關鍵字。在以下例子中,我們動態產生一個長度為 5 的切片: ``` 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) } } ``` 改變切片大小 我們在前文中提過,切片長度可以動態改變,這時候會使用 append 函式。在以下例子中,切片的長度由 5 變成 8: ``` package main import ( "log" ) func main() { slice := []int{1, 2, 3, 4, 5} if !(len(slice) == 5) { log.Fatal("Wrong length") } slice = append(slice, 6, 7, 8) if !(len(slice) == 8) { log.Fatal("Wrong length") } } ``` [start , end] 可以抓 start到end之前 記住 是之前 ### inset i 位置 v value ![](https://i.imgur.com/Qc7OhmF.png) ### slice陷阱 https://ithelp.ithome.com.tw/articles/10203043 ![](https://i.imgur.com/S7gRO4k.png) ![](https://i.imgur.com/rCADRfl.png) 必看 https://blogtitle.github.io/go-slices-gotchas/ ### slice修改 https://juejin.cn/post/6844904177022271501 ### slice range陷阱 https://github.com/jincheng9/go-tutorial/tree/main/workspace/senior/p13 ## 映射 (Map) https://opensourcedoc.com/golang-programming/map/ 內容看文章 這邊只重點 map沒有對應的value 會看預設的型態 要注意的是,映射是無序的,我們多執行幾次,就會發現每次的順序都不一樣 如果要有序 要用到切片 value , ok = a['key'] map抓key可以return 兩個 第一是value第二是有沒有這value ![](https://i.imgur.com/VSDLUqB.png) ![](https://i.imgur.com/7kMpOxr.png) ![](https://i.imgur.com/PeegMCK.png) ## function Go 語言中的函式 (function) 函式 (function) 可以說是大部分程式語言的必備,我們將一段要執行程式碼放在函式裡面,需要用時呼叫函式。 在大部分其他的程式語言中,函式正常會回傳一個輸出值,但 Go 語言中函式可以完全 沒有輸入值和輸出值 ; 也可以 一次有多個輸入和輸出值 。 且在 Go 語言中的函式是 First-class Function ,也就是一個函式可以當作另一個函式的引數或是回傳值,而可以接收其他函數為引數的函式,也可以稱為 高階函式 (higher-order functions) 。 ### ... 稱為 打包算符(pack operator) 只能放在參數最後面 參數不定的函式,Go 語言會將其轉成切片 **只有切片可以使用解包算符,若想將陣列拆解,可以使用 陣列[:]... 語法將其轉成切片,傳入參數不定的函式。** ### 變數呼叫匿名function ![](https://i.imgur.com/fINETch.png) ### 閉包 ![](https://i.imgur.com/KW1MIVi.png) ![](https://i.imgur.com/ZXO1HIt.png) ## naked return ![](https://i.imgur.com/UUUOmba.png) 如果沒有在 return 敘述後面指定要傳回的變數, Go 語言便會自己將傳回值清單的變數傳回,這就是 naked returns ,也可以稱為 具名 return (named return) 。 ![](https://i.imgur.com/ZkaeqUH.png) *注意注意!!!!* 它可以幫你new 宣告struct ![](https://i.imgur.com/BKqF2DG.png) ## type https://ithelp.ithome.com.tw/articles/10303057 ### 自訂型別 (custom types) ![](https://i.imgur.com/JhB2Rmn.png) ### 別名 ![](https://i.imgur.com/6wWFK5B.png) ## 結構 (Struct) ![](https://i.imgur.com/1FMiyIV.png) 如果有tag 一定要大寫 才能export (去跑fmt之類的其實也會看有沒有string method 所以最好要都大寫比較好) 在先前的程式中,我們的變數都是儲存單一的值。如果想用變數表示較複雜的概念,像是在平面座標系中的某個點,先前的語法就不敷使用了。在 Go 語言,使用結構 (struct) 來表示複合的概念;此外,大部分的 Go 物件導向程式也會使用結構 ``` package main import ( "fmt" ) type Point struct { x float64 y float64 } func main() { p := Point{x: 3.0, y: 4.0} fmt.Println(p.x) fmt.Println(p.y) } ``` 我們通常會在宣告結構時一併定義新的型別,便於後續程式呼叫,使用 type 可以宣告新型別。在本例中,我們宣告 Point 型別,該型別有兩個屬性 (field),分別是 x 和 y,兩者皆是 float64 型別;接著,我們宣告一個變數 p 及賦值;最後,我們呼叫 p 的屬性 x 和 y。 也可以匿名 ``` package main import ( "fmt" ) func main() { p := struct { x float64 y float64 }{x: 3.0, y: 4.0} fmt.Println(p.x) fmt.Println(p.y) } ``` 用法 ``` package main import ( "fmt" ) type Gender int const ( Male Gender = iota Female ) type Person struct { name string gender Gender age uint } func main() { me := Person{gender: Male, age: 30, name: "Michelle Chen"} fmt.Println(me.name) fmt.Println(me.age) if me.gender == Male { fmt.Println("Male") } else { fmt.Println("Female") } } ``` 結構內也可以再加入另一個結構,如下例: ``` package main import ( "fmt" "math" ) type Point struct { x float64 y float64 } type Circle struct { r float64 p Point } func main() { c := Circle{r: 5.0, p: Point{x: 3.0, y: 4.0}} fmt.Println(c.p.x) fmt.Println(c.p.y) circumstance := 2 * math.Pi * c.r fmt.Println(circumstance) } ``` 一開始初始化之後 可以重新賦value ![](https://i.imgur.com/GElNAkv.png) ### embedded 嵌入 主要使用嵌入來推廣方法 ``` //having type Doer interface{ Do() } func DoWith(d Doer){} func (s sample1)Do(){} //implemented type Struct1 struct { sample1 } type Struct2 struct { Sample1 sample1 } var s1 Struct1 var s2 Struct2 //you can call DoWith(s1) //method promoted so interface satisfied //but cannot DoWith(s2) ``` #### embedded poninter vs ordinary If S contains an embedded field T, the method sets of S and *S both include promoted methods with receiver T. The method set of *S also includes promoted methods with receiver *T. If S contains an embedded field *T, the method sets of S and *S both include promoted methods with receiver T or *T. 小心會有race問題 ![](https://i.imgur.com/2V4gW8m.png) https://stackoverflow.com/questions/69870054/usage-of-pointers-in-nest-golang-structs #### 遷入可以用interface ``` Interface Fields (Nested interface) type Salaried interface { getSalary() int } type Salary struct { basic, insurance, allowance int } func (s Salary) getSalary() int { return s.basic + s.insurance + s.allowance } type Employee struct { firstName, lastName string salary Salaried // 只要 salary 實作了 Salaried,就可以 Salaried interface type } func main() { ross := Employee{ firstName: "Ross", lastName: "Geller", // 因為 Salary struct 已經實作了 Salaried,因此可以當作 salary 的欄位值 salary: Salary{ 1100, 50, 50, }, } fmt.Println("Ross's salary is", ross.salary.getSalary()) } ``` #### anonymously nested interface 當該 struct 的欄位沒有填寫時(anonymous fields),interface 中所定義的方法也可以被 promoted https://pjchender.dev/golang/structs/ ### 匿名結構(anonymous strut) https://github.com/jincheng9/go-tutorial/tree/main/workspace/senior/p5 ![](https://i.imgur.com/VduXOJs.png) 1. 顧名思義它 沒有名稱 1. 在宣告時可一併賦值 1. 只能有一個變數,無法再建立其他結構 ``` <結構變數名稱> := struct { <欄位 1> <型別> <欄位 2> <型別> ... <欄位 N> <型別> }{ <值 1>, <值 2>, ... <值 N>, } ``` 上方賦值我們使用沒有欄位名稱的賦值法,當然大家也可以選擇使用有欄位名稱的賦值法,只是按照慣例,在匿名結構中,我們通常習慣使用沒有欄位名稱的賦值法。 ### 內嵌結構 (embedding struct) ### struct option https://blog.kennycoder.io/2021/09/06/Golang-%E5%B8%B8%E8%A6%8B%E7%9A%84-option-%E8%A8%AD%E8%A8%88%E6%8E%A2%E8%A8%8E/ ### 空结构体 ## init函式 init 函式是個特殊的函示,如果一段程式碼內包含著 init 函式,則在執行整個程式碼時,會優先值些 init裡的程式碼,通常需要初始化一些外部資源時,會將這些程式碼寫在init 函式裡 ## 型別斷言 (type assertion) ![](https://i.imgur.com/Et50hhy.png) ![](https://i.imgur.com/TnZPRkB.png) ![](https://i.imgur.com/quE0Lev.png) ![](https://i.imgur.com/AI07bjn.png) ## 條件編譯 https://opensourcedoc.com/golang-programming/build-constrants/ ## 多執行續 Goroutine 當你有一個單一的目標可以分解成可以相互獨立操作的短任務時使用它們 ![](https://i.imgur.com/fyFYLFc.png) 基本觀念 https://blog.kennycoder.io/2020/05/16/%E9%80%B2%E7%A8%8B-Process-%E3%80%81%E7%B7%9A%E7%A8%8B-Thread-%E3%80%81%E5%8D%94%E7%A8%8B-Coroutine-%E7%9A%84%E6%A6%82%E5%BF%B5%E8%AC%9B%E8%A7%A3/ https://peterhpchen.github.io/2020/03/08/goroutine-and-channel.html ### goroutine 多個strut https://www.youtube.com/watch?v=36X8uf7AxOg&list=PLKhlVcCW5bA2WGUs4Y7cRhQ8SSBAGVOTR&index=39 要注意如果多個strut 因為用傳value的方式 多個loop 會互相影響到 要就在這個goroutine內的loop直接建立一個strut 不然就不要用傳址的方式 再用管道連起來 ### goruntine 管道 https://aceld.gitbooks.io/how-do-go/content/channel/chuan-lian-de-channel-pipeline.html https://go.dev/blog/pipelines ## 處理三種concurrency的方式 https://blog.wu-boy.com/2020/08/three-ways-to-manage-concurrency-in-go/ waitGroup ![](https://i.imgur.com/TST7XEf.png) channel ![](https://i.imgur.com/yyp1Rs3.png) content ![](https://i.imgur.com/C2lTDUg.png) 要一直往下通知 不可能一個channl通知下去 ## channel goruntine使用時機 https://blog.wu-boy.com/2020/01/when-to-use-go-channel-and-goroutine/ ## 多執行續 channel https://betterprogramming.pub/golang-how-to-implement-concurrency-with-goroutines-channels-2b78b8077984 https://blogtitle.github.io/go-advanced-concurrency-patterns-part-3-channels/ https://www.youtube.com/watch?v=YEKjSzIwAdA https://www.youtube.com/watch?v=VkGQFFl66X4&ab_channel=CodeWithRyan 講很好 不能先後 只能同時 所以要在不同的goroutiue ![](https://i.imgur.com/uwlfdG1.png) ![](https://i.imgur.com/V7uzb2K.png) channel是一進一出 所以如果你用for 塞進去 for 拉出來沒問題 但如果你其中一個用range就會dealock 原因是因為他不知道長度 要一直等待下一個 這時候就要close調 ![](https://i.imgur.com/Mod3F5f.png) 預設0 所以他一定要有1才能保證他不會阻塞 他是資料結構queue 所以<-chanel , <-channel 的時候會先出來先放進去的 https://opensourcedoc.com/golang-programming/concurrency/ 由於 CPU 的時脈已經到物理上限,現在的硬體都往多核心、多 CPU 發展。同樣地,單一的大型伺服器相當昂貴,而且擴充量有限,使用多台主機組成的叢集 (cluster) 則相對易於擴充。然而,若程式碼沒有使用共時性 (concurrency) 的特性來撰寫,則無法真正發揮平行處理 (parallel computing) 所帶來的效能提升。 Go 主要的特色之一,就在於其對共時性程式的支援;大部分程式語言以函式庫來支援共時性程式,但 Go 將其內建在語法中。Go 的並時性程式有兩種,一種是以 CSP (communicating sequential processes) 模型的並時性程式,一種是傳統的多執行緒 (multi-thread) 程式。由於 Go 將 CSP 模型內建在語法中,通常建議使用這些內建功能來寫共時性程式。 goroutine 類似於執行緒,但更輕量,一次啟動數百甚至數千個以上的 goroutine 也不會占用太多記憶體。要使用 goroutine,在函式前加上 go 關鍵字即可 如果讀者多執行幾次本程式,會發現每次印出字串的順序不同。並時性程式和傳統的循序式程式的思維不太一樣,執行並時性程式時無法保證程式運行的先後順序,需注意 <-是对chan类型来说的。chan类型类似于一个数组。 当<- chan 的时候是对chan中的数据读取; 相反 chan <- value 是对chan赋值。 由於通道在傳輸時,會阻塞 (blocking) 程式的行進,在此處,我們不需要另外設置 WaitGroup。 ![](https://i.imgur.com/YBmsEDW.png) ### channel底曾 https://juejin.cn/post/6844904016254599176 底层数据结构是一个双向链表。是一个叫做hchan的结构体,每个Channel都有一个send队列和一个receive队列,用于存放发送和接收操作的goroutine。当发送操作和接收操作发生时,它们会被添加到对应的队列中,等待对方的操作来满足条件。 ![](https://hackmd.io/_uploads/r1TcLAN-p.png) ### 控制goroutines数量 https://www.yuque.com/aceld/golang/lmnbx5#b6d5422c ### nil channel https://www.cnblogs.com/f-ck-need-u/p/9994508.html https://lingchao.xin/post/why-are-there-nil-channels-in-go.html ### 讓自己忙碌或自己完成工作 https://github.com/jincheng9/practical-go-cn#93-keep-yourself-busy-or-do-the-work-yourself ### goroutine 之間的通道 通道不僅用於 goroutine 和主程序之間的交互,它們還提供了一種在不同 goroutine 之間進行通信的方式。例如,讓我們創建一個函數,它對 的每個返回結果減去 3,timesThree但前提是它是偶數。 ![](https://i.imgur.com/pnI6JXj.png) ### time out channel https://m.haicoder.net/note/golang/golang-time-after.html ### buffered 跟 unbuffered channel 的差異 https://www.evanlin.com/til-buffered-channel/ unbuffer只有空間1 buufer可以跑for 但不能range 要range要close讓他知道沒了 ### lime concurrencyLimit https://blog.wu-boy.com/2020/09/limit-concurrency-in-golang/ https://github.com/google/gops/blob/master/goprocess/goprocess.go ### 監控go 是否關閉 https://www.yuque.com/aceld/golang/vc7izr ## select channel https://github.com/jincheng9/go-tutorial/tree/main/workspace/lesson29 面試題 https://github.com/jincheng9/go-tutorial/tree/main/workspace/senior/p14 https://mp.weixin.qq.com/s?__biz=Mzg2MTcwNjc1Mg==&mid=2247483746&idx=1&sn=c3ec0e3f67fa7b1cb82e61450d10c7fd&chksm=ce124e0df965c71b7e148ac3ce05c82ffde4137cb901b16c2c9567f3f6ed03e4ff738866ad53&token=609026015&lang=zh_CN#rd ### select 四大用法 https://blog.wu-boy.com/2019/11/four-tips-with-select-in-golang/ ### Select Multiple Channel https://blog.wu-boy.com/2020/10/select-multiple-channel-in-golang/ ### 优先级 但是关于在select中实现优先级在实际生产中是有实际应用场景的,例如K8s的controller中就有关于上面这个技巧的实际使用示例,这里在关于select中实现优先级相关代码的关键处都已添加了注释,具体逻辑这里就不展开细说了。 ![](https://i.imgur.com/Kuz34LQ.png) ### reflect.SelectCase ### race condition https://peterhpchen.github.io/2020/03/08/goroutine-and-channel.html 可用內部檢查有沒有race `$ go run -race mysrc.go // compile and run the program` ### Pipeline pattern https://www.evanlin.com/til-buffered-channel/ ### fan-out ![](https://i.imgur.com/6kU1aPX.png) ## library ### number ![](https://i.imgur.com/jLB6x3R.png) ### string 記住 string is slice of byte so []byte(string)可以把string轉成byte 注意轉後會變成ascii編碼 ![](https://i.imgur.com/G8jsWBP.png) 比較特別是 string 你只抓一個會是位元 會出現asceil編碼 但如果是幾到幾 那就是抓字串 可以string相加 ![](https://i.imgur.com/9pGN7Wr.png) string , int 轉換 ![](https://i.imgur.com/JtDKkFI.png) ## graceful shutdown https://www.youtube.com/watch?v=nCn1Wmi6Wug&list=PLKhlVcCW5bA2WGUs4Y7cRhQ8SSBAGVOTR&index=31 ###### tags: `Go`