# Go Lang 學習筆記 - 環境建立與基本語法 ###### tags: `GoLang` ## 學習目標 熟悉Go語言的建立以及基本使用及了解其優勢。 ## 目錄 [TOC] # 環境建立 當我們第一次使用時需要至[官網](https://go.dev/dl/)下載安裝command使用,此外開發環境我們是用VsCode這邊在安裝Go的對應套件即可。 ![](https://i.imgur.com/qvBTlWN.png) :::info :bulb: 第一次檔案建立時會跳出以下畫面,直接點選安裝即可 ![](https://i.imgur.com/SyUaek1.png) ::: # 專案建立 ## 初始化 terminal 語法如下 ```shell= go mod init <project-name> ``` * `<project-name>`:為自己想要的專案名稱 執行完後會建立`go.mod`檔案,內部資料則會顯示專案名稱及go版本 ![](https://i.imgur.com/lAZe3NP.png) ## 進入點 在Go會透過`main`的`package`的`func main`作為程式進入點 ```go= // main.go package main func main() { // ... } ``` 要執行時也十分簡單只需要執行以下指令即可 ```shell= go run main.go # or go run . ``` 兩個差別在於一個是指定檔案,另一個則是指定執行該資料夾的所有檔案,執行後程式會自己找尋進入點執行。 # 宣告 ## 參數 ```go= var appName string = "Frank" // or // 此種方式為語法糖 appName := "Frank" ``` 也可以宣告唯獨 ```go= const tickets int = 50 ``` ## 陣列 與C#相同可以指定大小以及動態的,其賦予資料的方式各不相同 ### 動態大小 ```go= var bookings []string // 初始化方式 var bookings = []string{"A","B","C"} // or bookings := []string{"A","B","C"} ``` 使用方式 ```go= // 透過此方式加入資料 bookings = append(bookings, "Hello") // 修改資料 bookings[0] = "Test" ``` ### 固定大小 基本上就是在`[]`內部加上數字而已,有差別的地方固定的無法透過`append`的方式賦予值 # 條件式 ## if else 用法大致上相同 ```go= if <condition>{ // ... } else { // ... } ``` * `<condition>`:寫條件式或是留空,留空條件式為`true` ## switch 用法也大同小異,最主要差別在於不用像C#加上`Break` ```go= word := "Apple" switch word { case "Apple": fmt.Println("This is Apple") case "Banana": fmt.Println("This is Banana") default: fmt.Println("Default") } ``` # 迴圈 Go只有For語法可以使用,但`continue`或`break`的用法和C#相同,接下來看要如何達成想要的邏輯 ## 無限迴圈 ```go= for{ // ... } ``` ## 條件迴圈 ```go= myIndex := 10 for i := 0; i < myIndex; i++ { fmt.Println(i) } // or myIndex := 10 for myIndex > 0 { myIndex-- fmt.Println(myIndex) } for ``` ## foreache 此程式會透過`strings.Fields`函式將字串依據空白切分並回傳陣列,我們將第一個取出放入`names`,之後印出所有陣列內的內容 ```go= bookings := []string{"A AA", "B BB", "C CC"} names := []string{} for _, booking := range bookings { var fieldData = strings.Fields(booking) names = append(names, fieldData[0]) } fmt.Printf("The name of bookings are: %v\n", names) ``` 這邊for有兩個參數一個為`_`和`booking`,<font class="red">前者為index後者陣列內取出的元素</font>,因為Go不允許無使用參數故這邊使用捨棄。 # Key/Value 想使用`Key/Value`的儲存方式在Go的方式裡面的為`Map`,直接來看範例 ```go= func main() { var userData = make(map[string]string) userData["name"] = "frank" userData["email"] = "ff@ff" userData["numberOfTickets"] = strconv.FormatUint(uint64(3), 10) } ``` 如果要加上動態陣列時必須宣告初始化陣列的長度,數字可以任何數之後還是可以透過`append`增加 ```go= var userData = make([]map[string]string, 0) ``` # 函式 回傳寫法如下,以下兩種做法結果相同 ```go= func Add(number1 int, number2 int) int { return number1 + number2 } // or func Add(number1 int, number2 int) (resAdd int) { resAdd = number1 + number2 return } ``` ## 回傳多筆 這與C# tuple 相同廢話也不多說了,直接看程式 ```go= package main func calc(number1, number2 int) (int, int) { return number1 + number2, number1 - number2 } func main() { add, sub := calc(2, 1) fmt.Print(add, " ", sub) } ``` ## 傳入個數未知 與C# arg相同 ```go= package main func Sum(numbers ...int) int { var sum int for _, number := range numbers { sum += number } return sum } func main() { sum := Sum(1, 2, 3) fmt.Print(sum) } ``` # 併發處理 在Go當中要提供非同步範例如下: ```go= func main() { go printMsg("my goruntine !!") } func printMsg(msg string) { time.Sleep(2 * time.Second) fmt.Println("#####") fmt.Printf("show msg : %v\n", msg) fmt.Println("#####") } ``` 範例中使用在呼叫函示`printMsg`時在前面加上`go`即可 不過以上範例有一個問題,因為主執行緒(**fun main**)結束後底下的程序(**go printMsg**)將會被忽略或終止,如果我們要確保工作完成會需要使用`wgsync`的Package來完成想要的等待作業,我們將程式改成如下: ```go= var wg = sync.WaitGroup{} func main() { fmt.Println("Hello App") wg.Add(1) go printMsg("my goruntine !!") wg.Wait() } func printMsg(msg string) { time.Sleep(2 * time.Second) fmt.Println("#####") fmt.Printf("show msg : %v\n", msg) fmt.Println("#####") wg.Done() } ``` 這邊我們使用了`WaitGroup`的一些函示解釋如下 * wg.Add(n):告知後續有多少線程將要執行 * wg.Wait():等待所有線程完成任務 * wg.Done():告知任務完成 這邊可以得知透過簡單線程計數來確保需要確認完成的線程數量是否相同 # package 在安裝玩Go時會同時安裝常用的套件,使用上只需要透過`import`來導入即可 ```go= import ( "GolangApp/helper" "fmt" "strconv" "strings" ) // ... ``` ## fmt 為文字輸出套件,以下範例使用`fmt`套件來輸出文字 ```go= import "fmt" ``` Console輸出 ```go= // 輸出 fmt.Print("Hello world") // 輸出後換行 fmt.Println("Hello world") // 夾帶參數 // %v:value ; %T:type fmt.Printf("Hello %v", "world") // or fmt.Println("Hello ", "parameter" ," world") ``` ## 客製 我們專案的資料夾結構如下: ![](https://i.imgur.com/OqnCdiO.png) go.mod的內容如下: ![](https://i.imgur.com/DtKcoue.png) 其`frankHelper.go`的內容如下 ![](https://i.imgur.com/ljcaDF4.png) :::warning :bulb: 在撰寫func時其名稱<font class='red'>開頭大寫為Public,小寫則為Private</font> ::: 之後可以透過路徑來引入,需要注意的是這邊的<font class='red'>GolanApp</font>是module所定義(go.mod檔)的名稱,不是根資料夾的名稱 ![](https://i.imgur.com/6YuD0bi.png) :::info :bulb: 更熟悉後可以導入Go的[資料夾分類](https://github.com/golang-standards/project-layout) ::: # 結論 透過實際的實作以後了解了Go語言處理併發的魅力,不過非同步的概念在許多程式語言也有(ex.C#)但Go在寫法上更簡單其所需資源也更少,其原因為在Go當中線程式用`Goroutine`來建立的並非使用`thread`。 `Goroutine`是使用一種**Green Thread**的緒,**Green Thread**為建立在**真實緒**底下的**抽象緒**由go runtime直接管理 ![](https://i.imgur.com/W6wWiik.png) > 圖來源:https://www.youtube.com/watch?v=yyUHQIec83I&ab_channel=TechWorldwithNana 透過 [[Golang] Goroutine Concurrency多執行緒淺談](https://xiang753017.gitbook.io/zixiang-blog/golang/golang-goroutine-concurrency-duo-zhi-hang-xu-qian-tan)測試結果可以得知Go執行一個緒只需要花費2k的資源,與OS Thread來比較因為並非建立真實的Thread故所需花費更加便宜,此外其機制也不相依於硬體規格,故在未來程式開發上針對高併發的系統可以考慮換成Go來實作。 最後附上測試專案[GitHub](https://github.com/franksu129/play-go/tree/main/GolangApp)。 <br/> --- 相關參考來源: [Go by Example](https://gobyexample.com/) [Golang Tutorial for Beginners | Full Go Course](https://www.youtube.com/watch?v=yyUHQIec83I&ab_channel=TechWorldwithNana) <style> .red{color:red} </style>