Go資深工程師講解(慕課) 002

go(二)

string 字符串

package main

import (
    "fmt"
    "unicode/utf8"
)

func main() {
    s := "Yes我愛Go語言"
    fmt.Println(len(s))
    for _, b := range []byte(s) {
        fmt.Printf("%X ", b)
    }
    fmt.Println()
    for i, ch := range s { // ch is a rune 4字節的 將utf-8解碼再轉成unicode
        fmt.Printf("(%d %X) ", i, ch)
    }
    fmt.Println()
    fmt.Println("Rune count:", utf8.RuneCountInString(s))
    bytes := []byte(s)
    for len(bytes) > 0 {
        ch, size := utf8.DecodeRune(bytes) // 一個字符一個字符解
        bytes = bytes[size:]
        fmt.Printf("%c ", ch)
    }
    fmt.Println()

    for i, ch := range []rune(s) {
        fmt.Printf(" %d->%c ", i, ch)
    }
    fmt.Println()
}

strings 包可以字符串的常用操作

  • Fields,Split,Join
  • Contains,Index
  • ToLower,ToUpper
  • Trim,TrimRight,TrimLeft

結構體和方法

go 語言只支持封裝,不支持多態和繼承
沒有 class,只有 struct
不需要知道是在堆創建還是在棧上創建;

package main

type TreeNode struct {
    value       int
    left, right *TreeNode
}

func main() {
    //    實例化node
    var root TreeNode
    root = TreeNode{value: 3}
    root.left = &TreeNode{}
    root.right = &TreeNode{5, nil, nil}
    root.right.right = new(TreeNode{value:0}) // 不論地址還是結構,一律採用.來訪問成員
    root.left.right = createNode(2)
}

//沒有構造函數,我們可以通過工廠函數來創建實例
func createNode(value int) *TreeNode {
    return &TreeNode{value: value}
}
  • 要改變內容必須使用指針接收者
  • 結構過大也考慮使用指針接收者,必須複製一份數據是要佔用資源的

值接收者才是 go 語言所特有的

封裝

  • 首字母大寫 public
  • 首字母小寫 private
  • 包是根據目錄(目錄和包名可以不一致,但一個目錄一個包名即一個目錄只能放一個包),main 包包含可執行的入口
  • 為結構定義的方法必須放在同一個包內,但可以分不同的文件

改造一下目錄結構

  • tree
  • entry
    • entry.go
  • node.go
package tree

import (
    "fmt"
)

type Node struct {
    Value       int
    Left, Right *Node
}



//沒有構造函數,我們可以通過工廠函數來創建實例
func CreateNode(value int) *Node {
    return &Node{Value: value}
}

//為結構創建方法 就可以看成是一個普通函數如func Print(node TreeNode){}
//注意值拷貝
func (node Node) Print() {
    fmt.Println(node.Value)
}

//注意不寫*不無法修改node的值的,因為是傳遞都是值拷貝,所以(node *TreeNode)
func (node *Node) SetValue(val int) {
    if node == nil {
        fmt.Println("Setting value to nil node. Ignored.")
        return
    }
    node.Value = val
}

//中序遍歷 先左在中在右
func (node *Node) Traverse() {
    if node==nil {
        return
    }
    node.Left.Traverse()
    node.Print()
    node.Right.Traverse()
}

// entry.go
package main

import "gobasic/tree"

func main() {
    //    實例化node
    var root tree.Node
    root = tree.Node{Value: 3}
    root.Left = &tree.Node{}
    root.Right = &tree.Node{5, nil, nil}
    root.Right.Right = new(tree.Node) // 不論地址還是結構,一律採用.來訪問成員
    root.Left.Right = tree.CreateNode(2)
    //root.Print()
    root.Traverse()
}

依賴此包擴展一個後序遍歷

package main

import (
    "fmt"
    "gobasic/tree"
)
// 小寫不給別人用
type myTreeNode struct {
    node *tree.Node
}

//自定義的私有的後序遍歷
func (myNode *myTreeNode) postOrder() {
    if myNode == nil || myNode.node == nil {
        return
    }
    left := myTreeNode{myNode.node.Left} // cannot call pointer method on myTreeNode literal
    right := myTreeNode{myNode.node.Right}
    left.postOrder()
    right.postOrder()
    myNode.node.Print()
}

func main() {
    //    實例化node
    var root tree.Node
    root = tree.Node{Value: 3}
    root.Left = &tree.Node{}
    root.Right = &tree.Node{5, nil, nil}
    root.Right.Right = new(tree.Node) // 不論地址還是結構,一律採用.來訪問成員
    root.Left.Right = tree.CreateNode(2)
    //root.Print()
    root.Traverse()
    fmt.Println()
    myRoot := myTreeNode{&root}
    myRoot.postOrder()
    fmt.Println()
}

包裝一個隊列

package queue

type Queue []int

func (q *Queue) Push(v int) {
    //q是指針 *q 是指向的object本身,下面就是改變了對象本身
    *q = append(*q,v)
}

func (q *Queue) Pop() int  {
    head:=(*q)[0]
    *q = (*q)[1:]
    return head
}

func (q *Queue) IsEmpty() bool  {
    return len(*q) == 0
}

GOPATH 環境變量

默認~/go(unix,linux) %USERPROFILE%\go(windows)
官方推薦:所有項目和第三方庫都放在同一個 GOPATHG 下,也可以將每個項目放在不同的 GoPATH

  • go get 獲取第三方庫
  • go get -v github.com/gpmgo/gopm 可以用來拉取 google 服務器上的資源,之前通過 dep 的翻牆來實現(夠 2B)
  • 這樣我們就可以得到goimports gopm get -g -v golang.org/x/tools/cmd/goimports 因為之前用的是 go get 或 dep 翻牆安裝的,所以安裝沒成功,提示已經有一個 golang.org 的庫存在
  • 第一個 main 文件都放在一個單獨目錄中

接口

duck typing
使用者來定義接口

// main.go
package main

import (
    "fmt"
    "gobasic/retriever/moke"
    real2 "gobasic/retriever/real"
)

type Retriever interface {
    Get(url string) string
}

func download(r Retriever) string {
    return  r.Get("www.baidu.com")
}

func main()  {
    var r Retriever
    r = moke.Retriever{"====This is a fake content==="}
    fmt.Println(download(r))
    r = real2.Retriever{}
    fmt.Println(download(r))
}

// moke/mockretriever.go 有Get方法(返回string)就說明實現了這個接口
package moke

type Retriever struct {
    Contents string
}

func (r Retriever) Get(url string) string  {
    return r.Contents
}
// real/reltriever.go
package real

import (
    "net/http"
    "net/http/httputil"
    "time"
)

type Retriever struct {
    UserAgent string
    TimeOut   time.Duration
}

func (r Retriever) Get(url string) string {
    //panic("implement me")
    resp, err := http.Get("http://www.baidu.com")
    if err != nil {
        panic(err)
    }
    result, err := httputil.DumpResponse(resp, true)
    resp.Body.Close()
    if err != nil {
        panic(err)
    }

    return string(result)
}

接口變量里指向甚麼

  • 接口變量
  • 實現者類型
  • 實現者指針-->實現者
    接口變量自帶指針,接口變量同樣採用值傳遞,幾乎不需要使用接口的指針;指針接收者只能實現指針方式,值接收者可以會用值或地址

任何類型我們可以指定它為 interface{}

查看接口變量

  • 表示任何類型 interface{}
  • Type Assertion
  • Type Switch

go 的一些標準接口

  • Stringer
  • Reader
  • Writer

函數式編程

  • 函數是一等公民,參數,變量,返回值都可以是函數
  • 高階函數
  • 函數-->閉包

正統函數式編程:不可變性(不能有狀態,只有常量和函數),函數只能有一個參數,

package main

import "fmt"

func adder() func(int) int {
    sum:=0
    return func(i int) int {
        sum+=i
        return sum
    }
}

func main()  {
    a:=adder()
    for i:=0;i<=10;i++{
        fmt.Printf("0+1+...+%d = %d\n",i,a(i))
    }
}

斐波那切

package main

import "fmt"

// 1,1,2,3,5,8,13,21
//   a,b
//     a,b
func fibonacci() func() int {
    a,b:=0,1
    return func() int {
        a,b=b,a+b
        return a
    }
}

func main() {
    f:= fibonacci()
    for i:=0;i<10;i++ {
        fmt.Printf("%v \t",f())
    }
}

對閉包的應用

  • 更為自然,不需要修飾如何訪問自由變量
  • 沒有 lambda 表達式,但有匿名函數

改造tree node

package tree

import "fmt"

//中序遍歷 先左在中在右
func (node *Node) Traverse() {
    /*if node==nil {
        return
    }
    node.Left.Traverse()
    //只能作打印,
    node.Print()
    node.Right.Traverse()*/

    node.TraverseFunc(func(n *Node) {
        n.Print()
    })
    fmt.Println()
}

//改造一下
func (node *Node) TraverseFunc(f func(*Node)) {
    if node == nil {
        return
    }
    node.Left.TraverseFunc(f)
    f(node)
    node.Right.TraverseFunc(f)
}

defer

資源管理比如文件打開和關閉,資源管理和出錯處理,defer是確保函數結束時發生

package main

import "fmt"

func tryDefer()  {
    // 不怕中間 panic 或return
    defer fmt.Println(1)
    defer fmt.Println(2)
    fmt.Println(3)
    panic("error occoured") // return 正常的程序不要panic而是要處理panic
    fmt.Println(4)
}

func main() {
    tryDefer()
}

將fibonacci數列寫到fib.txt中

package main

import (
    "bufio"
    "fmt"
    "gobasic/functional/fib"
    "os"
)


func WriteFile(filename string) {
    file, err := os.Create(filename)
    if err != nil {
        panic(err)
    }
    defer file.Close() //關閉文件
//    先把內容寫到buffer中
    writer:=bufio.NewWriter(file) //打開一個資源,就想著把它關閉掉
    defer writer.Flush() // 就近寫一句關閉
    f:=fib.Fibonacci()
    for i:=0;i<20;i++{
        fmt.Fprintln(writer,f())
    }
}

func main() {
    //tryDefer()
    WriteFile("fib.txt")
}

return 或 panic或函數執行結束它都會執行

defer file.Close()
defer是一個棧
確保調用在函數結束時發生
參數在defer語句時計算,而不是在退出時計算
defer列表為後進先出

  • 何時調用
  • Open/Close
  • Lock/Unlock
  • PrintHeader/PrintFooter

錯誤處理

panic實際上是讓程序掛掉,為了避免這種掛掉我們可以把錯誤日誌打印出來,並return,err只是一個值,

package main

import (
    "bufio"
    "fmt"
    "gobasic/functional/fib"
    "iris_source/core/errors"
    "os"
)

func WriteFile(filename string) {
    //file, err := os.Create(filename)
    /*
        O_CREATE int = syscall.O_CREAT  // 如果不存在將創建一個新文件
        O_EXCL   int = syscall.O_EXCL   // 和O_CREATE配合使用,文件必須不存在
    */
    file, err := os.OpenFile(filename, os.O_EXCL|os.O_CREATE, 0666)
    //自己聲明的error
    err = errors.New("this is a custom error") // 自己聲明的不是一個pathError就走panic了
    if err != nil {
        //panic(err) //掛掉會很難看
        //fmt.Println("file already exists.")
        //fmt.Println("Error: ", err.Error())
        if pathError, ok := err.(*os.PathError); !ok {
            panic(err) //真的不知道是甚麼錯誤,我們就panic吧
        } else {
            fmt.Printf("%s,%s,%s\n", pathError.Op, pathError.Path, pathError.Err)
        }
        return
    }
    writer := bufio.NewWriter(file)
    defer file.Close() //關閉文件
    //    先把內容寫到buffer中,函數執行完需,要執行defer的棧
    defer writer.Flush()
    f := fib.Fibonacci()
    for i := 0; i < 20; i++ {
        fmt.Fprintln(writer, f())
    }
}

func main() {
    WriteFile("fib.txt")
}

統一錯誤處理

package main

import (
    "io/ioutil"
    "net/http"
    "os"
)

func main() {
    http.HandleFunc("/list/", func(writer http.ResponseWriter, request *http.Request) {
        path:= request.URL.Path[len("/list/"):]
        file,err:=os.Open(path)
        if err != nil{
            panic(err)
        }
        defer file.Close()
        all,err:=ioutil.ReadAll(file)
        if err!=nil {
            panic(err)
        }
        writer.Write(all)
    })
    err:=http.ListenAndServe(":8888",nil)
    if err!=nil {
        panic(err)
    }
}

當你輸入個錯誤的網址或不是/list/

//filelistingserver/web.go
package main

import (
    "gobasic/errhandling/filelistingserver/filelisting"
    "net/http"
    "os"

    "github.com/gpmgo/gopm/modules/log"
)

//定義一個類型
type appHandler func(writer http.ResponseWriter, request *http.Request) error

func errWrapper(handler appHandler) func(writer http.ResponseWriter, request *http.Request) {
    return func(writer http.ResponseWriter, request *http.Request) {
        err := handler(writer, request)
        if err != nil {
            log.Warn("Error handling request %s",err.Error())
            code := http.StatusOK
            switch {
            case os.IsNotExist(err):
                code = http.StatusNotFound
            case os.IsPermission(err):
                code = http.StatusForbidden

            default:
                code = http.StatusInternalServerError

            }
            http.Error(writer, http.StatusText(code), code)
        }
    }
}

func main() {
    http.HandleFunc("/list/", errWrapper(filelisting.HandleFileList))
    err := http.ListenAndServe(":8888", nil)
    if err != nil {
        panic(err)
    }
}
// filelistingserver/filelisting/handler.go 類似controller

panic&recover

停止當前函數執行,一直向上返回,執行每一層的defer,如果沒有遇見recover,程序退出;
recover僅在defer調用中使用,獲取panic的值,如果處理不了,可以再次panic

package main

import (
    "fmt"
)

func tryRecover()  {
    defer func() {
        r:=recover()
        if err,ok:=r.(error);ok{
            fmt.Println("Error occurred: ",err)
        }else{
            panic((r))
        }
    }()

    //panic(errors.New("this is an error."))
    //panic(123) 123不是一個error,就是觸發repanic
    b:=0
    a:=5/b
    fmt.Println(a)
}

func main() {
    tryRecover()
}

我們將請請求路徑改成/ 而不是/list/會出現甚麼問題

func main() {
    http.HandleFunc("/", errWrapper(filelisting.HandleFileList))
    err := http.ListenAndServe(":8888", nil)
    if err != nil {
        panic(err)
    }
}
// runtime error: slice bounds out of range
// handler
package filelisting

import (
    "io/ioutil"
    "iris_source/core/errors"
    "net/http"
    "os"
    "strings"
)

const prefix = "/list/"

func HandleFileList(writer http.ResponseWriter, request *http.Request) error { //返回error 有錯誤交給上層去處理,返回就好了
    if strings.Index(request.URL.Path, prefix) != 0 {
        return errors.New("path must start with " + prefix)
    }
    path := request.URL.Path[len(prefix):]
    file, err := os.Open(path)
    if err != nil {
        //panic(err) //這樣雖然程序沒有掛掉,但對用戶的提示不是很友好,我們可以進一步優化
        //http.Error(writer,err.Error(),http.StatusInternalServerError) // 這樣也不合適,我們要把錯誤分為內部和用戶顯示兩部分
        return err
    }
    defer file.Close()
    all, err := ioutil.ReadAll(file)
    if err != nil {
        //panic(err)
        return err
    }
    writer.Write(all)

    return nil
}
// web
package main

import (
    "gobasic/errhandling/filelistingserver/filelisting"
    "log"
    "net/http"
    "os"
)

//定義一個類型
type appHandler func(writer http.ResponseWriter, request *http.Request) error

func errWrapper(handler appHandler) func(writer http.ResponseWriter, request *http.Request) {
    return func(writer http.ResponseWriter, request *http.Request) {
        err := handler(writer, request)
        defer func() {
            if r:=recover();r!=nil {
                //log.Error("panic: %v", r)
                log.Printf("panic: %v",r)
                http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
            }
        }()
        if err != nil {
            //log.Warn("Error handling request %s",err.Error())
            log.Printf("Error handling request %s",err.Error())
            code := http.StatusOK
            switch {
            case os.IsNotExist(err):
                code = http.StatusNotFound
            case os.IsPermission(err):
                code = http.StatusForbidden

            default:
                code = http.StatusInternalServerError

            }
            http.Error(writer, http.StatusText(code), code)
        }
    }
}

func main() {
    http.HandleFunc("/", errWrapper(filelisting.HandleFileList))
    err := http.ListenAndServe(":8888", nil)
    if err != nil {
        panic(err)
    }
}

區分給用戶看的,和不能給用戶看的

package main

import (
    "gobasic/errhandling/filelistingserver/filelisting"
    "log"
    "net/http"
    "os"
)

//定義一個類型
type appHandler func(writer http.ResponseWriter, request *http.Request) error

func errWrapper(handler appHandler) func(writer http.ResponseWriter, request *http.Request) {
    return func(writer http.ResponseWriter, request *http.Request) {
        err := handler(writer, request)
        defer func() {
            if r:=recover();r!=nil {
                //log.Error("panic: %v", r)
                log.Printf("panic: %v",r)
                http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
            }
        }()
        if err != nil {
            //log.Warn("Error handling request %s",err.Error())
            log.Printf("Error handling request %s",err.Error())
            //處理userError
            if userError,ok:=err.(userError);ok{
                http.Error(writer,userError.Message(),http.StatusBadRequest)
                return
            }
            code := http.StatusOK
            switch {
            case os.IsNotExist(err):
                code = http.StatusNotFound
            case os.IsPermission(err):
                code = http.StatusForbidden

            default:
                code = http.StatusInternalServerError

            }
            http.Error(writer, http.StatusText(code), code)
        }
    }
}

//定義用戶看的錯誤
type userError interface {
    error //系統看的
    Message() string //給用戶看的
}

func main() {
    http.HandleFunc("/", errWrapper(filelisting.HandleFileList))
    err := http.ListenAndServe(":8888", nil)
    if err != nil {
        panic(err)
    }
}
// handler.og
package filelisting

import (
    "io/ioutil"
    "net/http"
    "os"
    "strings"
)

const prefix = "/list/"

type userError string

func (e userError) Error() string  {
    return e.Message()
}
func (e userError) Message() string  {
    return string(e)
}

func HandleFileList(writer http.ResponseWriter, request *http.Request) error { //返回error 有錯誤交給上層去處理,返回就好了
    if strings.Index(request.URL.Path, prefix) != 0 {
        //return errors.New("path must start with " + prefix)
        return userError("path must start with " + prefix)
    }
    path := request.URL.Path[len(prefix):]
    file, err := os.Open(path)
    if err != nil {
        //panic(err) //這樣雖然程序沒有掛掉,但對用戶的提示不是很友好,我們可以進一步優化
        //http.Error(writer,err.Error(),http.StatusInternalServerError) // 這樣也不合適,我們要把錯誤分為內部和用戶顯示兩部分
        return err
    }
    defer file.Close()
    all, err := ioutil.ReadAll(file)
    if err != nil {
        //panic(err)
        return err
    }
    writer.Write(all)

    return nil
}

接口定義是各自用各自的,不需要互相看得到
意料之中的:使用error。如:文件打不開,意料之外的:使用panic。如數組越界

主題測試文章,只做測試使用。發佈者:Walker,轉轉請注明出處:https://walker-learn.xyz/archives/6734

(0)
Walker的頭像Walker
上一篇 11小時前
下一篇 2025年2月26日 17:05

相關推薦

  • Go工程師體系課 007

    商品微服務 實體結構說明 本模塊包含以下核心實體: 商品(Goods) 商品分類(Category) 品牌(Brands) 輪播圖(Banner) 品牌分類(GoodsCategoryBrand) 1. 商品(Goods) 描述平台中實際展示和銷售的商品信息。 字段說明 字段名 類型 說明 name String 商品名稱,必填 brand Pointer …

    後端開發 7小時前
    000
  • 編程基礎 0005_錯誤處理進階

    Go 錯誤處理進階 目錄 Go 錯誤處理哲學 error 接口本質 自定義錯誤類型 fmt.Errorf 與 %w 包裝錯誤 errors.Is 和 errors.As 哨兵錯誤模式 錯誤處理最佳實踐 實際項目中的錯誤處理模式 1. Go 錯誤處理哲學 1.1 與 try-catch 的根本區別 在 Java、Python、C++ 等語言中,異常處理依賴 t…

    後端開發 16小時前
    700
  • 編程基礎 0013_Go企業實踐案例精華

    Go 企業實踐案例精華 知識來源:基於以下電子書資料整理- 《Go在百度BFE的應用 for Gopher China》- 《Go在分布式數據庫中的應用》- 《Go在獵豹移動的應用》- 《Golang與高性能DSP競價系統》- 《Go at Google: Language Design in the Service of Software Engineer…

    後端開發 20小時前
    000
  • Go工程師體系課 protoc-gen-validate

    protoc-gen-validate 簡介與使用指南 ✅ 甚麼是 protoc-gen-validate protoc-gen-validate(簡稱 PGV)是一個 Protocol Buffers 插件,用於在生成的 Go 代碼中添加結構體字段的驗證邏輯。 它通過在 .proto 文件中添加 validate 規則,自動為每個字段生成驗證代碼,避免你手…

  • 編程基礎 0001_基礎教程

    go 甚麼是 Go是一門併發支持、垃圾加收的編譯型系統編程語言,具有靜態編譯語言的高性能和動態語言的,主要特點如下 類型安全和內存安全 以非常直觀和極低代價的方案實現高併發 高效的垃圾回收機制 快速編譯(同時解決了 C 語言中頭文件太多的問題) UTF-8 支持 安裝 源碼安裝 標準包安裝 第三方安裝 標準包安裝,一路下一步。安裝完後,會自動添加如下環境變量…

    後端開發 14小時前
    600
簡體中文 繁體中文 English