《Go入门指南》笔记

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

make()

container:list/ring

runtime

reflectruntime

如何强制使用工厂方法?

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)typeObjobjfunc
SetFinalizer

接口命名约定?

[e]rPrinterReaderWriterLoggerConvertererRecoverableableI.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)
}
intSorter
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注意事项?

make()

判断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)

错误处理?

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

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

runtime.ErrorRuntimeError()

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)
    }
}

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

StartProcess

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

exec.Command(name string, arg ...string)Run()CommandRun()

单元测试和基准测试?

写测试用例

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

xxx

通道channel

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
}

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

后台服务模式:

// 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())
}