本文主要为《A Tour of Go》的笔记和练习题答案
建议读者直接阅读上面网站中的教程,建议阅读英文版(中文版有内容确实)
参考答案可通过导航栏快速跳转查阅

Packages, variables, and functions.

  • In Go, a name is exported if it begins with a capital letter. For example, Pizza is an exported name, as is Pi, which is exported from the math package.

  • Go’s return values may be named. If so, they are treated as variables defined at the top of the function.
    A return statement without arguments returns the named return values. This is known as a “naked” return.

    1
    2
    3
    4
    5
    6
    7
    // Naked return statements should be used only in short functions
    // They can harm readability in longer functions.
    func split(sum int) (x, y int) {
    x = sum * 4 / 9
    y = sum - x
    return
    }
  • If an initializer is present, the type can be omitted; the variable will take the type of the initializer. e.g. var c, python, java = true, false, "no!"

  • Variables declared without an explicit initial value are given their zero value.

    The zero value is:

    • 0 for numeric types,
    • false for the boolean type, and
    • "" (the empty string) for strings.
  • Numeric constants are high-precision values.
    An untyped constant takes the type needed by its context.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    const (
    // Create a huge number by shifting a 1 bit left 100 places.
    // In other words, the binary number that is 1 followed by 100 zeroes.
    Big = 1 << 100
    // Shift it right again 99 places, so we end up with 1<<1, or 2.
    Small = Big >> 99
    )

    func needInt(x int) int { return x*10 + 1 }
    func needFloat(x float64) float64 {
    return x * 0.1
    }

    func main() {
    fmt.Println(needInt(Small))
    // fmt.Println(needInt(Big))) // cannot use Big (untyped int constant 1267650600228229401496703205376) as int value in argument to needInt (overflows)
    fmt.Println(needFloat(Small))
    fmt.Println(needFloat(Big))
    }

Flow control statements: for, if, else, switch and defer

  • Unlike other languages like C, Java, or JavaScript there are no parentheses ( ) surrounding the three components of the for statement and the braces { } are always required.
  • At that point you can drop the semicolons: C’s while is spelled for in Go.
  • Like for, the if statement can start with a short statement to execute before the condition. if v := math.Pow(x, n); v < lim {return v}

Exercise: Loops and Functions

1
2
3
4
5
6
7
func Sqrt(x float64) float64 {
z := 1.0
for i := 0; i < 10; i ++ {
z -= (z*z - x) / (2 * z)
}
return z
}

  • Switch cases evaluate cases from top to bottom, stopping when a case succeeds.

    会中途停下这一点和C/C++中完全不一样

  • Switch without a condition is the same as switch true.
    This construct can be a clean way to write long if-then-else chains.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    func main() {
    t := time.Now()
    switch {
    case t.Hour() < 12:
    fmt.Println("Good morning!")
    case t.Hour() < 17:
    fmt.Println("Good afternoon.")
    default:
    fmt.Println("Good evening.")
    }
    }
  • A defer statement defers the execution of a function until the surrounding function returns.
    The deferred call’s arguments are evaluated immediately, but the function call is not executed until the surrounding function returns.

  • Deferred function calls are pushed onto a stack. When a function returns, its deferred calls are executed in last-in-first-out order.

More types: structs, slices, and maps.

  • The type *T is a pointer to a T value. Its zero value is nil.

    Go 中指针用*T, 数组用[]T表示

  • To access the field X of a struct when we have the struct pointer p we could write (*p).X. However, that notation is cumbersome, so the language permits us instead to write just p.X, without the explicit dereference.

  • You can list just a subset of fields by using the Name: syntax. (And the order of named fields is irrelevant.)

    1
    2
    3
    4
    5
    type Vertex struct {
    X, Y int
    }

    v2 = Vertex{X: 1} // Y:0 is implicit
  • A slice, is a dynamically-sized, flexible view into the elements of an array. In practice, slices are much more common than arrays.
    A slice does not store any data, it just describes a section of an underlying array.

    1
    2
    primes := [6]int{2, 3, 5, 7, 11, 13}
    var s []int = primes[1:4]
  • Slices can be created with the built-in make function; this is how you create dynamically-sized arrays.

    The make function allocates a zeroed array and returns a slice that refers to that array:

    1
    a := make([]int, 5)  // len(a)=5

    To specify a capacity, pass a third argument to make:

    1
    2
    3
    4
    b := make([]int, 0, 5) // len(b)=0, cap(b)=5

    b = b[:cap(b)] // len(b)=5, cap(b)=5
    b = b[1:] // len(b)=4, cap(b)=4

    为啥有时cap会变化,有时不会变化呢?
    个人感觉好像是[first:last]中设定first时会变化,设定last不会变化

  • The zero value of a slice is nil.

  • Slices can contain any type, including other slices.

    1
    2
    3
    4
    5
    board := [][]string{
    []string{"_", "_", "_"},
    []string{"_", "_", "_"},
    []string{"_", "_", "_"},
    }
  • It is common to append new elements to a slice, and so Go provides a built-in append function.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    var s []int
    printSlice(s)

    // append works on nil slices.
    s = append(s, 0)
    printSlice(s)

    // The slice grows as needed.
    s = append(s, 1)
    printSlice(s)

    /*
    len=0 cap=0 []
    len=1 cap=1 [0]
    len=2 cap=2 [0 1]
    */
  • The range form of the for loop iterates over a slice or map.
    When ranging over a slice, two values are returned for each iteration. The first is the index, and the second is a copy of the element at that index.

    1
    2
    3
    4
    5
    6
    7
    var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

    func main() {
    for i, v := range pow {
    fmt.Printf("2**%d = %d\n", i, v)
    }
    }
  • You can skip the index or value by assigning to _.

    1
    2
    for i, _ := range pow
    for _, value := range pow

    If you only want the index, you can omit the second variable.

    1
    for i := range pow

Exercise: Slices

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import "golang.org/x/tour/pic"

func Pic(dx, dy int) [][]uint8 {
pic := make([][]uint8, dy)
for y := 0; y < dy; y++ {
line := make([]uint8, dx)
for x := 0; x < dx; x++ {
line[x] = uint8(x*y)
}
pic[y] = line
}
return pic
}

func main() {
pic.Show(Pic)
}


  • The zero value of a map is nil. A nil map has no keys, nor can keys be added.

  • Mutating Maps

    Insert or update an element in map m:

    1
    m[key] = elem

    Retrieve an element:

    1
    elem = m[key]

    Delete an element:

    1
    delete(m, key)

    Test that a key is present with a two-value assignment:

    1
    elem, ok = m[key]
  • Mutating Maps

    Insert or update an element in map m:

    1
    m[key] = elem

    Retrieve an element:

    1
    elem = m[key]

    Delete an element:

    1
    delete(m, key)

    Test that a key is present with a two-value assignment:

    1
    elem, ok = m[key]

    Note: If elem or ok have not yet been declared you could use a short declaration form: elem, ok := m[key]

Exercise: Maps

func FieldsFunc:

1
func FieldsFunc(s string, f func(rune) bool) []string

FieldsFunc splits the string s at each run of Unicode code points c satisfying f© and returns an array of slices of s. If all code points in s satisfy f© or the string is empty, an empty slice is returned.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import (
"golang.org/x/tour/wc"
"strings"
)

func WordCount(s string) map[string]int {
hash := make(map[string]int)
for _, word := range strings.Fields(s) {
hash[word]++
}
return hash
}

func main() {
wc.Test(WordCount)
}


  • Go functions may be closures. A closure is a function value that references variables from outside its body. The function may access and assign to the referenced variables; in this sense the function is “bound” to the variables.
    For example, the adder function returns a closure. Each closure is bound to its own sum variable.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    func adder() func(int) int {
    sum := 0
    return func(x int) int {
    sum += x
    return sum
    }
    }

    func main() {
    pos, neg := adder(), adder()
    for i := 0; i < 10; i++ {
    fmt.Println(
    pos(i),
    neg(-2*i),
    )
    }
    }

Exercise: Fibonacci closure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import "fmt"

// fibonacci is a function that returns
// a function that returns an int.
func fibonacci() func() int {
a, b:= 0, 1
return func() int {
temp := a // 注意写法:这里先讲a暂存下来,后面再return
a, b = b, a + b
return temp
}

}

func main() {
f := fibonacci()
for i := 0; i < 10; i++ {
fmt.Println(f())
}
}

Methods and interfaces

  • A method is a function with a special receiver argument.
    The receiver appears in its own argument list between the func keyword and the method name. func (v Vertex) Abs() float64 {...}
    Remember: a method is just a function with a receiver argument. Here’s Abs written as a regular function with no change in functionality. func Abs(v Vertex) float64 {...}

  • Functions with a pointer argument must take a pointer.
    While methods with pointer receivers take either a value or a pointer as the receiver when they are called.

  • Functions that take a value argument must take a value of that specific type
    While methods with value receivers take either a value or a pointer as the receiver when they are called.

  • There are two reasons to use a pointer receiver.

    • The first is so that the method can modify the value that its receiver points to.
    • The second is to avoid copying the value on each method call. This can be more efficient if the receiver is a large struct, for example.
  • An interface type is defined as a set of method signatures.
    A value of interface type can hold any value that implements those methods.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    type Abser interface {
    Abs() float64
    }

    func main() {
    var a Abser
    f := MyFloat(-math.Sqrt2)
    v := Vertex{3, 4}

    a = f // a MyFloat implements Abser
    a = &v // a *Vertex implements Abser

    // In the following line, v is a Vertex (not *Vertex)
    // and does NOT implement Abser.
    a = v

    fmt.Println(a.Abs())
    }

    type MyFloat float64

    func (f MyFloat) Abs() float64 {
    if f < 0 {
    return float64(-f)
    }
    return float64(f)
    }

    type Vertex struct {
    X, Y float64
    }

    func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
    }
  • If the concrete value inside the interface itself is nil, the method will be called with a nil receiver.
    In some languages this would trigger a null pointer exception, but in Go it is common to write methods that gracefully handle being called with a nil receiver (as with the method M in this example.)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    func (t *T) M() {
    if t == nil {
    fmt.Println("<nil>")
    return
    }
    fmt.Println(t.S)
    }

    func main() {
    var i I

    var t *T
    i = t
    describe(i)
    i.M()

    i = &T{"hello"}
    describe(i)
    i.M()
    }

  • A nil interface value holds neither value nor concrete type.
    Calling a method on a nil interface is a run-time error because there is no type inside the interface tuple to indicate which concrete method to call.

  • The interface type that specifies zero methods is known as the empty interface:interface{}
    An empty interface may hold values of any type. (Every type implements at least zero methods.)

  • A type assertion provides access to an interface value’s underlying concrete value.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var i interface{} = "hello"

    s := i.(string)
    fmt.Println(s)

    s, ok := i.(string)
    fmt.Println(s, ok)

    f, ok := i.(float64) // 接收ok,则即使断言失败也不会触发panic
    fmt.Println(f, ok)

    f = i.(float64) // panic
    fmt.Println(f)
  • A type switch is a construct that permits several type assertions in series.

    1
    2
    3
    4
    5
    6
    7
    8
    switch v := i.(type) {  // .(type) 是固定语法
    case T:
    // here v has type T
    case S:
    // here v has type S
    default:
    // no match; here v has the same type as i
    }

    The declaration in a type switch has the same syntax as a type assertion i.(T), but the specific type T is replaced with the keyword type.

Exercise: Stringers

One of the most ubiquitous interfaces is Stringer defined by the fmt package.

1
2
3
type Stringer interface {
String() string
}

A Stringer is a type that can describe itself as a string. The fmt package (and many others) look for this interface to print values.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type Person struct {
Name string
Age int
}

func (p Person) String() string {
return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}

func main() {
a := Person{"Arthur Dent", 42}
z := Person{"Zaphod Beeblebrox", 9001}
fmt.Println(a, z)
}

// Arthur Dent (42 years) Zaphod Beeblebrox (9001 years)

Exercise Answer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "fmt"

type IPAddr [4]byte

// TODO: Add a "String() string" method to IPAddr.
func (ip IPAddr) String() string {
return fmt.Sprintf("%v.%v.%v.%v", ip[0], ip[1], ip[2], ip[3])
}

func main() {
hosts := map[string]IPAddr{
"loopback": {127, 0, 0, 1},
"googleDNS": {8, 8, 8, 8},
}
for name, ip := range hosts {
fmt.Printf("%v: %v\n", name, ip)
}
}

Exercise: Errors

The error type is a built-in interface similar to fmt.Stringer:

1
2
3
type error interface {
Error() string
}

(As with fmt.Stringer, the fmt package looks for the error interface when printing values.)

Functions often return an error value, and calling code should handle errors by testing whether the error equals nil.

1
2
3
4
5
6
i, err := strconv.Atoi("42")
if err != nil {
fmt.Printf("couldn't convert number: %v\n", err)
return
}
fmt.Println("Converted integer:", i)

A nil error denotes success; a non-nil error denotes failure

Exercise Answer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"fmt"
)

// Create a new type
type ErrNegativeSqrt float64 // 通过对该类型实现Error()使其成为一种error类型

// and make it an error by giving it a
func (e ErrNegativeSqrt) Error() string {
return fmt.Sprintf("cannot Sqrt negative number: %v", float64(e))
}

func Sqrt(x float64) (float64, error) {
if x < 0 {
return 0, ErrNegativeSqrt(1)
}
z := 1.0
for i := 0; i < 10; i++ {
z -= (z*z - x) / (2*x)
}
return z, nil
}

func main() {
fmt.Println(Sqrt(2))
fmt.Println(Sqrt(-2))
}

Note: A call to fmt.Sprint(e) inside the Error method will send the program into an infinite loop. You can avoid this by converting e first: fmt.Sprint(float64(e)). Why?

因为e变量是一个通过实现Error()的接口函数成为了error类型,那么在fmt.Sprint(e)时就会调用e.Error()来输出错误的字符串信息,于是函数就变成了如下等价形式,因此会陷入死循环中。

1
2
3
func (e ErrNegativeSqrt) Error() string {
return fmt.Sprint(e.Error())
}

Exercise: Readers

The io package specifies the io.Reader interface, which represents the read end of a stream of data: func (T) Read(b []byte) (n int, err error)

Read populates the given byte slice with data and returns the number of bytes populated and an error value. It returns an io.EOF error when the stream ends.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func main() {
r := strings.NewReader("Hello, Reader!")

b := make([]byte, 8)
for {
n, err := r.Read(b)
fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
fmt.Printf("b[:n] = %q\n", b[:n])
if err == io.EOF {
break
}
}
}
/*
n = 8 err = <nil> b = [72 101 108 108 111 44 32 82]
b[:n] = "Hello, R"
n = 6 err = <nil> b = [101 97 100 101 114 33 32 82]
b[:n] = "eader!"
n = 0 err = EOF b = [101 97 100 101 114 33 32 82]
b[:n] = ""
*/

Exercise Answer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "golang.org/x/tour/reader"

type MyReader struct{}

// TODO: Add a Read([]byte) (int, error) method to MyReader.
func (mr MyReader) Read(b []byte) (int, error) {
b[0] = 'A'
return 1, nil // 返回的第一个int类型参数表示读到字节数
}

func main() {
reader.Validate(MyReader{})
}

Exercise: rot13Reader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package main

import (
"io"
"os"
"strings"
)

type rot13Reader struct {
r io.Reader
}

// Go中使用''表示byte(相当于C中的char, 但Go中无char),
// 使用""表示string, 二者不能混用
func rot13(ch byte) byte {
switch { // while true, 这种switch常被用于替换if-else
case 'A' <= ch && ch <= 'M':
ch += 13
case 'M' < ch && ch <= 'Z':
ch -= 13
case 'a' <= ch && ch <= 'm':
ch += 13
case 'm' < ch && ch <= 'z':
ch -= 13
}
return ch
}

func (mr rot13Reader) Read(b []byte) (int, error) {
n, e := mr.r.Read(b)
for idx := range(b) {
b[idx] = rot13(b[idx])
}
return n, e
}

func main() {
s := strings.NewReader("Lbh penpxrq gur pbqr!")
r := rot13Reader{s}
io.Copy(os.Stdout, &r)
}

Exercise: Images

Package image defines the Image interface:

1
2
3
4
5
6
7
package image

type Image interface {
ColorModel() color.Model
Bounds() Rectangle
At(x, y int) color.Color
}

The color.Color and color.Model types are also interfaces, but we’ll ignore that by using the predefined implementations color.RGBA and color.RGBAModel. These interfaces and types are specified by the image/color package

Exercise Answer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import "golang.org/x/tour/pic"
import "image"
import "image/color"

type Image struct{}

func (img Image) ColorModel() color.Model {
return color.RGBAModel
}

func (img Image) Bounds() image.Rectangle {
return image.Rect(0, 0, 200, 200)
}

func (img Image) At(x, y int) color.Color {
return color.RGBA{uint8(x),uint8(y),uint8(255),uint8(255)}
}

func main() {
m := Image{}
pic.ShowImage(m)
}

Generics

The type parameters of a function appear between brackets, before the function’s arguments.

1
func Index[T comparable](s []T, x T) int

comparable is a useful constraint that makes it possible to use the == and != operators on values of the type. In this example, we use it to compare a value to all slice elements until a match is found. This Index function works for any type that supports comparison.

In addition to generic functions, Go also supports generic types.

1
2
3
4
5
6
// List represents a singly-linked list that holds
// values of any type.
type List[T any] struct {
next *List[T]
val T
}

Concurrency

  • Channels are a typed conduit through which you can send and receive values with the channel operator, <-.
    By default, sends and receives block until the other side is ready. This allows goroutines to synchronize without explicit locks or condition variables.

  • Channels can be buffered. Provide the buffer length as the second argument to make to initialize a buffered channel:ch := make(chan int, 100)
    Sends to a buffered channel block only when the buffer is full. Receives block when the buffer is empty.

    注意写法是chan int, chan表示channel

  • A sender can close a channel to indicate that no more values will be sent. v, ok := <-ch

  • The select statement lets a goroutine wait on multiple communication operations.
    A select blocks until one of its cases can run, then it executes that case. It chooses one at random if multiple are ready.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {
    select {
    case c <- x: // 接受者不再读取chan时就会阻塞发送
    x, y = y, x+y
    case <-quit:
    fmt.Println("quit")
    return
    }
    }
    }

    func main() {
    c := make(chan int)
    quit := make(chan int)
    go func() {
    for i := 0; i < 10; i++ {
    fmt.Println(<-c)
    }
    quit <- 0
    }()
    fibonacci(c, quit)
    }

Exercise: Equivalent Binary Trees

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import (
"golang.org/x/tour/tree"
"fmt"
)

// Walk walks the tree t sending all values
// from the tree to the channel ch.
func Walk(t *tree.Tree, ch chan int) {
if t == nil {return}
Walk(t.Left, ch)
ch <- t.Value
Walk(t.Right, ch)
}

// Same determines whether the trees
// t1 and t2 contain the same values.
func Same(t1, t2 *tree.Tree) bool {
ch1, ch2 := make(chan int), make(chan int)
go Walk(t1, ch1)
go Walk(t2, ch2)
if <-ch1 == <-ch2 {
return true
}
return false
}

func main() {
fmt.Println("Same(tree.New(1), tree.New(2)): ", Same(tree.New(1), tree.New(2)))
fmt.Println("Same(tree.New(1), tree.New(1)): ", Same(tree.New(1), tree.New(1)))
}

Exercise: Web Crawler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
package main

import (
"fmt"
"sync"
)

type Fetcher interface {
// Fetch returns the body of URL and
// a slice of URLs found on that page.
Fetch(url string) (body string, urls []string, err error)
}

type CrawlUrlMap struct {
urlMap map[string]bool
mutex sync.Mutex
}

func (m *CrawlUrlMap) exists(url string) bool {
m.mutex.Lock()
defer m.mutex.Unlock()
_, ok := m.urlMap[url]
m.urlMap[url] = true
return ok
}

// 指定一部分进行初始化,其余部分使用零初始化
var crawlUrlMap = &CrawlUrlMap{
urlMap: make(map[string]bool),
}

var wg = sync.WaitGroup{}

// Crawl uses fetcher to recursively crawl
// pages starting with url, to a maximum of depth.
func Crawl(url string, depth int, fetcher Fetcher) {
if depth <= 0 {
return
}
body, urls, err := fetcher.Fetch(url)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("found: %s %q\n", url, body)
for _, u := range urls {
if !crawlUrlMap.exists(u) {
wg.Add(1)
go func (url string, depth int, fetcher Fetcher) {
Crawl(url, depth, fetcher)
wg.Done()
} (u, depth-1, fetcher)
}
}
return
}

func main() {
if !crawlUrlMap.exists("https://golang.org/") {
Crawl("https://golang.org/", 4, fetcher)
}
wg.Wait() // 等待子进程跑完
}

// fakeFetcher is Fetcher that returns canned results.
type fakeFetcher map[string]*fakeResult

type fakeResult struct {
body string
urls []string
}

func (f fakeFetcher) Fetch(url string) (string, []string, error) {
if res, ok := f[url]; ok {
return res.body, res.urls, nil
}
return "", nil, fmt.Errorf("not found: %s", url)
}

// fetcher is a populated fakeFetcher.
var fetcher = fakeFetcher{
"https://golang.org/": &fakeResult{
"The Go Programming Language",
[]string{
"https://golang.org/pkg/",
"https://golang.org/cmd/",
},
},
"https://golang.org/pkg/": &fakeResult{
"Packages",
[]string{
"https://golang.org/",
"https://golang.org/cmd/",
"https://golang.org/pkg/fmt/",
"https://golang.org/pkg/os/",
},
},
"https://golang.org/pkg/fmt/": &fakeResult{
"Package fmt",
[]string{
"https://golang.org/",
"https://golang.org/pkg/",
},
},
"https://golang.org/pkg/os/": &fakeResult{
"Package os",
[]string{
"https://golang.org/",
"https://golang.org/pkg/",
},
},
}

WaitGroup 对象内部有一个计数器,最初从0开始,它有三个方法:Add(), Done(), Wait() 用来控制计数器的数量。

  • Add(n) 把计数器设置为n
  • Done() 每次把计数器-1
  • wait() 会阻塞代码的运行,直到计数器地值减为0。