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日
    6300
  • 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日
    5900
  • Go工程师体系课 004

    需求分析 后台管理系统 商品管理 商品列表 商品分类 品牌管理 品牌分类 订单管理 订单列表 用户信息管理 用户列表 用户地址 用户留言 轮播图管理 电商系统 登录页面 首页 商品搜索 商品分类导航 轮播图展示 推荐商品展示 商品详情页 商品图片展示 商品描述 商品规格选择 加入购物车 购物车 商品列表 数量调整 删除商品 结算功能 用户中心 订单中心 我的…

    2026年3月6日
    6500
  • Go工程师体系课 002

    GOPATH 与 Go Modules 的区别 1. 概念 GOPATH 是 Go 的早期依赖管理机制。 所有的 Go 项目和依赖包必须放在 GOPATH 目录中(默认是 ~/go)。 一定要设置 GO111MODULE=off 项目路径必须按照 src/包名 的结构组织。 不支持版本控制,依赖管理需要手动处理(例如 go get)。 查找依赖包的顺序是 g…

    2026年3月7日
    6200
  • 编程基础 0002_名库讲解

    名库讲解 goconfig go 语言针对 windows 下常见的 ini 格式的配置文件解析器,该解析器在涵盖了所有 ini 文件操作的基础上,又针对 go 语言实际开发过程中遇到的一些需求进行了扩展。该解析器最大的优势在于对注释的极佳支持,除此之外,支持多个配置文件覆盖加载也是非常特别但好用的功能。 提供与 windows api 一模一样的操作 支持…

    2026年3月6日
    6600
简体中文 繁体中文 English