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