#29 Golang container

2021-02-09

堆 Heap (container/heap)

  • 最小堆
  • 完全二叉树
  • 父节点的值小于其子节点的值
type Interface interface {
    sort.Interface
    Push(x any) // add x as element Len()
    Pop() any   // remove and return element Len() - 1.
}

func Fix(h Interface, i int)
func Init(h Interface)
func Pop(h Interface) any
func Push(h Interface, x any)
func Remove(h Interface, i int) any
  1. 接口:
  2. heap.Interface:定义了操作堆的基本方法,包括获取堆中元素数量、比较元素优先级、交换元素位置、添加元素到堆中、从堆中取出元素等。
  3. 函数:
  4. heap.Init(h Interface):对一个实现了 heap.Interface 的堆进行初始化操作。
  5. heap.Push(h Interface, x interface{}):将元素 x 添加到堆 h 中。
  6. heap.Pop(h Interface) interface{}:从堆 h 中取出具有最高优先级的元素并返回。
  7. heap.Remove(h Interface, i int) interface{}:从堆 h 中移除指定索引 i 处的元素并返回。

使用方法:

  1. 定义一个用户自定义的类型,表示要存储在堆中的元素,并为该类型实现 heap.Interface 接口的方法。
  2. 使用 heap.Init() 对堆进行初始化,或者使用 make() 创建一个空的堆。
  3. 使用 heap.Push() 将元素添加到堆中。
  4. 使用 heap.Pop() 从堆中取出具有最高优先级的元素。
  5. 可选地,使用 heap.Remove() 移除堆中的特定元素。

container/heap 包提供了一种方便而高效的方式来管理具有优先级的元素。它在各种场景中都有应用,例如任务调度、事件处理、优先级队列等。通过实现自定义的 heap.Interface 接口方法,可以根据具体需求定义元素的优先级比较方式,使堆适应不同的应用场景。

List (container/list)

type Element
    func (e *Element) Next() *Element
    func (e *Element) Prev() *Element
type List
    func New() *List
    func (l *List) Back() *Element
    func (l *List) Front() *Element
    func (l *List) Init() *List
    func (l *List) InsertAfter(v any, mark *Element) *Element
    func (l *List) InsertBefore(v any, mark *Element) *Element
    func (l *List) Len() int
    func (l *List) MoveAfter(e, mark *Element)
    func (l *List) MoveBefore(e, mark *Element)
    func (l *List) MoveToBack(e *Element)
    func (l *List) MoveToFront(e *Element)
    func (l *List) PushBack(v any) *Element
    func (l *List) PushBackList(other *List)
    func (l *List) PushFront(v any) *Element
    func (l *List) PushFrontList(other *List)
    func (l *List) Remove(e *Element) any

container/list 包是 Go 语言标准库中的一个包,提供了双向链表(doubly linked list)的实现。双向链表是一种数据结构,其中每个节点都包含对前一个节点和后一个节点的引用。

container/list 包提供了 List 类型,该类型代表了一个双向链表。List 类型的对象可以用来存储和操作任意类型的元素。它支持在链表的前部和后部添加和删除元素,以及在链表中搜索、遍历和修改元素。

container/list 包的主要功能和方法包括:

  1. List 类型的方法:
  2. PushFront(v interface{}) *Element:在链表的前部插入一个值为 v 的元素,并返回该元素的指针。
  3. PushBack(v interface{}) *Element:在链表的后部插入一个值为 v 的元素,并返回该元素的指针。
  4. Remove(e *Element) interface{}:从链表中移除指定元素 e,并返回该元素的值。
  5. Front() *Element:返回链表的第一个元素。
  6. Back() *Element:返回链表的最后一个元素。
  7. Len() int:返回链表中元素的数量。
  8. Element 类型的方法:
  9. Next() *Element:返回当前元素的下一个元素。
  10. Prev() *Element:返回当前元素的前一个元素。
  11. Value() interface{}:返回当前元素的值。

使用 container/list 包的步骤通常是:

  1. 导入 container/list 包。
  2. 创建一个新的链表对象:myList := list.New()
  3. 使用 PushFront()PushBack() 方法向链表中添加元素。
  4. 使用 Front()Back() 方法获取链表的第一个或最后一个元素。
  5. 使用 Next()Prev() 方法遍历链表中的元素。
  6. 使用 Remove() 方法从链表中移除特定的元素。

container/list 包提供了一种简单而灵活的方式来管理和操作链表。它适用于需要频繁进行元素插入、删除和遍历的场景。然而,由于链表是一种指针结构,因此在随机访问和索引元素方面的性能较差。如果需要更高效的随机访问和索引操作,可以考虑使用切片(slice)或其他数据结构。

Ring (container/ring)

type Ring
    func New(n int) *Ring
    func (r *Ring) Do(f func(any))
    func (r *Ring) Len() int
    func (r *Ring) Link(s *Ring) *Ring
    func (r *Ring) Move(n int) *Ring
    func (r *Ring) Next() *Ring
    func (r *Ring) Prev() *Ring
    func (r *Ring) Unlink(n int) *Ring

container/ring 包是 Go 语言标准库中的一个包,提供了环形链表(circular list)的实现。环形链表是一种特殊的链表,其中最后一个节点的下一个节点是第一个节点,形成一个环状结构。

container/ring 包提供了 Ring 类型,该类型代表了一个环形链表。Ring 类型的对象可以用来存储和操作任意类型的元素。它支持在环形链表中向前和向后遍历、插入和删除元素,以及计算环形链表的长度。

container/ring 包的主要功能和方法包括:

  1. Ring 类型的方法:
  2. New(n int) *Ring:创建一个具有 n 个节点的新环形链表,并返回指向第一个节点的指针。
  3. Next() *Ring:返回下一个节点的指针。
  4. Prev() *Ring:返回上一个节点的指针。
  5. Move(n int) *Ring:将当前指针向前(负值)或向后(正值)移动 n 步,并返回新的指针。
  6. Link(s *Ring) *Ring:将当前环形链表连接到另一个环形链表 s 的前面,并返回连接后的环形链表。
  7. Unlink(n int) *Ring:从当前环形链表中移除下一个 n 个节点,并返回剩余的环形链表。
  8. Ring 类型的属性:
  9. Len() int:返回环形链表中节点的数量。
  10. Value() interface{}:返回当前节点的值。

使用 container/ring 包的步骤通常是:

  1. 导入 container/ring 包。
  2. 使用 New() 方法创建一个新的环形链表,并指定节点的数量。
  3. 使用 Next()Prev() 方法在环形链表中遍历节点。
  4. 使用 Value() 方法获取当前节点的值。
  5. 使用 Move() 方法向前或向后移动指针。
  6. 使用 Link() 方法将两个环形链表连接起来,或使用 Unlink() 方法移除节点。

container/ring 包提供了一种简单而灵活的方式来处理环形链表。它适用于需要在环形结构中进行元素遍历和操作的场景。可以使用环形链表来实现循环队列、轮转算法等应用。但是,与普通链表相比,环形链表的插入和删除操作相对较慢,因此如果需要频繁进行插入和删除操作,可能有更适合的数据结构选择。

#27 Golang 反射简单示例

2021-02-09

通过反射(Reflection),我们可以在程序运行时,动态的增删该一些信息(类型,方法,变量),用来完成一些特殊的任务。
主要是实现不同类型都支持的一些方法。在 ORM,序列化等方面都有应用。

这种动态获取信息的能力让程序的开发变得更加灵活,但也会损失部分性能,毕竟需要进行很多类型检查、值转换等操作。
而且,滥用的话会导致程序变得复杂,可读性下降。

示例 1

type Person struct {
    Name string
    Age  int
}

func Insert(db *sql.DB, v interface{}) error {
    value := reflect.ValueOf(v)
    typ := reflect.TypeOf(v)

    sql := "INSERT INTO persons ("
    placeholders := "VALUES ("
    for i := 0; i < typ.Elem().NumField(); i++ {
        fieldName := typ.Elem().Field(i).Name
        sql += fmt.Sprintf("%s, ", fieldName)
        placeholders += "?, "
    }
    sql = sql[:len(sql)-2] + ")"
    placeholders = placeholders[:len(placeholders)-2] + ")"

    args := make([]interface{}, typ.Elem().NumField())
    for i := 0; i < typ.Elem().NumField(); i++ {
        args[i] = value.Elem().Field(i).Interface()
    }

    _, err := db.Exec(sql+placeholders, args...)
    return err
}

示例 2

package main

import (
    "fmt"
    "reflect"
)

type Person struct {
    Name    string
    Age     int
    Address string
}

// 定义结构体方法
func (p Person) Hello() string {
    return "Hello, " + p.Name
}

func main() {
    p := Person{
        Name:    "Alice",
        Age:     30,
        Address: "123 Main St",
    }

    fmt.Println("获取变量的反射类型:===============")
    pType := reflect.TypeOf(p)                  // &reflect.rtype
    fmt.Printf("Type: %s, %#v\n", pType, pType) // main.Person
    // fmt.Println("Type:", pType.Elem()) // panic: reflect: Elem of invalid type main.Person
    pType2 := reflect.TypeOf(&p)
    fmt.Printf("Type: %s, %#v\n", pType2, pType2)               // *main.Person
    fmt.Printf("Type: %s, %#v\n", pType2.Elem(), pType2.Elem()) // main.Person (指针类型的基础类型)

    fmt.Println("获取变量的反射值:===============")
    pValue := reflect.ValueOf(p) // main.Person
    fmt.Printf("Value: %s, %#v\n", pValue, pValue)
    pValue2 := reflect.ValueOf(&p)
    fmt.Printf("Value: %s, %#v\n", pValue2, pValue2) // 地址
    fmt.Printf("Value: %s, %#v\n", pValue2.Elem(), pValue2.Elem())

    fmt.Println("遍历结构体字段:===============")
    for i := 0; i < pType.NumField(); i++ {
        field := pType.Field(i)
        value := pValue.Field(i)
        fmt.Printf("%s: %v\n", field.Name, value.Interface())
    }

    fmt.Println("修改结构体字段值:===============")
    ageField1 := pValue.FieldByName("Age")
    fmt.Printf("Value: %#v\n", ageField1)
    // ageField1.SetInt(31) // panic: reflect: reflect.Value.SetInt using unaddressable value
    // ageField1.Elem().SetInt(31) // panic: reflect: call of reflect.Value.Elem on int Value
    ageField2 := pValue2.Elem().FieldByName("Age")
    fmt.Printf("Value: %#v\n", ageField2)
    ageField2.SetInt(31) // 只有指针类型才能通过反射修改
    fmt.Println("New Age:", p.Age)

    fmt.Println("调用结构体方法:===============")
    helloMethod := pValue.MethodByName("Hello")
    result := helloMethod.Call(nil)
    fmt.Println(result)

    // panic: reflect: call of reflect.Value.FieldByName on ptr Value
    pValue2.FieldByName("Age")

    // panic: reflect: reflect.Value.SetInt using unaddressable value
    // a := reflect.Indirect(pValue)
    b := reflect.Indirect(pValue2)
    b.FieldByName("Age").SetInt(33)
    fmt.Printf("%#v\n", b)
}
  • reflect.TypeOf:获取变量的类型
  • reflect.ValueOf:获取变量的值

  • Type.NumField:获取结构体字段数量
  • Type.Field:获取结构体字段信息

  • Value.Field:获取结构体字段的值

  • Value.FieldByName:根据字段名获取结构体字段的值
  • Value.SetInt:设置整数类型字段的值
  • Value.MethodByName:根据方法名获取结构体方法
  • Value.Call:调用结构体方法

#26 GO111MODULE 是什么?

2021-02-04

每一个 Golang 初学者都会遇到 GOPATH 和 Go Module 的问题,至少在最近的一两年是这样的。
简单的说,就是由于 Golang 诞生于谷歌,所以早期的项目工程化受谷歌内部开发流程影响很大。谷歌内部不同项目的代码放在一起,方便相互引用。GOPATH 就是这种代码管理方式的体现,所有的包都放在一个固定的开发目录下。
但是谷歌外面的世界,可能是受开源生态的影响,我们习惯将代码拆分成不同的包,分散在不通的仓库,需要什么包就导入什么包。所以虽然有一些人一直在吹捧 GOPATH 模式,但是大多数人还是喜欢传统的包模式。
所以在 Go Module 之前,官方或者社区也都有出一些解决方案,其中最有名的是 depvender。但随着最终方案 Go Module 的确定,他们已经完成了历史使命,而我最近两个月才开始学 Go,当然就跳过他们了。

#25 Golang HTTP Server

2021-02-01

最简模式

package main

import (
    "fmt"
    "net/http"
)

func hello(w http.ResponseWriter, req *http.Request) {
    fmt.Fprintf(w, "hello world\n")
}

func main() {
    http.HandleFunc("/", hello)

    http.ListenAndServe(":8080", nil)
}

重点是记住这两点:

  1. func hello(w http.ResponseWriter, req *http.Request)
  2. http.HandleFunc 注册一个函数到指定路径上

ListenAndServe 流程分析

https://github.com/golang/go/blob/master/src/net/http/server.go#L3240
https://github.com/golang/go/blob/master/src/net/http/server.go#L2976

func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

func (srv *Server) ListenAndServe() error {
    if srv.shuttingDown() {
        return ErrServerClosed
    }
    addr := srv.Addr
    if addr == "" {
        addr = ":http"
    }
    ln, err := net.Listen("tcp", addr)
    if err != nil {
        return err
    }
    return srv.Serve(ln)
}

主要的逻辑:

  1. net.Listen("tcp", addr) -> net.Listener
  2. for 循环:
  3. net.Listener.Accept() -> net.Conn
  4. http.Server.newConn(conn) -> http.Conn
  5. go http.Conn.serve(ctx)
  6. goroutine 中又是一个 for 循环:
  7. http.Conn.readRequest(ctx) -> http.response
  8. serverHandler{c.server}.ServeHTTP(w, w.req)
  9. 最后这个 serverHandler.ServeHTTP 就比较关键了:
    优先采用 http.Server 上的 Handler;
    如果没有,则用默认的 http.DefaultServeMux 实现简单的 URL 路由。
    这也是为什么 ListenAndServe 传了个 nil 进来,srv.Handlernil 就用 DefaultServeMux
type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry
    es    []muxEntry // slice of entries sorted from longest to shortest.
    hosts bool       // whether any patterns contain hostnames
}

func (mux *ServeMux) match(path string) (h Handler, pattern string)
func (mux *ServeMux) redirectToPathSlash(host, path string, u *url.URL) (*url.URL, bool)
func (mux *ServeMux) shouldRedirectRLocked(host, path string) bool
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string)
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string)
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)
func (mux *ServeMux) Handle(pattern string, handler Handler)
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request))

DefaultServeMux

// ==================================================================
// 封装 handler,添加到路由表 =========================================
// ==================================================================
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    if handler == nil {
        panic("http: nil handler")
    }
    // https://github.com/golang/go/blob/master/src/net/http/server.go#L2505
    // func (mux *ServeMux) Handle(pattern string, handler Handler)
    // 把 handler 注册到 mux.m (map[string]muxEntry) 上
    // muxEntry{h: handler, pattern: pattern}
    mux.Handle(pattern, HandlerFunc(handler))
}

// 封装普通函数为 Handler
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

// ==================================================================
// 路由匹配 =========================================================
// ==================================================================
ServeHTTP -> Handler -> handler -> match
          -> redirectToPathSlash -> shouldRedirectRLocked -> 跳转(http.RedirectHandler)

默认路由规则:

  1. 优先完整匹配
  2. URL 前缀匹配,最长的路由规则优先

另一种方式 (http.Handle)

不用 http.HandleFunc,而是传入一个 Handler(实现 ServeHTTP 方法的结构体)。

package main

import (
    "fmt"
    "net/http"
)

type HelloHandler struct {
    content string
}

func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    fmt.Fprintf(w, h.content)
}

func main() {
    http.Handle("/", &HelloHandler{"hello world\n"})

    http.ListenAndServe(":8080", nil)
}
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }

http.Handlehttp.HandleFunc 都只是给默认路由(DefaultServeMux)上注册一个规则,知道这一点就可以了。

重点是 mux.HandleHandler 接口。

还可以这样

package main

import (
    "fmt"
    "net/http"
)

type HelloHandler struct {
    content string
}

func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    fmt.Fprintf(w, h.content)
}

func main() {
    http.ListenAndServe(":8080", &HelloHandler{"hello world\n"})
}

知道背后的流程就豁然开朗了。

当然,这样也是可以的:

func hello(w http.ResponseWriter, req *http.Request) {
    fmt.Fprintf(w, "hello world\n")
}
func main() {
    http.ListenAndServe(":8080", http.HandlerFunc(hello))
}

整理总结

  • http.Conn
  • http.Server
  • 到处都是 Hander(接口),路由(mux)也是 Handler
  • func(w http.ResponseWriter, req *http.Request)

实现正则路由

GitHub 上一搜索 golang mux golang router 就有,这是几个可以参考的方案:

  1. gorilla/mux shield.io
  2. julienschmidt/httprouter shield.io
  3. go-chi/chi shield.io
  4. celrenheit/lion shield.io
  5. beego/mux shield.io

我自己实现一个简单的路由机制,参考 Django:

<name> OR <converter:name>

其中:converter 内置支持 str, int, slug, uuid, path, 可以自定义,默认是 str; name 可以是合法的 Go 变量名。


中间件

通过 Handler 套娃实现中间件:

type LogMiddleware struct {
    handler Handler
}
func (this LogMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    start := time.Now()
    this.handler.ServeHTTP(w, r)
    fmt.Printf("%s %s %v\n", r.Method, r.URL.RequestURI(), time.Since(start))
}
mux := ...
http.ListenAndServe(":8000", LogMiddleware{mux})

系统内置 Handler:

grep -E "func.+ServeHTTP" src/net/http -R | grep -Fv "_test.go"
src/net/http/cgi/host.go:func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
src/net/http/fs.go:func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) {
src/net/http/httputil/reverseproxy.go:func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
src/net/http/pprof/pprof.go:func (name handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
src/net/http/server.go:func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
src/net/http/server.go:func (rh *redirectHandler) ServeHTTP(w ResponseWriter, r *Request) {
src/net/http/server.go:func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
src/net/http/server.go:func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
src/net/http/server.go:func (h *timeoutHandler) ServeHTTP(w ResponseWriter, r *Request) {
src/net/http/server.go:func (globalOptionsHandler) ServeHTTP(w ResponseWriter, r *Request) {
src/net/http/server.go:func (h initALPNRequest) ServeHTTP(rw ResponseWriter, req *Request) {
src/net/http/triv.go:func (ctr *Counter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
src/net/http/triv.go:func (ch Chan) ServeHTTP(w http.ResponseWriter, req *http.Request) {

有待分析。

优雅重启


参考资料与拓展阅读

#24 转载:Golang 自举

2021-01-30

Google最近公布了实现Go 1.5自举(Bootstrap)的计划。相关文档的作者是Go核心开发者Russ Cox,他在Go语言上已经耕耘了接近6年。据Russ介绍,Google就“如何从Go源码树中去除所有的C程序”已经酝酿了一年。

#23 Golang SMTP Login 认证

2021-01-16

官方库里面只看到 PlainAuth 和 CramMd5Auth 两种认证方法。

type loginAuth struct {
    username, password string
}

func LoginAuth(username, password string) smtp.Auth {
    return &loginAuth{username, password}
}

func (a *loginAuth) Start(server *smtp.ServerInfo) (proto string, toServer []byte, err error) {
    return "LOGIN", []byte{}, nil
}

func (a *loginAuth) Next(fromServer []byte, more bool) (toServer []byte, err error) {
    if more {
        switch strings.ToUpper(string(fromServer)) {
        case "USERNAME:":
            return []byte(a.username), nil
        case "PASSWORD:":
            return []byte(a.password), nil
        default:
            return nil, fmt.Errorf("unknown command %v", fromServer)
        }
    }
    return nil, nil
}

#22 Go SMTP 发送邮件

2021-01-15
package main

import (
    "crypto/tls"
    "errors"
    "fmt"
    "net"
    "net/smtp"
)

// ============================================================================

type Transaction struct {
    Host      string
    Port      uint16
    LocalName string
    TlsEnable bool
    Username  string
    Password  string
    MailFrom  string
    RcptTo    []string
    Data      []byte
}

func NewTransaction() Transaction {
    trans := Transaction{}
    return trans
}

func (trans Transaction) Send() error {
    addr := fmt.Sprintf("%s:%d", trans.Host, trans.Port)

    // SendMail(addr string, a Auth, from string, to []string, msg []byte) error
    c, err := smtp.Dial(addr)
    if err != nil {
        return err
    }
    defer c.Close()

    c.Hello(trans.LocalName)

    if ok, _ := c.Extension("STARTTLS"); ok {
        serverName, _, _ := net.SplitHostPort(addr)
        config := &tls.Config{
            InsecureSkipVerify: true,
            ServerName:         serverName,
        }
        if err = c.StartTLS(config); err != nil {
            return err
        }
    } else {
        fmt.Printf("smtp: server doesn't support STARTTLS\n")
    }

    if trans.Username != "" {
        if ok, _ := c.Extension("AUTH"); !ok {
            return errors.New("smtp: server doesn't support AUTH")

        }
        auth := smtp.PlainAuth("", trans.Username, trans.Password, trans.Host)
        if err = c.Auth(auth); err != nil {
            fmt.Println("smtp: authentication failed")
            return err
        }
    }

    if err = c.Mail(trans.MailFrom); err != nil {
        return err
    }

    for _, addr := range trans.RcptTo {
        if err = c.Rcpt(addr); err != nil {
            return err
        }
    }

    w, err := c.Data()
    if err != nil {
        return err
    }

    _, err = w.Write(trans.Data)
    if err != nil {
        return err
    }

    err = w.Close()
    if err != nil {
        return err
    }

    return c.Quit()
}

// ============================================================================

func main() {
    from := "ninedoors@126.com"
    to := "ninedoors@qq.com"
    msg := []byte{}
    fmt.Println("============================================================")
    trans := Transaction{
        "smtp.126.com", 25, "peach", false,
        from, "password",
        from, []string{to},
        msg,
    }
    trans.Send()
}
  1. net/smtp 没有发现有好的日志实现,我只能定制了一个版本实现了日志
  2. smtp.Client -> textproto.Conn -> textproto.Writer

#21 Golang make

2020-12-28
m := make(map[string]int, 5)

发现 len(m) = 0 之后我觉得有必要重新复习一下 make 函数了。

make 的作用:分配内存,初始化,返回值(不是指针)。
PS:返回值的说明:返回的是这三种引用类型本身(指针),而不是指向这三个引用的指针。
PS:new 就会返回指针。还有,new 不限类型,不初始化。

  1. slice -> len, cap
  2. map -> 预留大约 n 个元素的空间(具体分配空间的规则不清楚)
  3. chan -> buffer size

The built-in function make takes a type T, which must be a slice, map or channel type, optionally followed by a type-specific list of expressions. It returns a value of type T (not *T). The memory is initialized as described in the section on initial values.

Call             Type T     Result

make(T, n)       slice      slice of type T with length n and capacity n
make(T, n, m)    slice      slice of type T with length n and capacity m

make(T)          map        map of type T
make(T, n)       map        map of type T with initial space for approximately n elements

make(T)          channel    unbuffered channel of type T
make(T, n)       channel    buffered channel of type T, buffer size n

Each of the size arguments n and m must be of integer type or an untyped constant. A constant size argument must be non-negative and representable by a value of type int; if it is an untyped constant it is given type int. If both n and m are provided and are constant, then n must be no larger than m. If n is negative or larger than m at run time, a run-time panic occurs.

s := make([]int, 10, 100)       // slice with len(s) == 10, cap(s) == 100
s := make([]int, 1e3)           // slice with len(s) == cap(s) == 1000
s := make([]int, 1<<63)         // illegal: len(s) is not representable by a value of type int
s := make([]int, 10, 0)         // illegal: len(s) > cap(s)
c := make(chan int, 10)         // channel with a buffer size of 10
m := make(map[string]int, 100)  // map with initial space for approximately 100 elements

Calling make with a map type and size hint n will create a map with initial space to hold n map elements. The precise behavior is implementation-dependent.

#20 Golang: 命令行参数解析(flag

2020-12-25

Go 自带 flag 包,可以用于解析命令行参数。但用起来非常繁琐,更谈不上优雅,远不如 Python 的 argparse 包简洁明了,易于使用。

语法

类型

  • flag.Flag
  • flag.FlagSet
  • flag.Getter
  • flag.Value

方法

func Bool(name string, value bool, usage string) *bool
func BoolVar(p *bool, name string, value bool, usage string)

func Duration(name string, value time.Duration, usage string) *time.Duration
func DurationVar(p *time.Duration, name string, value time.Duration, usage string)

func Float64(name string, value float64, usage string) *float64
func Float64Var(p *float64, name string, value float64, usage string)

func Func(name, usage string, fn func(string) error)

func Int(name string, value int, usage string) *int
func IntVar(p *int, name string, value int, usage string)

func Int64(name string, value int64, usage string) *int64
func Int64Var(p *int64, name string, value int64, usage string)

func String(name string, value string, usage string) *string
func StringVar(p *string, name string, value string, usage string)

func Uint(name string, value uint, usage string) *uint
func UintVar(p *uint, name string, value uint, usage string)

func Uint64(name string, value uint64, usage string) *uint64
func Uint64Var(p *uint64, name string, value uint64, usage string)

func Var(value Value, name string, usage string)
  1. 带 Var 结尾的方法表示将值绑定到一个变量上。
func Arg(i int) string  // i-th argument
func Args() []string    // non-flag arguments

func NArg() int         // number of arguments remaining after flags have been processed
func NFlag() int        // number of command-line flags that have been set

func Parse()
func Parsed() bool
func Set(name, value string) error

func PrintDefaults()    // 帮助信息
func UnquoteUsage(flag *Flag) (name string, usage string)  // 返回 flag 的名称和用法

func Visit(fn func(*Flag))
func VisitAll(fn func(*Flag))

type ErrorHandling

FlagSet

func NewFlagSet(name string, errorHandling ErrorHandling) *FlagSet

上面的方法 FlagSet 也都有一份。

示例:Todo

package main

import (
    "flag"
    "fmt"
)

var (
    addFlag       = flag.String("add", "", "Add a new task")
    listFlag      = flag.Bool("list", false, "List all tasks")
    removeFlag    = flag.Int("remove", -1, "Remove a task by its index")
    removeAllFlag = flag.Bool("remove-all", false, "Remove all tasks")
    doneFlag      = flag.Int("done", -1, "Mark a task as done by its index")
    undoneFlag    = flag.Int("undone", -1, "Mark a task as undone by its index")
)

func main() {
    flag.Parse()
    args := make(map[string]interface{}, 10)
    args["add"] = *addFlag
    args["list"] = *listFlag
    args["remove"] = *removeFlag
    args["remove"] = *removeAllFlag
    args["done"] = *doneFlag
    args["undone"] = *undoneFlag
    fmt.Printf("%#v\n", args)
    // map[string]interface {}{"add":"", "done":-1, "list":false, "remove":false, "undone":-1}
    fmt.Println("Other arguments:", flag.Args())
    // Other arguments: []
}

参考资料与拓展阅读