本文是《Go 语言之旅》中“方法和接口(Methods and interfaces)”章节中的练习题,并附上了我自己在学习这部教程时写出的解答代码,供大家参考。

教程其他章节的练习题解答可以点击链接查看:

  1. 基础
  2. 方法和接口(本文)
  3. 并发

本文地址:https://www.jeddd.com/article/a-tour-of-go-exercises-methods.html

练习:Stringer

题目

题目链接:中文 | English

通过让 IPAddr 类型实现 fmt.Stringer 来打印点号分隔的地址。

例如,IPAddr{1, 2, 3, 4} 应当打印为 "1.2.3.4"

解答

package main

import "fmt"

type IPAddr [4]byte

// TODO: 给 IPAddr 添加一个 "String() string" 方法
func (ip IPAddr) String() string {
    return fmt.Sprintf("%d.%d.%d.%d", 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)
    }
}

运行结果如下。

loopback: 127.0.0.1
googleDNS: 8.8.8.8

练习:错误

题目

题目链接:中文 | English

之前的练习中复制 Sqrt 函数,修改它使其返回 error 值。

Sqrt 接受到一个负数时,应当返回一个非 nil 的错误值。复数同样也不被支持。

创建一个新的类型

type ErrNegativeSqrt float64

并为其实现

func (e ErrNegativeSqrt) Error() string

方法使其拥有 error 值,通过 ErrNegativeSqrt(-2).Error() 调用该方法应返回 "cannot Sqrt negative number: -2"

注意:Error 方法内调用 fmt.Sprint(e) 会让程序陷入死循环。可以通过先转换 e 来避免这个问题:fmt.Sprint(float64(e))。这是为什么呢?

修改 Sqrt 函数,使其接受一个负数时,返回 ErrNegativeSqrt 值。

解答

初学者可能会对 error 的理解存在疑问。error 类型是一个内置的接口(interface),存放了实现了 Error() 方法的变量。若一个函数的返回值是 error 类型,说明该函数要返回一个实现了 Error() 方法的变量。在本题中 Sqrt 函数返回了 error 类型,因此我们需要给 ErrNegativeSqrt 类型实现 Error() 方法。

package main

import (
    "fmt"
    "math"
)

type ErrNegativeSqrt float64

/* 为ErrNegativeSqrt类型实现Error()方法 */
func (e ErrNegativeSqrt) Error() string {
    return fmt.Sprint("cannot Sqrt negative number:", float64(e))
}

/* 返回类型中的error是一个实现了Error()的接口 */
func Sqrt(x float64) (float64, error) {
    if x < 0 {
        return 0, ErrNegativeSqrt(x)
    }
    z := 1.0                      // 猜测初始值
    prev := 0.0                   // 上次迭代的结果
    for math.Abs(z-prev) > 1e-5 { // 当值改变非常小的时候退出循环
        prev = z
        z -= (z*z - x) / (2 * z)
    }
    return z, nil
}

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

运行结果如下。

1.4142135623746899 <nil>
0 cannot Sqrt negative number:-2

练习:Reader

题目

题目链接:中文 | English

实现一个 Reader 类型,它产生一个 ASCII 字符 'A' 的无限流。

解答

package main

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

type MyReader struct{}

// TODO: 给 MyReader 添加一个 Read([]byte) (int, error) 方法
func (r MyReader) Read(buf []byte) (int, error) {
    for i := range buf {
        buf[i] = 'A'  // 将字节切片填满字符'A'
    }
    return len(buf), nil
}

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

运行结果如下。

OK!

本文地址:https://www.jeddd.com/article/a-tour-of-go-exercises-methods.html

练习:rot13Reader

题目

题目链接:中文 | English

有种常见的模式是一个 io.Reader 包装另一个 io.Reader,然后通过某种方式修改其数据流。

例如,gzip.NewReader 函数接受一个 io.Reader(已压缩的数据流)并返回一个同样实现了 io.Reader*gzip.Reader(解压后的数据流)。

编写一个实现了 io.Reader 并从另一个 io.Reader 中读取数据的 rot13Reader,通过应用 rot13 代换密码对数据流进行修改。

rot13Reader 类型已经提供。实现 Read 方法以满足 io.Reader

解答

package main

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

type rot13Reader struct {
    r io.Reader
}

func (r rot13Reader) Read(buf []byte) (int, error) {
    n, err := r.r.Read(buf) // 读取内部字节流
    for i, ch := range buf {
        if ch >= 'a' && ch <= 'z' {
            buf[i] = (ch-'a'+13)%26 + 'a' // 转换小写字母
        } else if ch >= 'A' && ch <= 'Z' {
            buf[i] = (ch-'A'+13)%26 + 'A' // 转换大写字母
        }
    }
    return n, err
}

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

运行结果如下。

You cracked the code!

练习:图像

题目

题目链接:中文 | English

还记得之前编写的图片生成器 吗?我们再来编写另外一个,不过这次它将会返回一个 image.Image 的实现而非一个数据切片。

定义你自己的 Image 类型,实现必要的方法并调用 pic.ShowImage

Bounds 应当返回一个 image.Rectangle ,例如 image.Rect(0, 0, w, h)

ColorModel 应当返回 color.RGBAModel

At 应当返回一个颜色。上一个图片生成器的值 v 对应于此次的 color.RGBA{v, v, 255, 255}

解答

别忘了引入 imageimage/color 两个包。

package main

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

type Image struct {
    w int // 宽度
    h int // 高度
}

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

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

func (img Image) At(x, y int) color.Color {
    v := uint8(x ^ y) // 修改这里以产生不同的图像
    return color.RGBA{v, v, 255, 255}
}

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

运行结果如下。另外,修改第 23 行的公式(见题目),可以产生不同的图像。

References

  1. Go 语言之旅
  2. A Tour of Go

本文地址:https://www.jeddd.com/article/a-tour-of-go-exercises-methods.html