美文网首页
《Go入门指南》一些笔记

《Go入门指南》一些笔记

作者: raincoffee | 来源:发表于2019-06-15 14:33 被阅读0次

    《Go入门指南》笔记

    Map 删除:delete(map1, "Washington")

    假设我们想获取一个 map 类型的切片,我们必须使用两次 make() 函数,第一次分配切片,第二次分配 切片中每个 map 元素

    container:list/ring

    runtime

    • reflect: 实现通过程序运行时反射,让程序操作任意类型的变量。
    • runtime: Go 程序运行时的交互操作,例如垃圾回收和协程创建。
    • regex: 正则
    • sync.Mutex:加锁
    • godoc -http=:6060 -goroot="." 可以查看文档

    如何强制使用工厂方法?

    Go 语言不支持面向对象编程语言中那样的构造子方法,但是可以很容易的在 Go 中实现 “构造子工厂” 方法。为了方便通常会为类型定义一个工厂,按惯例,工厂的名字以 new 或 New 开头。

    将对象声明称私有(即小写开头),调用Newxx()方法。

    // 注意为私有方法
    type matrix struct {
        ...
    }
    func NewMatrix(params) *matrix {
        m := new(matrix) // 初始化 m
        return m
    }
    

    匿名字段和内嵌结构体?

    当两个字段拥有相同的名字(可能是继承来的名字)时该怎么办呢?

    1. 外层名字会覆盖内层名字(但是两者的内存空间都保留),这提供了一种重载字段或方法的方式;
    2. 如果相同的名字在同一级别出现了两次,如果这个名字被程序使用了,将会引发一个错误(不使用没关系)。没有办法来解决这种问题引起的二义性,必须由程序员自己修正。

    在一个对象 obj 被从内存移除前执行一些特殊操作?

    如果需要在一个对象 obj 被从内存移除前执行一些特殊操作,比如写到日志文件中,可以通过如下方式调用函数来实现:

    runtime.SetFinalizer(obj, func(obj *typeObj))
    

    func(obj *typeObj) 需要一个 typeObj 类型的指针参数 obj,特殊操作会在它上面执行。func 也可以是一个匿名函数。

    在对象被 GC 进程选中并从内存中移除以前,SetFinalizer 都不会执行,即使程序正常结束或者发生错误

    接口命名约定?

    (按照约定,只包含一个方法的)接口的名字由方法名加 [e]r 后缀组成,例如 PrinterReaderWriterLoggerConverter 等等。还有一些不常用的方式(当后缀 er不合适时),比如 Recoverable,此时接口名以 able 结尾,或者以 I 开头(像 .NETJava 中那样)

    一个接口可以包含一个或多个其他的接口,这相当于直接将这些内嵌接口的方法列举在外层接口中一样。

    接口类型判断?

    Type-switch

    func classifier(items ...interface{}) {
        for i, x := range items {
            switch x.(type) {
            case bool:
                fmt.Printf("Param #%d is a bool\n", i)
            case float64:
                fmt.Printf("Param #%d is a float64\n", i)
            case int, int64:
                fmt.Printf("Param #%d is a int\n", i)
            case nil:
                fmt.Printf("Param #%d is a nil\n", i)
            case string:
                fmt.Printf("Param #%d is a string\n", i)
            default:
                fmt.Printf("Param #%d is unknown\n", i)
            }
        }
    }
    

    如何使用接口来获取通用型?

    func Sort(data Sorter) {
        for pass := 1; pass < data.Len(); pass++ {
            for i := 0;i < data.Len() - pass; i++ {
                if data.Less(i+1, i) {
                    data.Swap(i, i + 1)
                }
            }
        }
    }
    type Sorter interface {
        Len() int
        Less(i, j int) bool
        Swap(i, j int)
    }
    

    现在如果我们想对一个 int 数组进行排序,所有必须做的事情就是:为数组定一个类型并在它上面实现 Sorter接口的方法:

    type IntArray []int
    func (p IntArray) Len() int           { return len(p) }
    func (p IntArray) Less(i, j int) bool { return p[i] < p[j] }
    func (p IntArray) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
    
    data := []int{74, 59, 238, -784, 9845, 959, 905, 0, 0, 42, 7586, -5467984, 7586}
    a := sort.IntArray(data) //conversion to type IntArray from package sort
    sort.Sort(a)
    

    切片注意事项?

    复制数据切片至空接口切片,必须要显式复制

    切片的底层指向一个数组,该数组的实际容量可能要大于切片所定义的容量。只有在没有任何切片指向的时候,底层的数组内存才会被释放,这种特性有时会导致程序占用多余的内存。

    Map注意事项?

    Map使用make进行初始化 不要使用new;假设我们想获取一个 map 类型的切片,我们必须使用两次 make() 函数,第一次分配切片,第二次分配 切片中每个 map 元素

    判断key是否存在 : val1, isPresent = map1[key1]

    map中删除key: delete(map1, key1)

    Map排序:如果你想为 map 排序,需要将 key(或者 value)拷贝到一个切片,再对切片排序(使用 sort 包),然后可以使用切片的 for-range 方法打印出所有的 key 和 value

    方法相关小技巧?

    在 Go 中,类型就是类(数据和关联的方法)。Go 不知道类似面向对象语言的类继承的概念。继承有两个好处:代码复用和多态。

    在 Go 中,代码复用通过组合和委托实现,多态通过接口的使用来实现:有时这也叫 组件编程(Component Programming)

    许多开发者说相比于类继承,Go 的接口提供了更强大、却更简单的多态行为。

    读写数据?

    如何读写一个文件?

    从命令行读入数据?

    文件拷贝 io.Copy()

    从命令行读取参数 : os.Args[1:]

    os.OpenFile outputWriter := bufio.NewWriter(outputFile)

    https://learnku.com/docs/the-way-to-go/122-file-reading-and-writing/3662

    错误处理?

    永远不要忽略错误,否则可能会导致程序崩溃!!

    创建error: fmt.Errorf() / errors.New()

    当发生像数组下标越界或类型断言失败这样的运行错误时,Go 运行时会触发运行时 panic,伴随着程序的崩溃抛出一个 runtime.Error 接口类型的值。这个错误值有个 RuntimeError() 方法用于区别普通错误。

    panic 会导致栈被展开直到 defer 修饰的 recover () 被调用或者程序中止。

    package main
    
    import (
        "fmt"
    )
    
    func badCall() {
        panic("bad end")
    }
    
    func test() {
        defer func() {
            if e := recover(); e != nil {
                fmt.Printf("Panicing %s\r\n", e)
            }
        }()
        badCall()
        fmt.Printf("After bad call\r\n") // <-- wordt niet bereikt
    }
    
    func main() {
        fmt.Printf("Calling test\r\n")
        test()
        fmt.Printf("Test completed\r\n")
    }
    
    Calling test
    Panicing bad end
    Test completed
    
    

    自定义包中的错误处理和 panicking?

    这是所有自定义包实现者应该遵守的最佳实践:

    1)在包内部,总是应该从 panic 中 recover:不允许显式的超出包范围的 panic ()

    2)向包的调用者返回错误值(而不是 panic)。

    在包内部,特别是在非导出函数中有很深层次的嵌套调用时,对主调函数来说用 panic 来表示应该被翻译成错误的错误场景是很有用的(并且提高了代码可读性)。

    包内panic;包外error

    // parse.go
    package parse
    
    import (
        "fmt"
        "strings"
        "strconv"
    )
    
    // A ParseError indicates an error in converting a word into an integer.
    type ParseError struct {
        Index int      // The index into the space-separated list of words.
        Word  string   // The word that generated the parse error.
        Err error // The raw error that precipitated this error, if any.
    }
    
    // String returns a human-readable error message.
    func (e *ParseError) String() string {
        return fmt.Sprintf("pkg parse: error parsing %q as int", e.Word)
    }
    
    // Parse parses the space-separated words in in put as integers.
    func Parse(input string) (numbers []int, err error) {
        defer func() {
            if r := recover(); r != nil {
                var ok bool
                err, ok = r.(error)
                if !ok {
                    err = fmt.Errorf("pkg: %v", r)
                }
            }
        }()
    
        fields := strings.Fields(input)
        numbers = fields2numbers(fields)
        return
    }
    
    func fields2numbers(fields []string) (numbers []int) {
        if len(fields) == 0 {
            panic("no words to parse")
        }
        for idx, field := range fields {
            num, err := strconv.Atoi(field)
            if err != nil {
                panic(&ParseError{idx, field, err})
            }
            numbers = append(numbers, num)
        }
        return
    }
    

    使用一种用闭包处理错误的模式?

    func check(err error) { if err != nil { panic(err) } }
    
    // errorhandler:这是一个包装函数。接收一个 fType1 类型的函数 fn 并返回一个调用 fn 的函数。里面就包含有 defer/recover 机制
    func errorHandler(fn fType1) fType1 {
        return func(a type1, b type2) {
            defer func() {
                if err, ok := recover().(error); ok {
                    log.Printf(“run time panic: %v”, err)
                }
            }()
            fn(a, b)
        }
    }
    

    如何启动外部命令和程序?

    os 包有一个 StartProcess 函数可以调用或启动外部系统命令和二进制可执行文件;它的第一个参数是要运行的进程,第二个参数用来传递选项或参数,第三个参数是含有系统环境基本信息的结构体。

    这个函数返回被启动进程的 id(pid),或者启动失败返回错误。

    exec 包中也有同样功能的更简单的结构体和函数;主要是 exec.Command(name string, arg ...string)Run()。首先需要用系统命令或可执行文件的名字创建一个 Command 对象,然后用这个对象作为接收者调用 Run()

    单元测试和基准测试?

    写测试用例

    不要通过共享内存来通信,而通过通信来共享内存

    xxx

    通道channel

    一个无缓冲通道只能包含 1 个元素,有时显得很局限。我们给通道提供了一个缓存,可以在扩展的 make 命令中设置它的容量

    func compute(ch chan int){
        ch <- someComputation() // when it completes, signal on the channel.
    }
    
    func main(){
        ch := make(chan int)    // allocate a channel.
        go compute(ch)      // stat something in a goroutines
        doSomethingElseForAWhile()
        result := <- ch
    }
    

    https://learnku.com/docs/the-way-to-go/142-covariance-channel/3686

    学习研究下素数打印这个函数

    后台服务模式:

    // Backend goroutine.
    func backend() {
        for {
            select {
            case cmd := <-ch1:
                // Handle ...
            case cmd := <-ch2:
                ...
            case cmd := <-chStop:
                // stop server
            }
        }
    }
    

    协程与恢复

    func server(workChan <-chan *Work) {
        for work := range workChan {
            go safelyDo(work)   // start the goroutine for that work
        }
    }
    
    func safelyDo(work *Work) {
        defer func() {
            if err := recover(); err != nil {
                log.Printf("Work failed with %s in %v", err, work)
            }
        }()
        do(work)
    }
    

    使用锁还是通道?

    • 使用锁的情景:
      • 访问共享数据结构中的缓存信息
      • 保存应用程序上下文和状态信息数据
    • 使用通道的情景:
      • 与异步操作的结果进行交互
      • 分发任务
      • 传递数据所有权

    惰性生成器的实现?

    package main
    import (
        "fmt"
    )
    
    type Any interface{}
    type EvalFunc func(Any) (Any, Any)
    
    func main() {
        evenFunc := func(state Any) (Any, Any) {
            os := state.(int)
            ns := os + 2
            return os, ns
        }
    
        even := BuildLazyIntEvaluator(evenFunc, 0)
    
        for i := 0; i < 10; i++ {
            fmt.Printf("%vth even: %v\n", i, even())
        }
    }
    
    func BuildLazyEvaluator(evalFunc EvalFunc, initState Any) func() Any {
        retValChan := make(chan Any)
        loopFunc := func() {
            var actState Any = initState
            var retVal Any
            for {
                retVal, actState = evalFunc(actState)
                retValChan <- retVal
            }
        }
        retFunc := func() Any {
            return <- retValChan
        }
        go loopFunc()
        return retFunc
    }
    
    func BuildLazyIntEvaluator(evalFunc EvalFunc, initState Any) func() int {
        ef := BuildLazyEvaluator(evalFunc, initState)
        return func() int {
            return ef().(int)
        }
    }
    

    使用通道实现Future模式。类似于java里面的future?

    每一个协程执行完返回一个channel对象,然后最后从每一个channel中读取数据。

    package gorout
    
    type Matrix struct {
    
    }
    
    func InverseProduct(a Matrix, b Matrix) Matrix{
        a_inv_future := InverseFuture(a)   // start as a goroutine
        b_inv_future := InverseFuture(b)   // start as a goroutine
        a_inv := <-a_inv_future
        b_inv := <-b_inv_future
        return Product(a_inv, b_inv)
    }
    
    func InverseFuture(a Matrix)  (chan Matrix){
        future := make(chan Matrix)
        go func() {
            future <- Inverse(a)
        }()
        return future
    }
    
    func Inverse(a Matrix) Matrix{
        return a
    }
    
    func Product(a,b Matrix) (c Matrix){
        return c
    }
    

    如何设计良好的错误处理,避免错误检测使代码变得混乱?

    使用recover来终止panic。

    使用闭包的方式解决错误。这样子啊子程序中错误处理简化成一个check(); 最后包装一层。

    func check(err error) { if err != nil { panic(err) } }
    
    func errorHandler(fn fType1) fType1 {
        return func(a type1, b type2) {
            defer func() {
                if err, ok := recover().(error); ok {
                    log.Printf(“run time panic: %v”, err)
                }
            }()
            fn(a, b)
        }
    }
    

    结构体初始化?

    当结构体的命名以大写字母开头时,该结构体在包外可见。

    通常情况下,为每个结构体定义一个构建函数,并推荐使用构建函数初始化结构体

    如何通过一个通道让主程序等待直到协程完成?(信号量模式)

    ch := make(chan int) // Allocate a channel.
    // Start something in a goroutine; when it completes, signal on the channel.
    go func() {
        // doSomething
        ch <- 1 // Send a signal; value does not matter.
    }()
    doSomethingElseForAWhile()
    <-ch // Wait for goroutine to finish; discard sent value.
    

    简单的超时模版?

    timeout := make(chan bool, 1)
    go func() {
        time.Sleep(1e9) // one second  
        timeout <- true
    }()
    select {
        case <-ch:
        // a read from ch has occurred
        case <-timeout:
        // the read from ch has timed out
    }
    

    如何使用输入通道和输出通道代替锁?

    func Worker(in, out chan *Task) {
        for {
            t := <-in
            process(t)
            out <- t
        }
    }
    

    如何取消取消耗时很长的同步调用?

    ch := make(chan error, 1)
    go func() { ch <- client.Call("Service.Method", args, &reply) } ()
    select {
    case resp := <-ch
        // use resp and reply
    case <-time.After(timeoutNs):
        // call timed out
        break
    }
    

    如何在程序出错时,终止程序?

    if err != nil {
       fmt.Printf(“Program stopping with error %v”, err)
       os.Exit(1)
    }
    
    // or
    if err != nil { 
        panic(“ERROR occurred: “ + err.Error())
    }
    

    相关文章

      网友评论

          本文标题:《Go入门指南》一些笔记

          本文链接:https://www.haomeiwen.com/subject/fwjbfctx.html