官方教程 A Tour of Go Excercises 我的答案总结及讲解
2015 年 5 月 21 日
这两天学完了 A Tour of Go 官方的语法教学,里面有很多的 Excercise(训练题)。希望对大家有用,如果有其他人也写过,并觉得我写的不对的,求教!:heart:
Exercise: Loops and Functions
题目
- 给一个 number
x
,我们通过 loop 和 function 来找到其平方根z
,即z² = x
- tour.golang.org/flowcontrol…
解答
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)) } 复制代码
-
z := x/2
这个是猜测的初始值(也可以像是题目里的 hint 写的设置成 1) -
math.Abs(z*z - x) > 0.0000000001
用最优解的逻辑就是给了一个 tolerance0.0000000001
,即我们用计算公式算出来的z²
与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) } 复制代码
-
pic := make([][]uint8, dy)
先建一个数组,长度是dy
,数组里每个元素的内容是一个数组[]uint8
-
pic[i] = make([]uint8, dx)
在数组里的第i
个元素里,我们再创造一个[]uint8
数组,长度为dx
-
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) } 复制代码
-
strings.Files(s)
这个函数会自动切分一个字符串到一个数组,每个数组里是一个 word - 建立
map
然后在数组里每当某一个word
出现,就相应的增加 1
Exercise: Fibonacci closure
题目
- 实现一个
fibonacci
函数,使其返回一个函数 - 这个函数会连续的输出斐波那契数列
- tour.golang.org/moretypes/2…
解答
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()) } } 复制代码
- closure 也叫闭包,意思是一个函数中需要使用的某一个变量是在此函数外定义的
- 在上面的
func fibonacci() func() int
中,返回的是一个函数func() int
,而这个函数每次运行返回的是一个int
- 在
fibonacci()
中,变量a
和b
定义在函数fibonacci()
里,并被此函数返回的return func() int { ... }
函数引用到,也就是说在返回的函数里a
和b
两个变量一直存储在内存中,且数值会一直变化 -
f := fibonacci()
中f
是fibonacci()
返回的函数,在初始情况中,此时的a, b := 0, 1
- 以第一次
f()
调用为例:-
c := a
:c
赋值为a
即 0 -
a, b = b, a+b
:a
赋值为b
即 1,b
赋值为a+b
即 1 -
return c
,返回 0,也就是斐波那契数列的 第一个值
-
- 第二次
f()
调用,注意此时a
是 1,b
是 1:-
c := a
:c
赋值为a
即 1 -
a, b = b, a+b
:a
赋值为b
即 1,b
赋值为a+b
即 2 -
return c
,返回 1,也就是斐波那契数列的 第二个值
-
- 以此类推,循环中
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) } } 复制代码
- 引入
strconv.Itoa
,int to string -
IPAddr
是一个大小为 4 的[]byte
,我们生成一个[4]string
数组s
,每一个元素是 IP 地址中的一位,并且我们用strconv.Itoa(int(val))
把它转换为字符串 -
strings.Join(s, ".")
将字符串数组用"."
连起来 - 这样在使用
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) } } 复制代码
-
error
类型是一个 built-in interface,需要定义Error() string
函数,测试一个error
type 是否是nil
是定义函数返回是否出错的方法。例如:i, err := strconv.Atoi("42")
返回值中i
代表函数返回数值,而err
如果不是nil
的话,则表示有错误发生 -
func (e ErrNegativeSqrt) Error() string
定义了ErrNegativeSqrt
属于error
的Error()
函数,也就简洁说明ErrNegativeSqrt
是一个error
-
func Sqrt(x float64) (float64, error)
函数返回两个数值,前者为参数的平方根,后者为error
,当后者不是nil
的时候,在Println
中会自动调用Error()
输出相应的错误信息字符串。
Exercise: Readers
题目
- 实现一个
Reader
type,以输出一个无限个'A'
的字符流 - tour.golang.org/methods/22
解答
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{}) } 复制代码
- 因为
MyReader
会输出无限个'A'
,因此只要输入参数b []byte
不是一个空的 Buffer,就会写满 - 当
bLength == 0
也就是 Bufferb
是空时,返回ErrEmptyBuffer(b)
错误 - 只要不是空,就都填满
'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) } 复制代码
- 先根据
rot13
规则实现函数func rot13(c byte) byte
,也就是A-M
与N-Z
的兑换,以及a-m
与n-z
的兑换 - 定义
rot13Reader
的Read
函数,首先使用期包含的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) } 复制代码
- 因为要用到
image.Rect
,color.RGBAModel
,color.RGBA
因此,我们引入"image"
和"image/color"
packages - 定义相关函数,即可得到结果,这里
At
函数我沿用了之前的图片颜色计算公式v := uint8(x*y + y*y)