Go指南笔记

官方doc:English 中文

Go指南:https://tour.go-zh.org/list

基础

包、变量、函数

    • 按照约定,包名与导入路径的最后一个元素一致。例如,"math/rand" 包中的源码均以 package rand 语句开始
  • 导出名

    • 大写开头 -> 已导出,类似public
    • 小写开头 -> 未导出,类似protected
  • 函数

    • 变量类型在声明之后,why

      • 更直观,在复杂的函数声明中尤为明显
    • 连续多个参数类型相同时,除最后一个外,其它可以不写func add(x, y int) int ...

    • 可以对返回值命名,需要在函数体内写入返回的变量名,然后返回空值

      1
      2
      3
      4
      5
      func split(sum int) (x, y int) {
      x = sum * 4 / 9
      y = sum - x
      return
      }
    • 函数值:函数也是值。它们可以像其它值一样传递。

    • 支持闭包

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

      import "fmt"

      // 返回一个“返回int的函数”
      func fibonacci() func() int {
      a := 0
      b := 1
      return func() int {
      ret := a
      a = b
      b = ret + b
      return ret
      }
      }

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

    • 声明:var name type
    • 初始化:var x int = 3 or var x = 1 对于赋值的初始化,类型能省略不写,因为能够推测出来
    • 短变量声明:在函数中,简洁赋值语句 := 可在类型明确的地方代替 var 声明
  • 基本类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    bool

    string

    // int、uint、uintptr追随系统,32位系统为32位,64位系统为64位
    int int8 int16 int32 int64
    uint uint8 uint16 uint32 uint64 uintptr

    byte // uint8 的别名

    rune // int32 的别名
    // 表示一个 Unicode 码点

    float32 float64

    complex64 complex128
    • 零值:没有明确初始值的变量声明会被赋予它们的 零值(0,false,””)。var i int
    • 类型转换:表达式 T(v) 将值 v 转换为类型 T
    • 类型推导:未指明类型的数值常量时,数值由常量的精度推断
    • 常量:使用const引导,不能使用短变量声明

流程控制语句

  • for

    • 只有for循环=。=

      1
      2
      3
      4
      5
      6
      for i := 0; i < 10; i++ {
      fmt.Println(i)
      }
      for {
      // 无限循环
      }
    • 后置语句(上例的i++)是可选的

  • if

    • 无需小括号,大括号必须

    • 也拥有初始化语句

      1
      2
      3
      4
      5
      6
      7
      func pow(x, n, lim float64) float64 {
      if v := math.Pow(x, n); v < lim {
      fmt.Println(v)
      return v
      }
      return lim
      }
    • switch:只运行选定的case,而非之后所有的。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      func main() {
      fmt.Println("When's Saturday?")
      today := time.Now().Weekday()
      // switch {} 不加变量时,默认是True
      switch time.Saturday {
      case today + 0:
      fmt.Println("Today.")
      case today + 1:
      fmt.Println("Tomorrow.")
      case today + 2:
      fmt.Println("In two days.")
      default:
      fmt.Println("Too far away.")
      }
      }
  • defer

    • 外围函数执行完毕才执行该函数。推迟的函数调用会被压入一个栈中

      1
      2
      defer fmt.Println("world")
      fmt.Println("hello")
    • more: Defer, Panic, and Recover

      • Defer可以确保程序结束之后的工作正常进行,让coder在开始操作之后立即编写结束代码。配合panicrecover可以对异常进行人为的处理。(感觉怎么像是对于大面积try catch的优雅改造)

        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
        package main

        import "fmt"

        func main() {
        f()
        fmt.Println("Returned normally from f.")
        }

        func f() {
        defer func() {
        if r := recover(); r != nil {
        fmt.Println("Recovered in f", r)
        }
        }()
        fmt.Println("Calling g.")
        g(0)
        fmt.Println("Returned normally from g.")
        }

        func g(i int) {
        if i > 3 {
        fmt.Println("Panicking!")
        panic(fmt.Sprintf("%v", i)) // 返回异常信息,4
        }
        defer fmt.Println("Defer in g", i)
        fmt.Println("Printing in g", i)
        g(i + 1)
        }

更多类型:struct、slice和映射

  • 指针

    • 和C类似,但是不能对指针进行运算
  • 结构体

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    type Vertex struct {
    X int
    Y int
    }
    v := Vertex{1, 2}
    v.X = 20
    var (
    v1 = Vertex{1, 2} // 创建一个 Vertex 类型的结构体
    v2 = Vertex{X: 1} // Y:0 被隐式地赋予
    v3 = Vertex{} // X:0 Y:0
    p = &Vertex{1, 2} // 创建一个 *Vertex 类型的结构体(指针)
    )
  • 数组

    • 类型 [n]T 表示拥有 nT 类型的值的数组。
    • 赋值声明:primes := [6]int{2, 3, 5, 7, 11, 13} or primes := [...]int{2, 3, 5, 7, 11, 13}
    • 当一个数组变量被赋值或者被传递的时候,实际上会复制整个数组。
  • 切片

    • array[low: high],数组的引用。

    • 切片文法:primes := []int{2, 3, 5, 7, 11, 13},先创建了上方的数组,然后primes是上方数组的引用

    • 长度和容量:len(array) and cap(array)。容量是从它的第一个元素开始数,到其底层数组元素末尾的个数,长度就是它所包含的元素个数。

    • nil切片:长度和容量都为0的切片

    • make创建切片(动态数组):make(array type, initial length, initial cap)

    • 切片的切片(多维数组)

      1
      2
      3
      4
      5
      board := [][]string{
      []string{"_", "_", "_"},
      []string{"_", "_", "_"},
      []string{"_", "_", "_"},
      }
    • 追加元素:func append(s []T, vs ...T) []T

      • e.g var s []int; s = append(s, 2, 3, 4)
      • 使用...语法进行参数展开:var x []int{1, 2}; var y []int{3}; var z = append(y, x...)
      • more: Go 切片:用法和本质
        • 一个切片是一个数组片段的描述。它包含了指向数组的指针,片段的长度,和容量(片段的最大长度)。
        • 切片增长不能超出其容量。
        • 避免陷阱:返回切片由于是数组的引用,即使只用一小部分,GC仍然不能回收未使用的部分。
  • range

    • for 循环的 range 形式可遍历切片或映射。当使用 for 循环遍历切片时,每次迭代都会返回两个值。第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。for index, value := range slice {}
    • 只需要索引的话,可以去掉value部分
  • 映射

    • 映射将键映射到值。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      type Vertex struct {
      Lat, Long float64
      }
      var m map[string]Vertex
      var m = map[string]Vertex{
      "Bell Labs": Vertex{
      40.68433, -74.39967,
      },
      "Google": Vertex{
      37.42202, -122.08408,
      },
      }
      // 若顶级类型只是一个类型名,你可以在文法的元素中省略它。
      var m = map[string]Vertex{
      "Bell Labs": {40.68433, -74.39967},
      "Google": {37.42202, -122.08408},
      }
      m = make(map[string]Vertex) // 返回给定类型的映射,并将其初始化备用
    • 删除元素 delete(map, key)

    • 双赋值检测元素是否存在 elem, ok = m[key]

方法和接口

  • 方法

    • 方法就是一类带特殊的 接收者 参数的函数,方法接收者在它自己的参数列表内,位于 func 关键字和方法名之间

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      type Vertex struct {
      X, Y float64
      }

      func (v Vertex) Abs() float64 {
      return math.Sqrt(v.X*v.X + v.Y*v.Y)
      }

      // 指针接收者,更常用,可以修改值
      func (v *Vertex) Scale(f float64) {
      v.X = v.X * f
      v.Y = v.Y * f
      }

      func main() {
      v := Vertex{3, 4}
      fmt.Println(v.Abs())
      }
    • 方法只是个带接收者参数的函数

    • 接收者的类型定义和方法声明必须在同一包内;不能为内建类型声明方法

    • 指针重定向:由于 Scale 方法有一个指针接收者,为方便起见,Go 会将语句 v.Scale(5) 解释为 (&v).Scale(5);方法调用 p := &v; p.Abs() 会被解释为 (*p).Abs()

    • 尽量使用指针接收者,然后避免混用指针和值接收者的情况。

      • 一是可以在方法内改变接收者值,二是不必要每一次调用复制一次
  • 接口

    • 接口类型 是由一组方法签名定义的集合

      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
      type Abser interface {
      Abs() float64
      }

      type MyFloat float64

      // 接口隐式实现:此方法表示类型 MyFloat 实现了接口Abser,但我们无需显式声明此事。
      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)
      }

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

      a = f // a MyFloat 实现了 Abser
      a = &v // a *Vertex 实现了 Abser

      // 下面一行,v 是一个 Vertex(而不是 *Vertex)
      // 所以没有实现 Abser,会报错
      a = v

      fmt.Println(a.Abs())
      }
    • 接口值:接口可以像值一样传递,作为函数的参数或返回值。在内部,接口值可以看做包含值和具体类型的元组(value, type)type I interface {..}; var i I; fmt.Printf("(%v, %T)\n", i, i)

    • 空接口:零个方法的接口值被称作空接口。可以保存任何类型的值,因为每个类型都至少实现了零个方法,🤣。可以用来接收未知类型的参数func print(i interface{}) {..}

  • 类型断言

    • 类型断言 提供了访问接口值底层具体值的方式。有两种断言形式。
    • t := i.(T) 该语句断言接口值 i 保存了具体类型 T,并将其底层类型为 T 的值赋予变量 t。如果失败,会触发恐慌
    • t, ok := i.(T) 如果失败,t为类型T的零值,ok为false,不会触发恐慌
  • 类型选择

    • 类型选择 是一种按顺序从几个类型断言中选择分支的结构。类似于switch

      1
      2
      3
      4
      5
      6
      7
      8
      switch v := i.(type) {
      case T:
      // v 的类型为 T
      case S:
      // v 的类型为 S
      default:
      // 没有匹配,v 与 i 的类型相同
      }
  • Stringer

    • fmt 包中定义的 Stringer 是最普遍的接口之一。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      /*
      type Stringer interface {
      String() string
      }
      */
      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)
      }
  • 错误

    • Go 程序使用 error 值来表示错误状态。和fmt.Stringer类似,是一个内建接口

      1
      2
      3
      4
      5
      6
      7
      8
      type error interface {
      Error() string
      }
      i, err := strconv.Atoi("42")
      if err != nil {
      fmt.Printf("couldn't convert number: %v\n", err)
      return
      }
    • 调用的它的代码应当判断这个错误是否等于 nil 来进行错误处理

  • Reader

    • io 包指定了 io.Reader 接口,它表示从数据流的末尾进行读取。有一个 Read 方法:func (T) Read(b []byte) (n int, err error)
  • 图像

    • image 包定义了 Image 接口

      1
      2
      3
      4
      5
      6
      7
      package image

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

并发

  • Go程

    • goroutine是由 Go 运行时管理的轻量级线程
    • go func即可将func的执行放入新的Go程中
  • 信道

    • 信道是带有类型的管道,你可以通过它用信道操作符 <- 来发送或者接收值

      1
      2
      3
      4
      5
      ch := make(chan int[, int])  // 创建信道,后面为信道的缓冲长度,当长度满后,向信道发送回阻塞
      ch <- v // 将 v 发送至信道 ch。
      v := <-ch // 从 ch 接收值并赋予 v。
      v, ok := <-ch // 通过close关键字关闭信道,可以给接收表达式分配第二个参数来看信道是否关闭
      for i := range(ch) {} // 会不断读取信道,直到信道关闭
  • select

    • select 语句使一个 Go 程可以等待多个通信操作,select 会阻塞到某个分支可以继续执行为止,这时就会执行该分支。当多个分支都准备好时会随机选择一个执行

      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
      package main

      import "fmt"

      func fibonacci(c, quit chan int) {
      x, y := 0, 1
      for {
      select {
      case c <- x:
      x, y = y, x+y
      case <-quit:
      fmt.Println("quit")
      return
      default:
      fmt.Println("default") // 所有select分支都没准备好时
      }
      }
      }

      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)
  • sync.Mutex

    • 互斥锁

      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
      package main

      import (
      "fmt"
      "sync"
      "time"
      )

      // SafeCounter 的并发使用是安全的。
      type SafeCounter struct {
      v map[string]int
      mux sync.Mutex
      }

      // Inc 增加给定 key 的计数器的值。
      func (c *SafeCounter) Inc(key string) {
      c.mux.Lock()
      // Lock 之后同一时刻只有一个 goroutine 能访问 c.v
      c.v[key]++
      c.mux.Unlock()
      }

      // Value 返回给定 key 的计数器的当前值。
      func (c *SafeCounter) Value(key string) int {
      c.mux.Lock()
      // Lock 之后同一时刻只有一个 goroutine 能访问 c.v
      defer c.mux.Unlock()
      return c.v[key]
      }

      func main() {
      c := SafeCounter{v: make(map[string]int)}
      for i := 0; i < 1000; i++ {
      go c.Inc("somekey")
      }

      time.Sleep(time.Second)
      fmt.Println(c.Value("somekey"))
      }

备忘

  1. 大括号不能在同一列(竟然被强行规定下来了,😓)
  2. 进阶:接下来去哪?

练习

1.循环与函数

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

import (
"fmt"
)

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

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

2.切片

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 "math"

func Pic(dx, dy int) [][]uint8 {
ret := make([][]uint8, dy)
for y := range(ret) {
ret[y] = make([]uint8, dx)
for x := range(ret[y]) {
//ret[y][x] = uint8((x + y) / 2)
//ret[y][x] = uint8(x * y)
//ret[y][x] = uint8(x^y)
//ret[y][x] = uint8(math.Pow(float64(x), float64(y)))
ret[y][x] = uint8(float64(x) * math.Log(float64(y)))
//ret[y][x] = uint8(x % (y + 1))
}
}
return ret
}

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

3.映射

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/wc"
"strings"
"fmt"
)

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

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

4.斐波纳契闭包

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"

// 返回一个“返回int的函数”
func fibonacci() func() int {
a := 0
b := 1
return func() int {
ret := a
a, b = b, a + b
return ret
}
}

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

5.Stringer

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

import "fmt"

type IPAddr [4]byte

// TODO: 给 IPAddr 添加一个 "String() string" 方法
func (i IPAddr) String() string {
return fmt.Sprintf("%v.%v.%v.%v", i[0], i[1], i[2], i[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)
}
}

6.错误

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

type ErrNegativeSqrt float64

func (e ErrNegativeSqrt) Error() string {
// 不把e转换为float64,那么这条句子其实是
// fmt.Sprint("cannot Sqrt negative number: %v", e.Error())
// 造成死循环
return fmt.Sprintf("cannot Sqrt negative number: %v", float64(e))
}

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

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

7.Reader

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

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

type MyReader struct{}

// TODO: 给 MyReader 添加一个 Read([]byte) (int, error) 方法
func (r MyReader) Read(data []byte) (int, error) {
for i := range(data) {
data[i] = 'A'
}
return len(data), nil
}

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

8.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
package main

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

type rot13Reader struct {
r io.Reader
}

func (rot *rot13Reader) Read(b []byte) (int, error) {
_, err := rot.r.Read(b)
if err != nil {
return 0, err
}
for i, v := range(b) {
if v >= 'A' && v <= 'Z' {
b[i] = byte((int(v) + 13 - int('A')) % 26 + int('A'))
} else if v >= 'a' && v <= 'z' {
b[i] = byte((int(v) + 13 - int('a')) % 26 + int('a'))
}
}
return len(b), nil
}

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

9.图像

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
package main

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

type Image struct{
w, h int
v uint8
}

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

func (i *Image) Bounds() image.Rectangle {
return image.Rect(0, 0, i.w, i.h)
}

func (i *Image) At(x, y int) color.Color {
return color.RGBA{uint8(x) + i.v, uint8(y) + i.v, 255, 255}
}

func main() {
m := Image{70, 140, 200}
pic.ShowImage(&m)
}

10.等价二叉查找树

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
package main

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

/*
type Tree struct {
Left *Tree
Value int
Right *Tree
}
*/

// Walk 步进 tree t 将所有的值从 tree 发送到 channel ch。
func Walk(t *tree.Tree, ch chan int) {
walk(t, ch)
close(ch)
}

func walk(t *tree.Tree, ch chan int) {
if t != nil {
walk(t.Left, ch)
ch <- t.Value
walk(t.Right, ch)
}
}

// Same 检测树 t1 和 t2 是否含有相同的值。
func Same(t1, t2 *tree.Tree) bool {
tc1 := make(chan int)
tc2 := make(chan int)
go Walk(t1, tc1)
go Walk(t2, tc2)
for v1 := range(tc1) {
if v1 != <- tc2 {
return false
}
}
return true
}

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

11.Web 爬虫

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
package main

import (
"fmt"
"sync"
)

var wg sync.WaitGroup

type SafeUrlCache struct {
v map[string]bool
mux sync.Mutex
}

type Fetcher interface {
// Fetch 返回 URL 的 body 内容,并且将在这个页面上找到的 URL 放到一个 slice 中。
Fetch(url string) (body string, urls []string, err error)
}

// Crawl 使用 fetcher 从某个 URL 开始递归的爬取页面,直到达到最大深度。
func Crawl(url string, depth int, fetcher Fetcher, c *SafeUrlCache) {
wg.Add(1)
go crawl(url, depth, fetcher, c)
wg.Wait()
}

func crawl(url string, depth int, fetcher Fetcher, c *SafeUrlCache) {
defer wg.Done()

if depth <= 0 {
return
}
c.mux.Lock()
if c.v[url] {
c.mux.Unlock()
return
}
c.v[url] = true
c.mux.Unlock()
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 {
wg.Add(1)
go crawl(u, depth-1, fetcher, c)
}
return
}

// 等待进程结束,参考:https://www.jianshu.com/p/d55483e90329
func main() {
c := SafeUrlCache{v: make(map[string]bool)}
Crawl("https://golang.org/", 4, fetcher, &c)
}

// fakeFetcher 是返回若干结果的 Fetcher。
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 是填充后的 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/",
},
},
}
梦想世界上每个人都能给我一元钱,2333