文章目录
- 练习:切片
- 练习:映射
- 练习:斐波纳契闭包
- 练习:Stringer
- 练习:错误
练习:切片
实现 Pic
。它应当返回一个长度为 dy
的切片,其中每个元素是一个长度为 dx
,元素类型为 uint8
的切片。当你运行此程序时,它会将每个整数解释为灰度值 (好吧,其实是蓝度值)并显示它所对应的图像。
图像的解析式由你来定。几个有趣的函数包括 (x+y)/2
、x*y
、x^y
、x*log(y)
和 x%(y+1)
。
(提示:需要使用循环来分配 [][]uint8
中的每个 []uint8
。)
(请使用 uint8(intValue)
在类型之间转换;你可能会用到 math
包中的函数。)
package mainimport ("math""golang.org/x/tour/pic"
)func Pic(dx, dy int) [][]uint8 {// 创建一个二维切片result := make([][]uint8, dy)for y := 0; y < dy; y++ {// 为每个 y 创建一个长度为 dx 的切片row := make([]uint8, dx)for x := 0; x < dx; x++ {// 使用一个公式来生成像素值,这里使用 x * log(y+1) 来生成图像row[x] = uint8(x * int(math.Log(float64(y+1))))}// 将该行添加到二维切片中result[y] = row}return result
}func main() {pic.Show(Pic)
}
练习:映射
实现 WordCount。它应当返回一个映射,其中包含字符串 s 中每个“单词”的个数。 函数 wc.Test 会为此函数执行一系列测试用例,并输出成功还是失败。
package mainimport ("strings""golang.org/x/tour/wc"
)func WordCount(s string) map[string]int {// 创建一个空的 map 来存储单词和它们的计数wordMap := make(map[string]int)// 使用 strings.Fields 将字符串切分成单词words := strings.Fields(s)// 遍历单词并更新 map 中对应单词的计数for _, word := range words {wordMap[word]++}return wordMap
}func main() {// 测试函数wc.Test(WordCount)
}
练习:斐波纳契闭包
让我们用函数做些好玩的。
实现一个 fibonacci
函数,它返回一个函数(闭包),该闭包返回一个斐波纳契数列 (0, 1, 1, 2, 3, 5, …)。
package mainimport "fmt"// 返回一个函数,生成斐波纳契数列
func fibonacci() func() int {// 初始化前两个斐波纳契数a, b := 0, 1return func() int {// 返回当前的 a 值(即斐波纳契数列的下一个数)fib := a// 更新斐波纳契数列,a = b,b = a + ba, b = b, a+breturn fib}
}func main() {f := fibonacci()for i := 0; i < 10; i++ {// 每次调用 f(),返回下一个斐波纳契数fmt.Println(f())}
}
练习:Stringer
通过让 IPAddr 类型实现 fmt.Stringer 来打印点号分隔的地址。 例如,IPAddr{1, 2, 3, 4} 应当打印为 “1.2.3.4”。
package mainimport ("fmt"
)// 定义 IPAddr 类型,它是一个包含 4 个字节的数组
type IPAddr [4]byte// 实现 fmt.Stringer 接口
func (ip IPAddr) String() string {// 返回通过 fmt.Sprintf 格式化后的点号分隔的 IP 地址return fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3])
}func main() {// 定义一个 IPAddr 类型的变量并打印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)}
}
googleDNS: 8.8.8.8
loopback: 127.0.0.1
练习:错误
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
值。
package mainimport ("fmt""math"
)// 创建自定义错误类型
type ErrNegativeSqrt float64// 实现 error 接口的 Error 方法
func (e ErrNegativeSqrt) Error() string {return fmt.Sprintf("cannot Sqrt negative number: %v", float64(e))
}// 实现 Sqrt 函数
func Sqrt(x float64) (float64, error) {if x < 0 {return 0, ErrNegativeSqrt(x)}// 计算平方根return math.Sqrt(x), nil
}func main() {// 测试 Sqrt 函数fmt.Println(Sqrt(2))fmt.Println(Sqrt(-2))
}
- 代码解析:
ErrNegativeSqrt 类型:我们定义了一个新的类型 ErrNegativeSqrt,它是 float64 类型的别名。
Error 方法:为 ErrNegativeSqrt 类型实现了 Error() 方法。在该方法中,使用 fmt.Sprintf 来格式化错误消息。要注意的是,直接调用 fmt.Sprint(e) 会导致死循环,因为 fmt.Sprint(e) 会试图再次调用 Error() 方法,导致无限递归。因此,需要先将 e 转换为 float64 类型,再使用 fmt.Sprint(float64(e)) 格式化输出。
Sqrt 函数:在 Sqrt 函数中,我们首先检查 x 是否为负数,如果是负数,则返回自定义的 ErrNegativeSqrt 错误。否则,使用 math.Sqrt 计算并返回平方根。
- 输出示例:
1.4142135623730951 <nil>
0 cannot Sqrt negative number: -2
- 为什么直接 fmt.Sprint(e) 会导致死循环?
因为 fmt.Sprint(e) 会隐式调用 e.Error() 方法。如果你在 Error() 方法内再次调用 fmt.Sprint(e),它又会再次调用 Error(),从而陷入无限递归。因此,我们在 Error() 方法中使用 fmt.Sprint(float64(e)),确保先将 e 转换为 float64,避免再次调用 Error() 方法。