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)
}
```

如圖 他是採用蓋住的方式 產生新的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