Go语言之旅(一)

最近要学习分布式系统,需要用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

image-20230616222405176

Fprintln中调用了doPrint

image-20230616222332753

doPrint中调用了printArg

image-20230616222250007

printArg方法中调用了handleMethods方法

image-20230616221816377

如果参数是error类型则调用它的Error方法。

image-20230616222112732

为什么要将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/",
		},
	},
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值