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
上一篇 2026年3月8日 15:11
下一篇 2026年3月9日 12:56

相關推薦

  • Go工程師體系課 008

    訂單及購物車 先從庫存服務中將 srv 的服務代碼框架複製過來,查找替換對應的名稱(order_srv) 加密技術基礎 對稱加密(Symmetric Encryption) 原理: 使用同一個密鑰進行加密和解密 就像一把鑰匙,既能鎖門也能開門 加密速度快,適合大量數據傳輸 使用場景: 本地文件加密 數據庫內容加密 大量數據傳輸時的內容加密 內部系統間的快速通…

    後端開發 2026年3月7日
    6100
  • Go工程師體系課 020

    性能優化與 pprof 1. 先測量後優化 "Premature optimization is the root of all evil." — Donald Knuth 優化流程:1. 先寫正確的代碼2. 用 Benchmark 確認性能瓶頸3. 用 pprof 定位具體位置4. 優化 → 再測量 → 對比 2. pprof 工具 2.1 在 HTTP …

    後端開發 2026年3月7日
    5800
  • Go工程師體系課 004

    需求分析 後臺管理系統 商品管理 商品列表 商品分類 品牌管理 品牌分類 訂單管理 訂單列表 用戶信息管理 用戶列表 用戶地址 用戶留言 輪播圖管理 電商系統 登錄頁面 首頁 商品搜索 商品分類導航 輪播圖展示 推薦商品展示 商品詳情頁 商品圖片展示 商品描述 商品規格選擇 加入購物車 購物車 商品列表 數量調整 刪除商品 結算功能 用戶中心 訂單中心 我的…

    2026年3月6日
    6000
  • Go工程師體系課 002

    GOPATH 與 Go Modules 的區別 1. 概念 GOPATH 是 Go 的早期依賴管理機制。 所有的 Go 項目和依賴包必須放在 GOPATH 目錄中(默認是 ~/go)。 一定要設置 GO111MODULE=off 項目路徑必須按照 src/包名 的結構組織。 不支持版本控制,依賴管理需要手動處理(例如 go get)。 查找依賴包的順序是 g…

    2026年3月7日
    5900
  • 編程基礎 0002_名庫講解

    名庫講解 goconfig go 語言針對 windows 下常見的 ini 格式的配置文件解析器,該解析器在涵蓋了所有 ini 文件操作的基礎上,又針對 go 語言實際開發過程中遇到的一些需求進行了擴展。該解析器最大的優勢在於對註釋的極佳支持,除此之外,支持多個配置文件覆蓋加載也是非常特別但好用的功能。 提供與 windows api 一模一樣的操作 支持…

    2026年3月6日
    6400
簡體中文 繁體中文 English