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
上一篇 14小時前
下一篇 2025年2月26日 17:05

相關推薦

  • 編程基礎 0003_Web_beego開發

    Web 開發之 Beego 使用 go get 安裝 bee 工具與 beego Bee Beego 使用 bee 工具初始化 Beego 項目 在$GOPATH/src 目錄下執行 bee create myapp 使用 bee 工具熱編譯 Beego 項目 在$GOPATH/src/myapp 目錄下執行 bee start myapp // hello…

    後端開發 17小時前
    500
  • Go工程師體系課 018

    API 網關與持續部署入門(Kong & Jenkins) 對應資料目錄《第 2 章 Jenkins 入門》《第 3 章 通過 Jenkins 部署服務》,整理 Kong 與 Jenkins 在企業級持續交付中的實戰路徑。即便零基礎,也能順著步驟搭建出自己的網關 + 持續部署流水線。 課前導覽:什麼是 API 網關 API 網關位於客戶端與後端微服務…

  • Go日積月累 go-s3-upload-example

    Go 語言實現文件上傳到 AWS S3 示例 本示例演示如何使用 Go 和 AWS SDK v2 將本地文件上傳到 Amazon S3。 🧾 前提條件 已擁有 AWS 賬號; 已創建 S3 Bucket; 已配置 AWS 憑證(通過 aws configure 或設置環境變量); 已準備本地文件(如 test.jpg); 📦 安裝依賴 go mod init…

  • Go工程師體系課 016

    Kubernetes 入門 —— Go 微服務部署與編排 一、Kubernetes 核心概念 1.1 什麼是 Kubernetes Kubernetes(簡稱 K8s)是 Google 開源的容器編排平臺,用於自動化部署、擴展和管理容器化應用。如果說 Docker 解決了"如何打包和運行單個容器"的問題,那麼 K8s 解決的是"如何管理成百上千個容器"的問題…

  • Go工程師體系課 017

    限流、熔斷與降級入門(含 Sentinel 實戰) 結合課件第 3 章(3-1 ~ 3-9)的視頻要點,整理一套面向初學者的服務保護指南,幫助理解“爲什麼需要限流、熔斷和降級”,以及如何用 Sentinel 快速上手。 學習路線速覽 3-1 理解服務雪崩與限流、熔斷、降級的背景 3-2 Sentinel 與 Hystrix 對比,明確技術選型 3-3 Sen…

簡體中文 繁體中文 English