964 views
Golang === ###### tags: `Basic Component` `Web` `API` # Intruduce :::success Go 語言是由 Google 開發的開放原始碼項目,目的之一為了提高開發人員的程式設計效率。 Go 語言語法靈活、簡潔、清晰、高效。它對的並發特性可以方便地用於多核處理器 和網絡開發,同時靈活新穎的類型系統可以方便地撰寫模組化的系統。Go 可以快速編譯, 同時具有記憶體垃圾自動回收功能,並且還支持運行時反射。Go 是一個高效、靜態類型, 但是又具有解釋語言的動態類型特徵的系統級語法。 有C與Python底子的人無壓力上手 ::: ## 為什麼要學習 Go 1. 開源專案,代表他有無限的可能性。 2. 主要維護者是 Google。 3. 簡單!它很容易學習。 4. Go 跨平台!在這個什麼都要多平台的年代太重要了,甚至你可以 Cross Compile。 5. Channel!Go 的 Goroutine (相當於其他語言的 Thread,但它更輕巧) 可以透過 Channel 溝通。 6. Go 的錯誤處理方式很優雅。 7. Go 的內建函式庫很多,甚至可以直接使用 GitHub 上面的函式庫! 8. 多傳回值,你函式的回傳值可以是多個。 ### reference :::info * [為何GoLang](http://www.cnblogs.com/howDo/archive/2013/04/07/GoLang-WhyStudy.html) * [The Go Programming Language ](https://golang.org/) * [Go Resources](http://www.golang-book.com/) ::: ## [開發工具](https://medium.com/@quintinglvr/golang-guide-a-list-of-top-golang-frameworks-ides-tools-e7c7866e96c9) 1. jetbrain://window&MacOS首選 https://www.jetbrains.com/go/whatsnew/ 2. GVM//Unix-like可用 GVM 可以有效的管理 Go 的版本,連 GOPATH 都幫你設定好,超方便! https://github.com/moovweb/gvm#installing ```C++= //我還在練習 所以使用jetbrain安裝實作較方便 ``` # Basic Component ## 變數 1. 宣告 用 var 來定義變數,跟其他語言不大箱同的是,Go 必須將型態宣告寫在後面。 如果要同時宣告不同的變數,也可以用小括弧把變數括起來,但是一定要換行。 ```go= package main import ( "fmt" "strings" ) var x, y, z int var c, python, java bool func main() { fmt.Println(x, y, z, c, python, java) } ``` ```go= package main import "fmt" var ( x int y int z int c bool python bool java bool ) func main() { fmt.Println(x, y, z, c, python, java) } ``` 2. 初始化變數 定義變數時可以直接賦予初始值,變數與賦予的值要互相對應。如果有初始化的話,型別就可以省略;變數會直接取用初始化的類型。 ```go= package main import "fmt" var x, y, z int = 1, 2, 3 var c, python, java = true, false, "no!" func main() { fmt.Println(x, y, z, c, python, java) } ``` 3. 短宣告 在函數中,「:=」 簡潔賦值語句在明確類型的地方,可以替代 var 定義。 ```go= var a // 不定型別的變數 var a int // 宣告成 int var a int = 10 // 初始化同時宣告 var a, b int // a 跟 b 都是 intvar a, b = 0, "" var a int , b string a := 0 a, b, c := 0, true, "tacolin" // 這樣就可以不同型別寫在同一行 var( a bool = false // 記得要不同行,不然會錯 b int c = "hello" ) ``` ```go= import ( "fmt" ) type example struct { x int str string } func main() { s := new(example) s.x =1 s.str = "Hello World" fmt.Printf("%s\n",s.str) } ``` 4. 浮點數 包涵兩種型態 float32、float64。 與其他語言相同, Go 當然也可以用浮點數。 要注意的是,如果初始化的時候沒有加小數點會被推斷為整數,另外初始化的時候沒有指定型態的話,會被自動推斷為 float64。 float64 在 Go 語言相當於 C 語言的 Double,且 float32 與 float64 是無法一起計算的,需要強制轉型。 ```go= package main import "fmt" func main() { var floatValue float64 floatValue = 7.0 var floatValue2 = 3.0 fmt.Println("7.0/3.0 =", floatValue/floatValue2) var test float64 var test2 float32 test = 1.1 test2 = 2.2 fmt.Println("test + test2 =", float32(test) + test2) } ``` 5. boolen Go 語言中的布林與其他語言一致,關鍵字一樣是 bool ,可欲賦予值為 true 和 false。 ```go= package main import "fmt" func main() { var a bool a = true fmt.Println("a =", a) b := false fmt.Println("b =", b) fmt.Println(true && true) fmt.Println(true && false) fmt.Println(true || true) fmt.Println(true || false) fmt.Println(!true) } ``` ### Coding Style Go 有一定的coding Style 就是 * ex:(error) func main() { i:= 1 fmt.Println("Hello World", i) } (right) func main(){ i:= 1 fmt.Println("Hello World", i) } * 為了保持程式碼的乾淨,你宣告了一個變數,但是卻沒有使用,Go 語言連編譯都不會讓你編譯。 ```go= a, b, c := 1, 2, 3 fmt.Printf("%d %d\n",a, b,) //其中的c沒有用到 那他編譯不會讓你過 //c declared and not used ``` ### control block Go 只有一種迴圈 --「 for 」 。 基本的「 for 」迴圈除了沒有了 「 () 」 之外(甚至強制不能使用它們),它看起來跟 C 或者 Java 中做的一樣,而 「 {} 」 是必須的。 1. for example ```go= package main import "fmt" func main() { sum := 0 for i := 0; i < 10; i++ { sum += i } fmt.Println(sum) } ``` 2. 跟 C 或者 Java 中一樣,可以讓前置、後置語句為空。 ```go = package main import "fmt" func main() { sum := 1 for ; sum < 1000; { sum += sum } fmt.Println(sum) } ``` 3. For is Go's "while" 如題 都是用for來實作while ```go= package main import "fmt" func main() { sum := 1 for sum < 1000 { sum += sum } fmt.Println(sum) } ``` 4. 無窮迴圈 如果省略了迴圈條件,迴圈就不會結束,因此可以用更簡潔地形式表達無窮迴圈。 ```go= package main func main() { for { } } ``` :::info for 迴圈有另外一種內建的寫法可以走訪每個陣列,就是利用 range ,但是他預設會有兩個回傳值,一個是鍵一個是值。 ```go = var x [4]float64 x[0] = 23 x[1] = 45 x[2] = 33 x[3] = 21 var total float64 = 0 for _, value := range x { total += value } ``` ::: 5. if if 語句除了沒有了「 () 」 之外(甚至強制不能使用它們),看起來跟 C 或者 Java 中的一樣,而 「 {} 」 是必須的。 ```go= package main import ( "fmt" "math" ) func sqrt(x float64) string {//回傳型態為string不回傳則不用寫型態func function(){} if x < 0 { return sqrt(-x) + "i" } return fmt.Sprint(math.Sqrt(x)) //Sprint:formats the string and does not print to console. It returns the formatted string. } func main() { fmt.Println(sqrt(2), sqrt(-4)) } ``` 6. if 簡短的敘述 跟「 for 」一樣,「 if 」 語句可以在條件之前執行一個簡單的語句。 由這個語句定義的變數的作用範圍僅在「 if 」範圍之內。 ```go = package main import ( "fmt" "math" ) func pow(x, n, lim float64) float64 { if v := math.Pow(x, n); v < lim { return v } return lim } func main() { fmt.Println( pow(3, 2, 10), pow(3, 3, 20), pow(3, 3, 5), ) } ``` 7. if 和 else ```go = import ( "fmt" "math" ) func pow(x, n, lim float64) float64 { if v := math.Pow(x, n); v < lim { return v } else { fmt.Printf("%g>= %g\n", v, lim) } // else 必須接在}後面 return lim } func main() { fmt.Println( pow(3, 2, 10), pow(3, 3, 20), ) } ``` 8. Switch Switch 是比 if 更有閱讀性的控制結構,跟 C 語言不同的是他不需要「 break 」來跳出。 注意,如果使用「 fallthrough 」關鍵字,會執行下一個 Case。 另外 Switch 後的陳述不是必須的,你可以寫 Case 裡面。 ```go = package main import "fmt" func choose(i int) { switch i { case 1: fmt.Println("1") case 2, 3: fmt.Println("2 or 3") case 4: fallthrough case 5: fmt.Println("5") } } func anotherChoose(i int) { switch { case i > 5: fmt.Println(i, "> 5") case i < 5: fmt.Println(i, "< 5") case i == 5: fmt.Println(i, "= 5") } } func main() { choose(1) choose(3) choose(4) anotherChoose(6) } ``` 9. goto Go 語言跟 C 語言一樣也有「 goto 」 ```go= package main import "fmt" func main() { i := 0 HERE: fmt.Print(i) i++ if i < 10 { goto HERE } } ``` ### Array & Slice 1. Array 如何宣告陣列呢?很簡單!只要在變數宣告後面多家 [] 就可以了,裡面要填寫宣告的陣列數字。 ```go= package main import "fmt" func main() { var x [5]int x[0] = 10 x[1] = 11 x[2] = 22 x[3] = 33 x[4] = 44 fmt.Println(x) } ``` 但初始化的部份還是太長了 他一樣可以短宣告 ```go = package main import "fmt" func main() { arry := [4]int{ 23, 45, 33, 21,//最後一個逗號要有 聽說是為了方便維護 }//或是arry := [4]int{23, 45, 33, 21} fmt.Println(x) } ``` 2. Slice 在建立陣列的時候要指定元素的大小,但是!我們今天如果不知道要多大怎麼辦??陣列可以不輸入元素大小嗎?答案是可以!!!不過這個方法會有問題!而且他的型態不是陣列,而是 Slice。 - ex 宣告 ```go= var x []float64 ``` Slice 有兩種數值,一個是容量一個是長度。這兩個有什麼不同呢?其實容量就是最大能裝多少,而長度不能超過容量。 - ex 宣告 ```go= x := make([]float64, 5) //設定一個長度和容量都是 5 的 slice 變數 x ``` - ex [lox,high]宣告 用[low : high] 這個方法來參考別的 Slice high 表示的是結束的點 ```go= arr := [5]float64{1,2,3,4,5} x := arr[0:5] //arr[0:5] 其實是指 [1,2,3,4,5] //如果是 arr[1:4] 則是 [2,3,4] //某些情況下 low 跟 high 是可以省略的 //像是 arr[0:5] 這樣子就可以省略成arr[:5] //arr[0:] 則是相當於 arr[0: len(arr)] //[:] ,它相當於 arr[0:len(arr)]。 ``` :::info [Go by Example: Slices](https://gobyexample.com/slices) ::: - Append, Copy Slice 有兩個函式Append&Copy 1. Append ```go= package main import "fmt" func main() { arr := []float64{1, 2, 3, 4, 5} arr2 := arr[2:4] fmt.Println("arr", arr) fmt.Println("arr2", arr2) arr = append(arr, 6) fmt.Println("\nChange Origin Data\n") fmt.Println("arr", arr) fmt.Println("arr2", arr2) fmt.Println("\nAnother Case") arr3 := []float64{1, 2, 3, 4, 5} arr4 := append(arr3, 6) fmt.Println("\nChange Origin Data\n") arr3 = append(arr3, 7) fmt.Println("arr3", arr3) fmt.Println("arr4", arr4) } ``` ![](https://minio.mcl.math.ncu.edu.tw:443/hackmd/uploads/upload_2b84ec28ac8c95cd94c9c35cb4e29515.png) 如圖 他是採用蓋住的方式 產生新的slice而且不是連續的記憶體位置 2. Copy 可以將一個 slice 的值複製到另外一個 slice,但是無法超過容量限制,下面的範例將容量為 3 的 slice 的數值 1, 2, 3 複製到 slice2 ,但是 slice 2 的容量只有 2,所以實際上 slice2 的值是 1, 2。 ```go = func main() { slice1 := []int{1,2,3} slice2 := make([]int, 2) copy(slice2, slice1) fmt.Println(slice1, slice2) } ``` ## 函式 1. 多數值的返回 函數可以返回任意數量的返回值。 這個函數返回了兩個字串。 ```go= package main import "fmt" func swap(x, y string) (string, string) { return y, x } func main() { a, b := swap("hello", "world") fmt.Println(a, b) } ``` 2. 命名返回值 在 Go 中,函數可以返回多個「結果參數」,而不僅僅是一個值。它們可以像變數那樣命名和使用。 如果命名了返回值參數,一個沒有參數的 return 語句,會將當前的值作為返回值返回。 以這個程式碼為例,sum int 表示宣告整數 sum ,將參數 17 放入 sum 中,x, y int 宣告整數 x,y 在下面使用,由於 return 沒有設定返回值,這邊程式就將 x,y 都回傳了,所以結果會出現 7 10。 ```go = package main import "fmt" func split(sum int) (x, y int) { x = sum * 4 / 9 y = sum - x return } func main() { fmt.Println(split(17)) } ``` ## Goroutine Goroutine ,他類似於其他語言的 Thread。 ```go= package main import "fmt" func f(n int) { for i := 0; i < 10; i++ { fmt.Println(n, ":", i) } } func main() { go f(0) } ``` ## try...catch 錯誤處理 以前的 try...catch...finally Go 改良過去會導致整個結構變得很糟糕,維護又很不方便的問題。 ```go= package main import "fmt" func main() { defer func() { //必須要先聲明defer,否則不能捕獲到panic異常 //只有捕獲到panic異常才進入 fmt.Println("first") if err := recover(); err != nil { fmt.Println(err)//這裡的err其實就是panic傳入的內容,1 } fmt.Println("end") }() f() } func f() { fmt.Println("test") panic(1) fmt.Println("test2") } ``` :::success test first 1 end ::: :::info 這說明,在f函數中拋出的panic被自己defer語句中的recover捕獲,然後控制流程進入到recover之後的語句中,即print first、print err、print end,之後正常退出。 ::: * 在java或其他很多面向對象的語言中, 異常處理是依靠throw、catch來進行的。在go語言中,panic和recover函數在作用層面分別對等throw和catch語句,當然也存在不同之處。 ```java = try{ throw new Exception(); } catch(Exception $e) { do something ... } finally { } ``` * 這種機制中,我們把可能拋出異常的語句或拋出自定義異常的語句放置到try語句塊中,而在catch塊中,我們將上述語句拋出的異常捕獲,針對不同的異常進行報警或log等處理。之後,控制流程進入到finally語句塊中。若沒有finally語句,控制流程將進入到catch之後的語句中。也就是說,在這種機制中,控制流程是轉移到同一層級中異常捕獲之後的語句中。 * 在go的異常機制中,panic可以將原有的控制流程中斷,進入到一個"恐慌(panic)"流程。這種恐慌流程可以顯式調用panic()函數產生或者由運行時錯誤產生(例如訪問越界的數組下標)。panic會在調用它的函數中向本層和它的所有上層逐級拋出,若一直沒有recover將其捕獲,程序退出後會產生crash;若在某層defer語句中被recover捕獲,控制流程將進入到recover之後的語句中。 * ex2 ```go = package main import ( "fmt" ) func main() { func g() { defer func() { fmt.Println("b")//2 if err := recover();err != nil { fmt.Println(err)//3 } fmt.Println("d")//4 }() f() fmt.Println("e")//5 } fmt.Println("x") } func f() { fmt.Println("a")//1 panic("a bug occur") fmt.Println("c") } ``` :::success a b a bug occur d x ::: :::info 過程:f()中拋出panic,由於自身(f())沒有定義defer語句,panic被拋到g()中。g()的defer語句中定義了recover,捕獲panic後並執行完defer剩餘的語句,之後控制流程被轉交到main()函數中,直到結束。 ::: ## Pinter 跟C沒什麼不一樣 ```go= func zero(xPtr *int) { *xPtr = 0 } func main() { x := 5 zero(&x) fmt.Println(x) // x is 0 } ``` ## Struct 也同C ```go = package main import "fmt" type person struct { name string age int } func main() { fmt.Println(person{"Bob", 20}) fmt.Println(person{name: "Alice", age: 30}) fmt.Println(person{name: "Fred"}) fmt.Println(&person{name: "Ann", age: 40}) s := person{name: "Sean", age: 50} fmt.Println(s.name) sp := &s fmt.Println(sp.age) sp.age = 51 fmt.Println(sp.age) } ``` :::success {Bob 20} {Alice 30} {Fred 0} &{Ann 40} Sean 50 51 ::: ## Interface 相信有學過物件導向的人都聽過 Interface ,GO 是靜態語言當然也有類似的 Interface ```go= package main import "fmt" import "math" type geometry interface { area() float64 perimeter() float64 } //interface 這邊,你會發現他的語法和 struct 很類似 //裡面是繼承 area 跟 perimeter 的方法 //後面利用 struct 建立正方形跟圓形的結構。 type square struct { width, height float64 } type circle struct { radius float64 } func (s square) area() float64 { return s.width * s.height } func (s square) perimeter() float64 { return 2*s.width + 2*s.height } func (c circle) area() float64 { return math.Pi * c.radius * c.radius } func (c circle) perimeter() float64 { return 2 * math.Pi * c.radius } //定義的是 area 跟 perimeter 的方法 func measure(g geometry) { fmt.Println(g) fmt.Println(g.area()) fmt.Println(g.perimeter()) } //只是將當作參數的 interface 繼承的方法印出而已。 func main() { s := square{width: 3, height: 4} c := circle{radius: 5} //這邊我們利用剛剛建立的 struct 建立兩個變數 s, c ,然後呼叫函式。 measure(s) //以 measure(s) 來看,因為我們初始化 struct 的數值的時候 //給予 width: 3, height: 4, //所以第一行會印出 {3 4},再來會印出 12 measure(c) } ``` :::info reference: * [解釋 Golang 中的 Interface 到底是什麼](https://yami.io/golang-interface/) * [理解golang五大重點](https://sanyuesha.c2017/07/22/how-to-understand-go-interface/) * [Struct 與 Interface](http://golang-zhtw.netdpi.net/09-structs-and-interfaces/09-03-interface) * [[Golang] 程式設計教學:介面 (Interface)](https://michaelchen.tech/golang-prog/interface/) * [What's the differences between Go and Java about interface?](https://stackoverflow.com/questions/39932713/whats-the-differences-between-go-and-java-about-interface) ::: ## List List 不是基礎型別 需要特別引入 ```go= package main import ( "fmt" "container/list" ) func main() { var x list.List x.PushBack(1) x.PushBack(2) x.PushBack(3) for e := x.Front(); e != nil; e=e.Next() { fmt.Println(e.Value.(int)) } } ``` :::info [Package list](https://golang.org/pkg/container/list/) [Package heap](https://golang.org/pkg/container/heap/) ::: ## 函式庫 ```go= package main import "fmt" import "time" func main() { p := fmt.Println //以p代替fmt.Println now := time.Now() //獲得目前的時間 p(now) then := time.Date( 2009, 11, 17, 20, 34, 58, 651387237, time.UTC) //自訂時間 p(then) p(then.Year()) p(then.Month()) p(then.Day()) p(then.Hour()) p(then.Minute()) p(then.Second()) p(then.Nanosecond()) p(then.Location()) p(then.Weekday()) p(then.Before(now)) p(then.After(now)) p(then.Equal(now)) diff := now.Sub(then) p(diff) p(diff.Hours()) p(diff.Minutes()) p(diff.Seconds()) p(diff.Nanoseconds()) p(then.Add(diff)) p(then.Add(-diff)) } ``` # Web ## GET/POST ```go= func httpGet() { resp, err := http.Get("https://tw.yahoo.com/") //利用 http.get 的方法來將 request 送給 Yahoo //Get 方法會返回兩個數值,一個是 response 另一個是 error if err != nil { // handle error } defer resp.Body.Close() //利用 defer 來確定是否有收到 body, err := ioutil.ReadAll(resp.Body) if err != nil { // handle error } fmt.Println(string(body)) } func httpPost() { //POST 其實大部份就跟 GET 相同 resp, err := http.Post("https://tw.yahoo.com/", "application/x-www-form-urlencoded", strings.NewReader("name=test")) if err != nil { fmt.Println(err) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { // handle error } fmt.Println(string(body)) } ``` # API