最近要学习分布式系统,需要用go语言做lab。记录一下自己学习go语言的过程,以及学习过程中遇到的一些问题。
Go 语言之旅是Go语言官方提供的学习go语言基本语法的指南,是新手学习Go语言非常好的材料。花半天到一天的时间过一遍,可以快速上手go语言。
私以为学习一门编程语言的最好方式就是直接“看代码”,通过理解代码实现的特定功能去理解这门语言。掌握基本语法之后,就可以直接上手写了,配合文档查漏补缺。这样学习编程语言比之通过看视频学习来的更快,更容易得到反馈。
下面是Go 语言之旅
中练习的答案。仅供参考。
练习:循环与函数
循环10次
package main
import (
"fmt"
"math"
)
func Sqrt(x float64) float64 {
// 赋初值 1
z := float64(1)
// 迭代 10 次,更新 z 值
for i := 0; i <= 10; i++ {
// 根据牛顿法公式计算 z 的新值
z = z - (z*z - x) / (2 * z)
}
return z
}
func main() {
fmt.Println(Sqrt(2))
fmt.Println(math.Sqrt(2))
}
无限接近
package main
import (
"fmt"
"math"
)
func Sqrt(x float64) float64 {
z := 1.0
count := 0 //循环次数
temp := z - (z*z - x) / (2*z)
// 反复更新z值,直到满足精度要求
for math.Abs(temp - z) > 1e-14 {
z = temp
temp = z - (z*z - x) / (2*z)
count += 1
}
fmt.Println("the count of loo :", count)
return z
}
func main() {
fmt.Println(Sqrt(2))
fmt.Println(math.Sqrt(2))
}
练习:切片
package main
import (
"fmt"
"golang.org/x/tour/pic"
)
// Pic 函数接受两个整数参数 dx 和 dy 表示图像大小,
// 返回一个二维数组,该数组包含每个像素的灰度值。
func Pic(dx, dy int) [][]uint8 {
// 使用 make 创建一个长度为 dy 的切片,
// 每个元素都是一个长度为 dx 的 uint8 类型切片。
a := make([][]uint8, dy)
fmt.Println("slice:", a)
// 对切片 a 进行循环迭代,
// x 代表 a 中的每个元素(即每个一维 uint8 类型切片)。
for x := range a {
// 使用 make 创建一个长度为 dx 的 uint8 类型切片 b。
b := make([]uint8, dx)
// 对切片 b 进行循环迭代,
// y 代表 b 中的每个元素(即每个像素的横坐标)。
for y := range b {
// 将当前像素的灰度值设为其横纵坐标之积。
b[y] = uint8(x * y)
}
// 将切片 b 添加到切片 a 中。
a[x] = b
}
// 返回形如 [[0 0 0 ... 0] [0 1 2 ... 255] ... [0 2 4 ... 510]] 的二维 uint8 类型切片。
return a
}
func main() {
// 使用函数 Show 显示 Pic 函数返回的图像。
pic.Show(Pic)
}
练习:映射
package main
import (
"golang.org/x/tour/wc"
"strings"
)
// 定义函数 WordCount,接收一个字符串类型的参数 s,返回一个 map 类型的结果
func WordCount(s string) map[string]int {
m := make(map[string]int) // 创建一个空 map,这跟上面a := make([][]uint8, dy)的含义是不一样的,
for _, c := range strings.Split(s, " ") { // 将字符串按空格分割成单词,并遍历这些单词
m[c] += 1 // 统计每个单词出现的次数,如果该单词在 map 中不存在,则初始化为 0,再加上 1
}
return m // 返回统计好的 map
}
// 主函数
func main() {
wc.Test(WordCount) // 测试 WordCount 函数是否正确
}
练习:斐波纳契闭包
package main
import "fmt"
// 返回一个“返回类型为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())
}
}
练习:Stringer
前提知识:
在 Go 中,如果某个类型实现了
String()
方法,那么在使用%v
格式化字符串时,将会自动调用该类型的String()
方法。所以在这段代码中,当我们使用%v
格式化ip
变量时,Go 语言会自动调用IPAddr
类型的String()
方法,然后将其返回值插入到格式化字符串中。
package main
import "fmt"
type IPAddr [4]byte
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)
}
}
练习:错误
前提知识:
在调用fmt包中的打印函数(包括:
fmt.Println
fmt.Print
fmt.Printf
fmt.Sprint
fmt.Sprintf
fmt.Sprintln
fmt.Fprint
fmt.Fprintf
fmt.Fprintln
)时,如果传入的参数实现了Error()方法,那么就会自动调用该方法并将返回结果输出到控制台。后面给出源码示例。
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 {
return 0, ErrNegativeSqrt(x)
}
z := 1.0
count := 0 //循环次数
temp := z - (z*z - x) / (2*z)
for math.Abs(temp - z) > 1e-14 {
z = temp
temp = z - (z*z - x) / (2*z)
count += 1
}
fmt.Println("the count of loo :", count)
return z, nil
}
func main() {
fmt.Println(Sqrt(2))
fmt.Println(Sqrt(-2))
}
以Println为例,查看函数如何最终调用Error方法。源码地址:# Source file src/fmt/print.go
Println中调用了Fprintln
Fprintln中调用了doPrint
doPrint中调用了printArg
printArg方法中调用了handleMethods方法
如果参数是error类型则调用它的Error方法。
为什么要将e转换为float64(e)。
根本原因:fmt.Sprintf会在内部调用ErrNegativeSqrt
类Error()方法,在Error方法中又会继续调用下去,这样就产生了死循环。
至于为什么fmt.Sprintf会去调用Error()方法,除了通过上面源码的方式证明外,也可以直观很理解,因为 %v
格式化标识符只能用于常规(非自定义)类型的值,而此时的e是一个ErrNegativeSqrt
自定义类,所以编译器会试图将ErrNegativeSqrt
转换为string类型,ErrNegativeSqrt
实现了error接口,所以就会去调用Error()方法。;
练习:Reader
package main
import "golang.org/x/tour/reader"
type MyReader struct{}
func (reader MyReader) Read(b []byte) (n int, err error){
for i := range b {
b[i] = 'A'
}
return len(b), nil
}
func main() {
reader.Validate(MyReader{})
}
练习:rot13Reader
package main
import (
"io"
"os"
"strings"
)
type rot13Reader struct {
r io.Reader
}
func (r rot13Reader) Read(b []byte) (n int, err error){
//读取数据
n, err = r.r.Read(b)
//应用 rot13 代换密码对数据流进行修改。
for i := 0; i < n; i++ {
c := b[i]
if c >= 'a' && c <= 'z' {
b[i] = 'a' + (c-'a'+13)%26
} else if c >= 'A' && c <= 'Z' {
b[i] = 'A' + (c-'A'+13)%26
}
}
return n, err
}
func main() {
s := strings.NewReader("Lbh penpxrq gur pbqr!")
r := rot13Reader{s}
io.Copy(os.Stdout, &r)
}
练习:图像
package main
import (
"image"
"image/color"
"golang.org/x/tour/pic"
)
type myImage struct {
width int
height int
}
func (m myImage) Bounds() image.Rectangle {
return image.Rect(0, 0, m.width, m.height) //两点定义一个矩形Pt(x0, y0), Pt(x1, y1)
}
func (m myImage) ColorModel() color.Model {
return color.RGBAModel //RGBA即(红、绿、蓝、Alpha)Alpha 通道表示透明度
}
func (m myImage) At(x, y int) color.Color {
v := uint8((x * y))
return color.RGBA{v, v, 255, 255}
}
func main() {
m := myImage{256, 256}
pic.ShowImage(m)
}
练习:等价二叉查找树
package main
import (
"golang.org/x/tour/tree"
"fmt"
)
// Walk 步进 tree t 将所有的值从 tree 发送到 channel ch。
func Walk(t *tree.Tree, ch chan int) {
if t != nil {
Walk(t.Left, ch)
ch <- t.Value
Walk(t.Right, ch)
}
}
// Same 检测树 t1 和 t2 是否含有相同的值。
func Same(t1, t2 *tree.Tree) bool {
ch1, ch2 := make(chan int), make(chan int)
go Walk(t1, ch1)
go Walk(t2, ch2)
for i := 0; i < 10; i++ {
if <-ch1 != <-ch2 {
return false
}
}
return true
}
func main() {
fmt.Println(Same(tree.New(1), tree.New(1)))
fmt.Println(Same(tree.New(1), tree.New(-11)))
}
练习:Web 爬虫
package main
import (
"fmt"
"sync"
)
// Cache 是一个线程安全的数据结构,用于跟踪已经访问过的url
type Cache struct {
v map[string]bool // map用于存储已经访问过的url
mux sync.Mutex // 互斥锁用于在并发环境中保护map数据结构
}
// cache 是全局的Cache实例,用于在整个程序中跟踪已访问的url
var cache = Cache{v: make(map[string]bool)}
// visited 方法检查给定的url是否已经被访问过
func (cache *Cache)visited(key string) bool {
cache.mux.Lock()
_, ok := cache.v[key]
cache.mux.Unlock()
return ok
}
// visit 方法标记一个url已被访问
func (cache *Cache)visit(key string) {
cache.mux.Lock()
cache.v[key] = true
cache.mux.Unlock()
}
type Fetcher interface {
Fetch(url string) (body string, urls []string, err error)
}
// Crawl 函数抓取给定的url和相关的链接,深度为depth。
// 如果给定的url已经被访问过,或者深度为0,则Crawl会立即返回。
func Crawl(url string, depth int, fetcher Fetcher) {
if depth <= 0 || cache.visited(url) {
return
}
body, urls, err := fetcher.Fetch(url)
cache.visit(url)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("found: %s %q\n", url, body)
done := make(chan bool) // 创建一个channel用于跟踪goroutine的完成
for _, u := range urls {
go func(u string){
Crawl(u, depth-1, fetcher)
done <- true // 当爬取完成时,发送一个信号到channel
}(u)
}
for range urls {
<- done // 等待一个信号,表明一个goroutine已经完成
}
return
}
func main() {
Crawl("https://golang.org/", 4, fetcher)
}
// fakeFetcher 是返回若干结果的 Fetcher。
type fakeFetcher map[string]*fakeResult
type fakeResult struct {
body string
urls []string
}
func (f fakeFetcher) Fetch(url string) (string, []string, error) {
if res, ok := f[url]; ok {
return res.body, res.urls, nil
}
return "", nil, fmt.Errorf("not found: %s", url)
}
// fetcher 是填充后的 fakeFetcher。
var fetcher = fakeFetcher{
"https://golang.org/": &fakeResult{
"The Go Programming Language",
[]string{
"https://golang.org/pkg/",
"https://golang.org/cmd/",
},
},
"https://golang.org/pkg/": &fakeResult{
"Packages",
[]string{
"https://golang.org/",
"https://golang.org/cmd/",
"https://golang.org/pkg/fmt/",
"https://golang.org/pkg/os/",
},
},
"https://golang.org/pkg/fmt/": &fakeResult{
"Package fmt",
[]string{
"https://golang.org/",
"https://golang.org/pkg/",
},
},
"https://golang.org/pkg/os/": &fakeResult{
"Package os",
[]string{
"https://golang.org/",
"https://golang.org/pkg/",
},
},
}