Go: 寶哥我來惹~ day 01
Tags: go
課程主題
- Go 基礎語法介紹
- 命名常規
- 分號規則
- 變數宣告
- 何謂零值
- 常數
- 遞增常數 (iota)
- 流程控制
- 指標
- 陣列與切片(Slice)
- 雜湊表(map)
- 延遲執行
- panic vs error
- Go 型別系統
- 內建型別(built-in type)
- 參考型別(reference type)
- 結構型別(struct type)
- Method 與 receiver
- Pointer receiver
- Value receiver
- 型別內嵌(Type Embedding)
- 標籤(tag)
- 介面
- 字串深入剖析
- 別名(Alias)
- Go package
- 生命週期介紹
- 命名常規
- internal package
- Go module
- 語意化版本(semantic versioning)
- 初始化 go module
- 認識 go module 相關檔案 -go.mod -go.sum
- 認識 go.mod directive
- 常用 go mod 指令
- Gin 網頁開發框架
- 何謂 multiplexer
- 何謂 handler
- Gin engine 介紹
- 路由機制
- 資料繫節(data binding)
- 資料驗證(data validation)
- 使用 Middleware
- 啟用 Swagger
- 使用 GORM 操作資料庫
- 資料庫連線概念說明
- GORM the ORM library 簡介
- Auto Migration -常用資料庫 tag 介紹
- 使用 GORM 操作 CRUD
- 部署 Go 應用程式
- 如何部署 Go 開發的網站
- 發佈 CLI 工具的注意事項
Go 的原因
code | 優點 | 缺點 |
C++ | 執行速度快 型別安全 | 編譯慢 語法複雜 |
Java | 編譯快 型別安全 | 生態系統複雜 |
Python | 容易使用 | 缺少型別安全 執行速度較慢 |
Go 沒有執行序的概念(Goroutines) 底層一定是用 thread
BUT
語言概念沒有唷!
Go Standard Lib.
Go Cli
Go 跨平台
Go 只有 25 個 關鍵字!
break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var
猜猜關鍵字
package main
import "fmt"
func main () {
var true bool = false
fmt.Println(true) // false
var int string = "str"
fmt.Println(int) // str
var bool int32 = 123
fmt.Println(bool) // 123
}
在 go 中
true
false
是變數唷!!!
-
原始碼
// true and false are the two untyped boolean values. const ( true = 0 == 0 // Untyped bool. false = 0 != 0 // Untyped bool. )
誰使用 Go
Json-to-Go
Go 抓 data and 掃出來
- json-to-go 超酷!!!!! 知道 json 樣貌後 可以幫你趴 struct !!
package main
import (
"encoding/json"
"fmt"
"net/http"
)
type AutoGenerated struct {
Result struct {
Limit int `json:"limit"`
Offset int `json:"offset"`
Count int `json:"count"`
Sort string `json:"sort"`
Results []struct {
Info string `json:"info"`
Stitle string `json:"stitle"`
XpostDate string `json:"xpostDate"`
Longitude string `json:"longitude"`
REFWP string `json:"REF_WP"`
AvBegin string `json:"avBegin"`
Langinfo string `json:"langinfo"`
MRT string `json:"MRT"`
SERIALNO string `json:"SERIAL_NO"`
RowNumber string `json:"RowNumber"`
CAT1 string `json:"CAT1"`
CAT2 string `json:"CAT2"`
MEMOTIME string `json:"MEMO_TIME"`
POI string `json:"POI"`
File string `json:"file"`
Idpt string `json:"idpt"`
Latitude string `json:"latitude"`
Xbody string `json:"xbody"`
ID int `json:"_id"`
AvEnd string `json:"avEnd"`
Address string `json:"address"`
} `json:"results"`
} `json:"result"`
}
func main() {
resp, err := http.Get("https://data.taipei/api/v1/dataset/36847f3f-deff-4183-a5bb-800737591de5?scope=resourceAquire&limit=10&fbclid=IwAR08alf9h64Hv8ZVMZFW6ZXeO3h0eNQ0P9hy-0uhp8GVIFjscMu_xyNa5J8")
if err != nil {
return
}
defer resp.Body.Close()
var data AutoGenerated // 宣告就占住記憶體位置!
err = json.NewDecoder(resp.Body).Decode(&data)
if err != nil {
return
}
for _, v := range data.Result.Results {
fmt.Println(v.Address)
}
}
Go 不是 OOP 沒有物件的概念! 沒有
Object
所有東西都是Value
!!!
-
Value : 記憶體中的某一格
- 在 Go 中 只有 struct ! 沒有 class!
- 一個 struct 就是一個 memory layout
- 記憶體中每一格可以放什麼 在宣告的時候就存在了!
-
type data struct { Name string `json:"name"` Age int `json:"age"` } var user data // == var user = data{}
- 在 Go 中沒有空值 只有 zero value
- Struct 就是會佔住 記憶體位置!
Go 基礎語法
- 自動加分號規則
- an identifier
- an integer, floating-point, imaginary, rune, or string literal
- one of the keywords break, continue, fallthrough, or return
- one of the operators and punctuation ++, –, ), ], or }
-
Naming Convention
- package names:
- 全部
小寫
- 不准用
-
- 只有
測試程式
才會用(_) 當 package name - package name
一定
要等於資料夾名稱!!!
- 全部
- Getters/Setters
- Go Get 通常不會加入 Get ex:
t.Owner()
- Go Set 加入 Set ex:
t.SetOwner(user)
- Go Get 通常不會加入 Get ex:
- Interface names:
- 通常命名 用 er 結尾, ex:
Writer, Reader
- 通常命名 用 er 結尾, ex:
- MixedCaps:
- 用駝峰命名
- 命名大小寫:
- 小 私有
- 大 公開
- package names:
-
宣告
var i = "G is" + " for Go " var j = 'V' var k1, k2 = true, !k1 const l = 111*100000 + 9 const m1 = math.Pi / 3.141592
var ( name string = "Earth" desc string = "Planet" radius int32 = 6378 mass float64 = 5.972E+24 active bool = true satellites []string )
- Zero Value
類別 | Zero Value |
string | ”” |
byte, int, int8, int16, int32, int64, rune, uint, uint8, uint16, uint32, uint64,uintptr, float32,float64 | 0 |
bool | 0!=0 |
interface, func, chan, slice, map, *pointer | nil |
-
宣告常數
const
(僅可使用基礎型別)- 數字: byte, int, int8, int16, int32, int64, rune, uint, uint8, uint16, uint32, uint64, uintptr, float32, float64, complex64, complex128
- 布林: bool
- 字串 string
const ( a = 5 b // 5 c // 5 d // 5 )
-
untyped const
const ( PI = 3.1415 ) func main() { var diameter float64 = 5.2 circumference := diameter * PI ... }
-
iota 遞增常數
const ( Zero = iota // 0 One // 1 Two // 2 Three // 3 Four // 4 )
const ( p2_0 = 1 << iota // 2^0 = 1 _ p2_2 // 2^2 = 4 _ p2_4 // 2^4 = 16 )
package main import ( "fmt" "time" ) const ( Sunday time.Weekday = iota Monday Tuesday Wednesday = 20 Thursday = iota * 2 Friday Saturday ) func main() { fmt.Println(Tuesday) // Tuesday fmt.Println(Wednesday) // 20 fmt.Println(Saturday) // 12 }
-
if statement
if err := req.ParseForm(); err != nil {
log.Println(err)
}
-
Switch Statements + fallthrough
- 跟其他語言不同 Go switch/case 不需要 break, 預設就會 break
- 如果要繼續 加入
fallthrough
currency := "NTD" switch currency { case "JPY": fmt.Println("JPY is valid") case "NTD": fmt.Println("NTD is valid") fallthrough case "USD": fmt.Println("USD is valid") case "HKD": fmt.Println("XY HKD valid") }
-
Switch statements + 判斷條件
currency := "NTD" switch { case currency == "JPY": fmt.Println("JPY is valid") case currency == "NTD" || currency == "USD": fmt.Println("NTD and USD is valid") }
-
Swtich statements + interface
func findAny(val interface{}) { switch i := val.(type) { case int: fmt.Printf("It's int. %d", i) case string: fmt.Printf("It's string. %s", i) } }
-
for statements
for x < 10 { ... }
for { ... }
for i := 0; i < 0; i ++ { ... }
values := [2]string{"NTD", "USD"} for i, val := range values { fmt.Println(i, val) }
values := [2]string{"NTD", "USD"} for index := range values { fmt.Println(index) }
for k:= 0, k < 10; { ... }
for ; k < 10 ; { ... }
for ;; { ... }
for { ... }
-
func 回傳多個值
func GetScore() (int, string){ score, comment := 95, "high score" return score, comment }
func Divide(a, b float32) (res float32, err error) { if b == 0 { err = errors.New(" 0") return } return a / b, nil }
Sum(1, 2, 3, 4, 5) Sum([]int{1, 2, 3, 4, 5}...) func Sum(args ...int) (sum int){ for _, val := range args { sum += val } return }
-
int_lit = decimal_lit | binary_lit | octal_lit | hex_lit . decimal_lit = "0" | ( "1" … "9" ) [ [ "_" ] decimal_digits ] . binary_lit = "0" ( "b" | "B" ) [ "_" ] binary_digits . octal_lit = "0" [ "o" | "O" ] [ "_" ] octal_digits . hex_lit = "0" ( "x" | "X" ) [ "_" ] hex_digits . decimal_digits = decimal_digit { [ "_" ] decimal_digit } . binary_digits = binary_digit { [ "_" ] binary_digit } . octal_digits = octal_digit { [ "_" ] octal_digit } . hex_digits = hex_digit { [ "_" ] hex_digit } .
基礎語法練習
- 1
const ( first = iota second third = 9 fourth ) const ( fifth = iota sixth ) func main() { println(first, second, third, fourth, fifth, sixth) // 0, 1, 9, 9. 0, 1 }
-
2
func main() { var a int = 10 var b int = 0B0111 sum := a + b println(sum) // 17 }
-
3
const ( a = 2 << iota // 2^0 b c ) func main() { println(a, b, c) // 0, 4, 8 }
-
4
func main() { for i := 0; i < 3; { println(i) } } // 印出 0 的 無群迴圈!!
-
5
func main() { name := "John" { name := "Bob" fmt.Println(name) } fmt.Println(name) { name = "Kevin" fmt.Println(name) } fmt.Println(name) } // Bob, John, Kevin, Kevin
基礎語法: 陣列與切片
-
Array & Slice
-
Array 長度不可變
weekdays := [...]string{ "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", }
var days [7]string = [7]string{ "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday", }
- Slice
- 一定有一個相對實際的陣列在記憶體中
- Slice 有三個重要屬性(The Slice header)
- a pointer
- Slice 起始位置
指向
陣列的一個元素(a poiner to the starting pointer ofunderlying array
)
- Slice 起始位置
- len
- 切片長度
- cap
- 可使用的陣列長度
- a pointer
- slice 初始化
weekdays := [] string{"Mon", "Tue", "Wed", "Thu", "Fri"}
var days [5]string = [5]string{"Mon", "Tue", "Wed", "Thu", "Fri"} days2 := days[0:7] // days[:]
make([]T, len, cap) months := make([]sting, 6, 6)
- ex:
weekdays := []string{"Mon", "Tue", "Wed", "Thu", "Fri"} q1 := weekdays[0: 3] // len=3, cap=5 q2 := weekdays[3:] // len=2, cap=2
-
練習 Slice
-
- 種點 切邊
arr[2:7]
–> 2, 3, 4, 5, 6 不包含駔後一個唷~
func main() { arr := [10]int{1, 2, 3, 4, 5} slice := arr[2: 7] fmt.Println(len(slice), cap(slice), slice) // 5, 8, [3, 4. 5. 0, 0] }
- 種點 切邊
-
s := []int{8, 3, 5, 1, 2, 4, 6, 9 , 7, 10} sort.Slice(s, func(i, j int) bool { return s[i] < s[j] })
-
-
使用 append 擴充 slice 容量
slice := []int{10, 20, 30, 40} newSlice := append(slice, 50)
package main import "fmt" func main() { temCap := 0 var b []int for i := 0; i < 99999; i++ { if temCap != cap(b) { temCap = cap(b) fmt.Println(cap(b)) } b = append(b, i) } }
- copy(dst, src)
func main(){
a6 := []int{-10, 1, 2, 3, 4, 5}
a4 := []int{-1, -2, -3, -4}
copy(a6, a4)
// [-1, -2, -3, -4, 4, 5]
}
func main() {
a6 := []int{-10, 1, 2, 3, 4, 5}
a4 := []int{-1, -2, -3, -4}
copy(a4, a6)
// [-10, 1, 2, 3]
}
- 合併
w1 := []string{"Mon", "Tue"}
w2 := []string{"Wed", "Thu", "Fri"}
w1 = append(w1, w2...)
- 強化觀念
package main
import "fmt"
func main() {
w1 := make([]string, 2, 10)
w1[0] = "Mon"
w1[1] = "Tue"
w2 := []string{"Wed", "Thu", "Fri"}
w3 := append(w1, w2...)
fmt.Println(w1)
fmt.Println(w3)
}
// 總共 2 array, len(w1): 2, len(w2): 3, len(w3): 5 cap(w3): 10
-
寫一個 func 傳入 slice (
s1
) return slice (s2
)- 修改 s2 不影響 傳入的 s1 slice
b[0] = 100
a[0] = 1
- 修改 s2 不影響 傳入的 s1 slice
package main
import "fmt"
func main() {
var a = []int{1, 2, 3}
var b = CopySlice(a)
b[0] = 100
a[0] = 1
fmt.Println(a, b)
fmt.Println(b[0])
fmt.Println(a[0])
}
func CopySlice(input []int) []int{
copySlice := make([]int, len(input), len(input))
copy(copySlice, input)
return copySlice
}
func CopySlice2(input []interface{}) []interface{}{
// a[low: high: max]
return append(input[0:0:0], input...)
}
-
- a[low: high: max]
-
slice 練習
package main import "fmt" func main() { // exercise 1 weedays := []string{"Mon", "Tue", "Wed", "Thu", "Fri"} w1 := weedays[:3] w2 := weedays[3:] w1 = append(w1, "XXX") fmt.Println("[exercise 1]: ", w2) // exercise 2 // 請把 arr2 的 7 個人名,加入 arr1 的 slice 中 (請貼上程式碼) arr1 := []string{"Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower"} arr2 := []string{"Hayes", "Hoover", "McKinley", "Roosevelt", "Taft", "Taylor", "Wilson"} arr1 = append(arr1, arr2...) fmt.Println("[exercise 2] arr1: ", arr1) // exercise 3 // 請把 5 拿掉 arr := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} 變成 => arr => []int{1, 2, 3, 4, 5, 7, 8, 9, 10} arr := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} exercise3 := append(arr[:5], arr[6:]...) fmt.Println("[exercise 3]: ", exercise3) // exercise 4 // 把 exercise 3 改成 func arr4 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} exercise3Result := sliceIndex(arr4, 5) fmt.Println("[exercise 4]: ", exercise3Result) } func sliceIndex(input []int, index int) (result []int) { return append(input[:index], input[index+1:]...) }
雜湊表
- map
map[<key type>] <value type>
- reference type 初始值是 nil
m := make(map[string]string) m["Red"] = "#FF0000" m["Lime"] = "#00FF00" value := m["Red"]
m := map[string]string{ "Red": "#FF0000", "Lime": "#00FF00", } value := m["Red"]
- 判斷 map kwy 是否存在
value, exists := m["Blue"] if exist { ... }
-
可用 key type
- boolean
- number(integer, float, complex)
- stuct
- array
-
刪除 map 的 key
m := map[string]int{"Will": 10} value, exists := m["Will"] fmt.Println(value, exists) // 10, true delete(m, "Will") value, exists = m["Will"] fmt.Println(value, exists) // 0, false
-
在 func 之間傳遞 map 的注意事項
- Passing a map between two functions doesn’t make a copy of the map.
- In fact, you can pass a map to a function and make changes to the map, and the changes will be reflected by all references to the map.
延遲執行 恐慌與復原
-
defer
- LIFO 先進先出
- 方法區塊結束執行
func main() { conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/") failOnError(err, "Failed to connect to RabbitMQ") defer conn.Close() ch, err := conn.Channel() failOnError(err, "Failed to open a channel") defer ch.Close() ... } // ch.Close() ---> conn.Close()
-
panic
- 發生 panic 會立刻執行 defer 的方法
- 如果沒有處裡 則 panic 最終會傳到 main 導致城市停止運行
-
defer & recover
defer func() { if r := recover(); r != nil { ... } }() ... panic("panic test2") ...
package main import "fmt" func main(){ panicMachine() } func panicMachine(){ defer func(){ if r := recover(); r != nil { fmt.Println(r) } }() panic("GG 拉!!") }
- 顯示錯誤
errors.New("GG 拉~~~")
Go 型別系統 (三種型別)
-
Built-in-types
ALWAYS: Pass by value !!!
- string
- bool
- number
- byte, int, int8, int16, int32, int64, rune, uint, uint8, uint16, uint32, uint64, uintptr, float32, float64, complex64, complex128
-
Reference types
Pass by header value
- slice, map, interface(不能實體化 只能宣告型別), func
-
zero value 是
nil
- 初始化
- slice
s := make([]byte, 10)
- map
m := make(map[string]string)
- chan
c := make(chan, int, 10)
- func
var f = func(){}
- slice
-
定義 interface 型別
type Writer interface { Write(p []byte) (n int, err error) }
-
-
有序的欄位集合, 欄位可以是 built-in types, reference types, 或 struct types
-
init
package main import "fmt" type User struct { ID int Name string } func main(){ var u User u2 := User{ 01, "Tim"} fmt.Printf("u: %#+v\n", u) fmt.Printf("u: %#+v\n", u2) }
-
匿名 struct 型別
var msg = struct { Name string Message string Number init }{ Name: "Will", Message: "hello", Number: 123, } fmt.Println(msg.Name) // Will
-
-
定義任意型別
-
type myType ini
type Handler func(c Context) type Open func(c Context) error type Output func(c Context) (int, error) type Type int type Code string type H map[string]interface{} type C chan int
-
-
定義型別的應用技巧
package main import "time" type Weekday int const ( Sunday Weekday = iota Monday Tuesday Wednesday Thursday Friday Saturday ) func main(){ var aDay int = 3 var today2 Weekday = Weekday(aDay) var today3 Weekday = 3 today4 := Weekday(3) }
指標
- 指標使用範例
- & 取得記憶體位置
-
- dereference 解開指標
func main() { var a int = 10 var p *int = &a fmt.Println(a, p, *p) Modify(p) fmt.Println(a, p, *p) } func Modify(v *int){ *v = 99 }
type User struct { Id int Name string } func main() { var user *User = NewUser(10, "doggy") fmt.Println(user) } func NewUser(id int, name string) *User { return &User{Id: id, Name: name} }
-
關於 * 的兩種意義與用法
- 當 * 與型別一起使用
- 宣告為指標型別 (Pointer Type)
- 其值必須為記憶體位址
- 可透過 & 取得變數的記憶體位址
- 當 * 與變數一起使用
- 用來取得指標所指向的值
- dereference a pointer
package main import "fmt" func main() { var a int = 10 p := &a fmt.Println(*p) // 10 fmt.Println(p) // 0xc000000a0c0 } *p = 99 fmt.Println(*p) // 99 fmt.Println(p) // 0xc000000a0c0 fmt.Println(a) // 99
- 當 * 與型別一起使用
只要不是用指標 就是把資料 copy 一份完整的內容過去!
- 範例1
func main() {
var a int = 10
var p *int = &a
// a = 10
fmt.Println("a = ", a)
// address of a = 0xc00018ccc0
fmt.Println("address of a = ", &a)
// p = 0xc00018ccc0
fmt.Println("p = ", p)
// address of p = 0xc0000cac28
fmt.Println("address of p = ", &p)
// dereferenced value of p = 10
fmt.Println("dereferenced value of p = ", *p)
}
- 範例2
package main
import "fmt"
func main(){
a := 10
p := &a
p2 := &p
fmt.Println(a, p, p2) // 10 0xc00000a0c0 0xc000006028
fmt.Println(p2) // 0xc000006028
fmt.Println(*p2) // 0xc00000a0c0
fmt.Println(**p2) // 10
}
- pointer 練習
package main
import "fmt"
func main() {
// exercise 1
i := 5
pi := &i
*pi--
square(pi)
fmt.Println("i: ", i) // 16
// exercise 2
a := 20
p1 := &a
p2 := &p1
fmt.Println(**p2) // 20
// exercise 3
a3 := 10
p3 := &a3
*p3 = 99
fmt.Println(a3, *p3) // 99, 99
// exercise 4
a4 := 10
p4 := &a4
b4 := 20
p4 = &b4
fmt.Println(a4, *p4) // 10 20
}
func square(n *int) {
*n = *n * *n
}
Web Api
- net/http 建立 Web 服務
package main
import "net/http"
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/home", index)
server := http.Server{
Addr: ":8080",
Handler: mux,
}
server.ListenAndServe()
}
func index(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello Web"))
}
-
寫法一
package main import "net/http" func main() { // Multiplexer mux := http.NewServeMux() mux.HandleFunc("/home", index) http.ListenAndServe(":8080", mux) } func index(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello Web")) }
-
寫法二(簡化)
package main import "net/http" func main() { http.HandleFunc("/home", index) // 用預設 multiplexer http.ListenAndServe(":8080", nil) } func index(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello Web")) }
-
安裝 gin
go get -u github.com/gin-gonic/gin
-
ex:
package main
import (
"api.homework.yuting/model"
"encoding/json"
"github.com/gin-gonic/gin"
"net/http"
)
func main(){
router := gin.Default()
router.GET("/emp", func(c *gin.Context){
data, err := GetJson()
if err != nil {
return
}
c.JSON(http.StatusOK, data)
})
router.Run(":8080")
}
func GetJson() (a *model.Travel, err error) {
resp, err := http.Get("https://data.taipei/api/v1/dataset/36847f3f-deff-4183-a5bb-800737591de5?scope=resourceAquire&limit=10&fbclid=IwAR08alf9h64Hv8ZVMZFW6ZXeO3h0eNQ0P9hy-0uhp8GVIFjscMu_xyNa5J8")
if err != nil {
return
}
defer resp.Body.Close()
err = json.NewDecoder(resp.Body).Decode(&a)
if err != nil {
return
}
return a, nil
}
回家作業說明
實作主題
霹靂布袋戲角色資料庫系統
事前準備
- 請使用事先建立好的 Go 專案範本進行開發
實作目標
請利用 gin
實作五個 RESTful APIs
- 取得全部資料 [GET] http://localhost:8080/role
- 新增單筆資料 [POST] http://localhost:8080/role
- 取得單筆資料 [GET] http://localhost:8080/role/:id
- 更新單筆資料 [PUT] http://localhost:8080/role/:id
- 刪除單筆資料 [DELETE] http://localhost:8080/role/:id
實作方式
請直接利用 slice
管理資料集合,無須透過資料庫存取!