官方 Go 语言学习之旅练习题
Aidan Engineer

最近在学习 Go 语言,不管什么东西先去看官方文档总是最标准的,发现官方文档里的 Go 语言学习之旅 质量是真高啊,还贴心的搭配了练习题,做练习的过程很有意思,部分题难度是有的所以也借鉴了很多别人的写法,这里总结一下

Hello,世界

任何语言的必经之路了属实是

1
2
3
4
5
6
7
package main

import "fmt"

func main() {
fmt.Println("Hello, 世界 ")
}

循环与函数

练习地址

使用牛顿法来开平方,公式为:z=(z×zx)/(2×z)z -= (z×z - x) / (2×z),这里主要是注意精度的判断,但是精度过小的情况下可能又会有超时的情况出现,所以注意取舍,可以使用循环次数的方式代替精度判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"fmt"
)

func Sqrt(x float64) float64 {
z := x
next := float64(0)
for {
z = z - (z*z-x)/2*z
// 可以使用 math 包下的 Abs() 简写,精度的判断也可以使用 1e-10
if (next > z && (next-z) < 0.1) || (z > next && (z-next) < 0.1) {
break
}
next = z
}
return next
}

func main() {
fmt.Println(Sqrt(9))
}

切片

练习地址

从这道题开始意识到 Go 的简洁和强大,less is more 不仅是说说的,切片的存在大大的提高了数组的使用效率

这道题其实就是一个简单的二维数组赋值的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

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

func Pic(dx, dy int) [][]uint8 {
// 创建 dx 长度的二维数组
a := make([][]uint8, dx)
for x := range a {
// dy 长度的子数组
b := make([]uint8, dy)
for y := range b {
// 使用公式填充
b[y] = uint8(x % (y + 1))
}
a[x] = b
}
return a
}

func main() {
pic.Show(Pic)
}

映射

练习地址

使用 strings. Fields() 分解字符串,然后使用 map 计数即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

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

func WordCount(s string) map[string]int {
// 创建一个 "string": int 的 map 集合
m := make(map[string]int)
c := strings.Fields(s)
// 遍历数组,将字符作为下标进行累加
for _, val := range c {
m[val]++
}

return m
}

func main() {
wc.Test(WordCount)
}

斐波纳契闭包

练习地址

采用闭包这种将函数和变量绑定的方式在很多场景下可以做出一些取巧的操作,比如斐波纳契数列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "fmt"

// 返回一个“返回 int 的函数”
func fibonacci() func() int {
x, y := 1, 1
// 使用外部的变量返回函数
return func() int {
result := x
x, y = y, x+y
return result
}
}

func main() {
f := fibonacci()
for i := 0; i < 10; i++ {
fmt.Println(f())
}
}

Stringer

练习地址

使用实现接口的方式格式化输出,有点像 Java 里的 toString() 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

type IPAddr [4]byte

// TODO: 给 IPAddr 添加一个 "String() string" 方法
func (ip IPAddr) String() string {
// 直接格式化即可
return fmt.Sprintf("%v.%v.%v.%v", 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)
}
}

Error

练习地址

同样是实现接口的方式实现 Error() 函数,但留了一个陷阱:

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

我认为这里应该是类型递归调用的锅, return fmt.Sprint(e) 实际上是 return fmt.Sprint(e.Error()) 所以就会持续查找本身的 Error 语句,改为 float64(e) 的形式转换为基本数据类型就可以了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main

import (
"fmt"
)

type ErrNegativeSqrt float64

func (e ErrNegativeSqrt) Error() string {
// 使用 `float64(e)` 的形式
return fmt.Sprintf("cannot Sqrt negative number: %v", float64(e))
}

func Sqrt(x float64) (float64, error) {
if x <= 0 {
// 自身会根据返回类型调用 `Error()`
return float64(0), ErrNegativeSqrt(x)
}

z := x
next := float64(0)
for {
z = z - (z*z-x)/2*z
if (next > z && (next-z) < 0.1) || (z > next && (z-next) < 0.1) {
break
}
next = z
}
return next, nil
}

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

暂时先写到这里,后边的练习有点太点到即止了,看的似懂非懂所以不敢说会

参考:Golang 学习向导练习题 @xialingsc 夏令

  • 本文标题:官方 Go 语言学习之旅练习题
  • 本文作者:Aidan
  • 创建时间:2021-12-29 19:37:54
  • 本文链接:https://aidanblog.top/practice-documentation/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
 评论