Go 语言之旅:习题解答之“方法和接口”模块

·  阅读 148

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第14天,点击查看活动详情

前言

之前在学习 Go 的官网教程时,发现它里面的练习好像没有答案,所以在这里分享一下自己写的解答,给新入门的 Gopher 提供一个对照,如果有疑问或者有更好的方法,欢迎大家在评论区里一起讨论。

英文版:A Tour of Go

中文版:Go 语言之旅

方法和接口模块

Stringer

英文版:golang.google.cn/tour/method…

中文版:tour.go-zh.org/methods/18

题目简述

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

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

解答

package main

import "fmt"
import "strings"

type IPAddr [4]byte

func (ip IPAddr) String() string {
	s := make([]string, 0, len(ip))
	for _, v := range ip {
		s = append(s, fmt.Sprintf("%d", v))
	}
	return 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)
	}
}
复制代码

先创建一个和 ip 一样长(容量)的字符串切片,注意初始长度要为0,不然后面追加的时候就从初始长度后面开始加了。然后遍历 ip 只取值,通过 Sprintf 将字节转为字符串,注意这里不可以直接用类型转换。最后用 strings.Join 方法添加 . 即可。

我这里写的答案会更加泛用一点,如果仅对于这道题来说,你也可以直接:

return fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3])
复制代码

错误

英文版:golang.google.cn/tour/method…

中文版:tour.go-zh.org/methods/20

题目简述

之前的练习中复制 Sqrt 函数并修改它,使其接受一个负数时,返回 ErrNegativeSqrt (自定义的错误类型)值,打印错误值得到如 "cannot Sqrt negative number: -2" 的提示。

你需要创建一个新的类型:

type ErrNegativeSqrt float64
复制代码

并为其实现:

func (e ErrNegativeSqrt) Error() string
复制代码

以满足 error 接口, 与 fmt.Stringer 类似,fmt 包在打印值时会寻找 error 接口。

解答

package main

import (
	"fmt"
	"math"
)

type ErrNegativeSqrt float64

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

func Sqrt(x float64) (float64, error) {
	if x < 0 {
		return x, ErrNegativeSqrt(x)
	}
	if x == 0 {
		return 0, nil
	}
	z := x / 2
	d := 1.0
	for math.Abs(d) > 1e-15 {
		d = (z*z - x) / (2 * z)
		z -= d
	}
	return z, nil
}

func main() {
	if v, err := Sqrt(-0.1); err != nil {
		fmt.Println(err)
	} else {
		fmt.Println(v)
	}
}
复制代码

要注意一点是,在 Sprint 里打印 ErrNegativeSqrt 类型值时,会递归调用 Error 方法,所以应该先对其进行类型转换。

或者也可以使用 Sprintf 方法:

return fmt.Sprintf("cannot Sqrt negative number: %g", e)
复制代码

Reader

英文版:golang.google.cn/tour/method…

中文版:tour.go-zh.org/methods/22

题目简述

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

io.Reader 接口需要一个 Read 方法:

func (T) Read(b []byte) (n int, err error)
复制代码

解答

package main

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

type MyReader struct{}

func (MyReader) Read(b []byte) (int, error) {
	b[0] = 'A'
	return 1, nil
}

func main() {
	reader.Validate(MyReader{})
}
复制代码

直接令 b[0]='A' , 返回填充的字节数为 1 比较省事,也符合题意。但如果切片大小为零可能会出现错误,读取效率也不高,所以一般来讲应该像下面这样写:

func (MyReader) Read(b []byte) (int, error) {
        for i := range b { 
                b[i] = 'A' 
        } 
        return len(b), nil
}
复制代码

rot13Reader

英文版:golang.google.cn/tour/method…

中文版:tour.go-zh.org/methods/23

题目简述

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

自定义一个 rot13Reader 类型,包含一个 Read 方法来实现 io.Reader 接口,该方法从另一个 io.Reader 中读取数据,通过应用 rot13 代换密码对数据流进行修改。

rot13 是一种简单的替换密码,它将字母表中前13个字母和后13个字母一一对应进行相互替换。编码和解码是相同的操作。

ROT13_example.png

wikipedia ROT13

解答

package main

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

type rot13Reader struct {
	r io.Reader
}

func (rot *rot13Reader) Read(b []byte) (int, error) {
	n, err := rot.r.Read(b)
	for i := range b {
		if unicode.IsLetter(rune(b[i])) {
			if b[i] >= 'A' && b[i] < 'N' || b[i] >= 'a' && b[i] < 'n' {
				b[i] += 13
			} else {
				b[i] -= 13
			}
		}
	}
	return n, err
}

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

输出结果是: You cracked the code!

图像

英文版:golang.google.cn/tour/method…

中文版:tour.go-zh.org/methods/25

题目简述

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

定义你自己的 Image 类型,实现必要的方法:

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

并调用 pic.ShowImage

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

ColorModel 应当返回 color.RGBAModel

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

解答

package main

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

type Image struct {
	w, h int
}

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

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

func (Image) At(x, y int) color.Color {
	v := (x + y) / 2
	return color.RGBA{uint8(v), uint8(v), 255, 255}
}

func main() {
	m := Image{256, 256}
	pic.ShowImage(m)
}
复制代码

At 方法定义了每一个位置的像素是什么颜色。你可以像上一个图片生成器一样,通过更改 v 的公式来生成不同的图案;或者你也可以更改 color.RGBA 的值来进行更自由的创作。

总结

本篇文章是对方法和接口模块的习题解答,包括 fmt.Stringer, builtin.error, io.Reader, image.Image 这四个接口的实现。如果大家有更好的答案,欢迎在评论区留言。

最后,如果本篇文章对你有所帮助,求 点赞、收藏、评论,感谢支持 ✧(≖ ◡ ≖✿

分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改