本文是《Go 语言之旅》中“方法和接口(Methods and interfaces)”章节中的练习题,并附上了我自己在学习这部教程时写出的解答代码,供大家参考。
教程其他章节的练习题解答可以点击链接查看:
本文地址:https://www.jeddd.com/article/a-tour-of-go-exercises-methods.html
练习:Stringer
题目
通过让
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
练习:错误
题目
从之前的练习中复制
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
题目
实现一个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
题目
有种常见的模式是一个
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!
练习:图像
题目
还记得之前编写的图片生成器 吗?我们再来编写另外一个,不过这次它将会返回一个
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}
。
解答
别忘了引入 image
和 image/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
本文地址:https://www.jeddd.com/article/a-tour-of-go-exercises-methods.html