官方教程 A Tour of Go Excercises 我的答案总结及讲解

这两天学完了 A Tour of Go 官方的语法教学,里面有很多的 Excercise(训练题)。希望对大家有用,如果有其他人也写过,并觉得我写的不对的,求教!:heart:

Exercise: Loops and Functions

题目

解答

package main

import (
    "fmt"
    "math"
)

func Sqrt(x float64) float64 {
    z := x/2
    for i:= 0; math.Abs(z*z - x) > 0.0000000001; i++ {
        z -= (z*z - x) / (2*z)
        fmt.Println(i, "z:", z, "z^2 -x:", z*z - x)
    }
    return z
}

func main() {
    fmt.Println(Sqrt(1000))
}
复制代码
  1. z := x/2 这个是猜测的初始值(也可以像是题目里的 hint 写的设置成 1)
  2. math.Abs(z*z - x) > 0.0000000001 用最优解的逻辑就是给了一个 tolerance 0.0000000001 ,即我们用计算公式算出来的 x 的差值已经足够小,我们认定预估的 z 算是一个近似准确值。

Exercise: Slices

题目

  • 写一个 Pic 函数来生成一个 [][]uint8 的 2D 图片(即可说是 Array of Array)。它的大小由参数 (dx, dy int) 决定,这个有 dy 个数组,每个数组里又有一个长度为 dx 的数组。而相关的位置上 pic[y][x] 是这个图片的 bluescale(只有蓝色)数值,格式为 uint8
  • tour.golang.org/moretypes/1…

解答

package main

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

func Pic(dx, dy int) [][]uint8 {
    pic := make([][]uint8, dy)
    for i := range pic {
        pic[i] = make([]uint8, dx)
        for j := range pic[i] {
            pic[i][j] = uint8(i*j + j*j)
        }
    }
    return pic
}

func main() {
    pic.Show(Pic)
}
复制代码
  1. pic := make([][]uint8, dy) 先建一个数组,长度是 dy ,数组里每个元素的内容是一个数组 []uint8
  2. pic[i] = make([]uint8, dx) 在数组里的第 i 个元素里,我们再创造一个 []uint8 数组,长度为 dx
  3. pic[i][j] = uint8(i*j + j*j) 表示我们设计的 bluesacle 计算公式里, pic[i][j] 位置的数值是 uint8(i*j + j*j) (这里你可以随意改几个,能看到很多不同的效果哦!)

Exercise: Maps

题目

  • 实现一个函数 WordCount ,它可以回复一个 map 里面包含输入字符串中出现的单词 word 及相应出现的次数。
  • 例如:”I love you you you you”,返回 map[string]int{"I":1, "love":1, "you":3}
  • tour.golang.org/moretypes/2…

解答

package main

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

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

func main() {
    wc.Test(WordCount)
}
复制代码
  1. strings.Files(s) 这个函数会自动切分一个字符串到一个数组,每个数组里是一个 word
  2. 建立 map 然后在数组里每当某一个 word 出现,就相应的增加 1

Exercise: Fibonacci closure

题目

解答

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 {
        c := a
        a, b = b, a+b
        return c
    }
}

func main() {
    f := fibonacci()
    for i := 0; i < 10; i++ {
        fmt.Println(f())
    }
}
复制代码
  1. closure 也叫闭包,意思是一个函数中需要使用的某一个变量是在此函数外定义的
  2. 在上面的 func fibonacci() func() int 中,返回的是一个函数 func() int ,而这个函数每次运行返回的是一个 int
  3. fibonacci() 中,变量 ab 定义在函数 fibonacci() 里,并被此函数返回的 return func() int { ... } 函数引用到,也就是说在返回的函数里 ab 两个变量一直存储在内存中,且数值会一直变化
  4. f := fibonacci()ffibonacci() 返回的函数,在初始情况中,此时的 a, b := 0, 1
  5. 以第一次 f() 调用为例:
    • c := ac 赋值为 a 即 0
    • a, b = b, a+ba 赋值为 b 即 1, b 赋值为 a+b 即 1
    • return c ,返回 0,也就是斐波那契数列的 第一个值
  6. 第二次 f() 调用,注意此时 a 是 1, b 是 1:
    • c := ac 赋值为 a 即 1
    • a, b = b, a+ba 赋值为 b 即 1, b 赋值为 a+b 即 2
    • return c ,返回 1,也就是斐波那契数列的 第二个值
  7. 以此类推,循环中 f 函数被调用了 10 次,输出了斐波那契数列前 10 个值

Exercise: Stringers

题目

  • type IPAddr [4]byte 增加 Stringer Interface 函数来输出字符串,即 IPAddr{1, 2, 3, 4} print 为 1.2.3.4
  • tour.golang.org/methods/18

解答

package main

import (
    "fmt"
    "strings"
    "strconv"
)

type IPAddr [4]byte

// TODO: Add a "String() string" method to IPAddr.
func (ip IPAddr) String() string {
    s := make([]string, len(ip))
    for i, val := range ip {
        s[i] = strconv.Itoa(int(val))
    }
    return fmt.Sprintf(strings.Join(s, "."))
}

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)
    }
}
复制代码
  1. 引入 strconv.Itoa ,int to string
  2. IPAddr 是一个大小为 4 的 []byte ,我们生成一个 [4]string 数组 s ,每一个元素是 IP 地址中的一位,并且我们用 strconv.Itoa(int(val)) 把它转换为字符串
  3. strings.Join(s, ".") 将字符串数组用 "." 连起来
  4. 这样在使用 fmt.Printf("%v: %v\n", name, ip) 时,会对 type IPAddr 默认调用其 Stringer interface 下定义的 String() 函数来输出

Exercise: Errors

题目

  • 优化 Exercise: Loops and Functions 里写的 sqrt 函数,当参数是一个负数时,添加 type ErrNegativeSqrt float64 ,并通过定义 func (e ErrNegativeSqrt) Error() string 从而使其为 error
  • tour.golang.org/methods/20

解答

package main

import (
    "fmt"
    "math"
)

type ErrNegativeSqrt float64

func (e ErrNegativeSqrt) Error() string {
    return fmt.Sprintf("cannot Sqrt negative number: %v", float64(e))
}

func Sqrt(x float64) (float64, error) {
    if (x > 0) {
        z:= x/2
        for i:= 0; math.Abs(z*z - x) > 0.0000000001; i++ {
            z -= (z*z - x) / (2*z)
            fmt.Println(i, "z:", z, "z^2 -x:", z*z - x)
        }
        return z, nil
    } else {
        return 0, ErrNegativeSqrt(x)
    }
}
复制代码
  1. error 类型是一个 built-in interface,需要定义 Error() string 函数,测试一个 error type 是否是 nil 是定义函数返回是否出错的方法。例如: i, err := strconv.Atoi("42") 返回值中 i 代表函数返回数值,而 err 如果不是 nil 的话,则表示有错误发生
  2. func (e ErrNegativeSqrt) Error() string 定义了 ErrNegativeSqrt 属于 errorError() 函数,也就简洁说明 ErrNegativeSqrt 是一个 error
  3. func Sqrt(x float64) (float64, error) 函数返回两个数值,前者为参数的平方根,后者为 error ,当后者不是 nil 的时候,在 Println 中会自动调用 Error() 输出相应的错误信息字符串。

Exercise: Readers

题目

解答

package main

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

type MyReader struct{}

type ErrEmptyBuffer []byte

func (b ErrEmptyBuffer) Error() string {
    return fmt.Sprintf("cannot read an empty buffer: %v", b)
}

// TODO: Add a Read([]byte) (int, error) method to MyReader.
func (reader MyReader) Read(b []byte) (int, error) {
    bLength := len(b)
    if (bLength == 0) {
        return 0, ErrEmptyBuffer(b)
    }
    for i := range b {
        b[i] = 'A'
    }
    return bLength, nil
}

func main() {
    reader.Validate(MyReader{})
}
复制代码
  1. 因为 MyReader 会输出无限个 'A' ,因此只要输入参数 b []byte 不是一个空的 Buffer,就会写满
  2. bLength == 0 也就是 Buffer b 是空时,返回 ErrEmptyBuffer(b) 错误
  3. 只要不是空,就都填满 'A' 并返回 bLength, nil

Exercise: rot13Reader

题目

  • 实现一个 rot13Reader type 使其包含一个 io.Reader 使得其在执行 Read 函数时,会自动根据 rot13 来转化相应的字母字符。
  • tour.golang.org/methods/23

解答

package main

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

type rot13Reader struct {
    r io.Reader
}

func rot13(c byte) byte {
    switch {
    case (c >= 'A' && c <= 'M') || (c >= 'a' && c <= 'm'):
        c += 13
    case (c >= 'N' && c <= 'Z') || (c >= 'n' && c <= 'z'):
        c -= 13
    }
    return c
}

func (reader *rot13Reader) Read(b []byte) (n int, err error) {
    n, err := reader.r.Read(b)
    for i := range b {
        b[i] = rot13(b[i])
    }
    return n, err
}


func main() {
    s := strings.NewReader("Lbh penpxrq gur pbqr!")
    r := rot13Reader{s}
    io.Copy(os.Stdout, &r)
}
复制代码
  1. 先根据 rot13 规则实现函数 func rot13(c byte) byte ,也就是 A-MN-Z 的兑换,以及 a-mn-z 的兑换
  2. 定义 rot13ReaderRead 函数,首先使用期包含的 r Reader 来读取数据,然后每一个数据都通过 rot13 转换,最终返回相应的数字结果

Exercise: Images

题目

  • 优化 Exercise: Slices 里实现的图片函数,这次实现一个 image.Image 图像而不仅仅是一个二维数组数据
  • 定义一个 Image type,并定义相应的 image.Image interface,其中
    • ColorModel 使用 color.RGBAModel
    • Bounds 使用 image.Rectangle type,并用 image.Rect(0, 0, w, h) 定义
    • At 会返回具体图片像素点上的颜色,最终用 color.RGBA{v, v, 255, 255} 定义
  • tour.golang.org/methods/25

解答

package main

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

type Image struct{
    W int
    H int
}

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 {
    v := uint8(x*y + y*y)
    return color.RGBA{v, v, 255, 255}
}

func main() {
    m := Image{200, 200}
    pic.ShowImage(m)
}
复制代码
  1. 因为要用到 image.Rect , color.RGBAModel , color.RGBA 因此,我们引入 "image""image/color" packages
  2. 定义相关函数,即可得到结果,这里 At 函数我沿用了之前的图片颜色计算公式 v := uint8(x*y + y*y)