Go 编程语言指南 练习题目分享

Go 编程语言指南-练习题目分享

‌Go 语言中文指南(Tour of Go 中文版)‌ 是专为中文开发者打造的权威学习平台,完美契合"Go语言学习之旅"这一核心概念。作为由 Go 官方团队直接支持并提供中文本地化内容的资源,它不仅是初学者踏入 Go 语言世界的理想起点,更是构建系统性知识体系的基石。该指南以清晰的逻辑结构和循序渐进的编排方式,引导学习者从基础语法到高级特性,全面掌握 Go 语言的精髓。

每一章节都精心设计了配套的练习题目,这些题目不仅紧扣章节主题,还注重实践性与趣味性,帮助学习者通过动手操作加深理解。

‌网站地址‌:https://tour.go-zh.org

练习:切片

题目内容

实现 Pic。它应当返回一个长度为 dy 的切片,其中每个元素是一个长度为 dx,元素类型为 uint8 的切片。当你运行此程序时,它会将每个整数解释为灰度值 (好吧,其实是蓝度值)并显示它所对应的图像。

图像的解析式由你来定。几个有趣的函数包括 (x+y)/2、xy、x^y、xlog(y) 和 x%(y+1)。

(提示:需要使用循环来分配 [][]uint8 中的每个 []uint8。)

(请使用 uint8(intValue) 在类型之间转换;你可能会用到 math 包中的函数。)

代码

GO 复制代码
package main

import "golang.org/x/tour/pic"
import "math"

func Pic(dx, dy int) [][]uint8 {
	result := make([][]uint8, dy)
	for x := range result {
		result[x] = make([]uint8, dx)
		for y := range result[x] {
			// result[x][y] = uint8((x+y)/2)
			// result[x][y] = uint8(x*y)
			// result[x][y] = uint8(x^y)
			// result[x][y] = uint8(x%(y+1))
			result[x][y] = uint8(float64(x) * math.Log(float64(y)))
		}
	}
	return result
}

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

练习:映射

题目内容

实现 WordCount。它应当返回一个映射,其中包含字符串 s 中每个"单词"的个数。 函数 wc.Test 会为此函数执行一系列测试用例,并输出成功还是失败。

你会发现 strings.Fields 很有用。

代码

Go 复制代码
package main

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

func WordCount(s string) map[string]int {
	result := make(map[string]int)
	split_arr := strings.Fields(s)
	for _, word := range split_arr {
		result[word] = result[word] + 1
	}
	return result
}

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

练习:斐波纳契闭包

题目

让我们用函数做些好玩的。

实现一个 fibonacci 函数,它返回一个函数(闭包),该闭包返回一个斐波纳契数列 (0, 1, 1, 2, 3, 5, ...)。

代码

Go 复制代码
package main

import "fmt"

// fibonacci 是返回一个「返回一个 int 的函数」的函数
func fibonacci() func() int {
	data_arr := [2]int{0, 1}
	count := 0
	return func() int {
		result := 0
		if count == 0 || count == 1 {
			result = data_arr[count]
		} else {
			result = data_arr[0] + data_arr[1]
			data_arr[0] = data_arr[1]
			data_arr[1] = result
		}
		count += 1
		return result
	}
}

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

练习:Stringer

题目

通过让 IPAddr 类型实现 fmt.Stringer 来打印点号分隔的地址。

例如,IPAddr{1, 2, 3, 4} 应当打印为 "1.2.3.4"。

代码

Go 复制代码
package main

import "fmt"

type IPAddr [4]byte

// TODO: 为 IPAddr 添加一个 "String() string" 方法。
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)
	}
}

练习:错误

题目

从之前的练习中复制 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 值。

代码

Go 复制代码
package main

import (
	"fmt"
)

type ErrNegativeSqrt float64

func (e ErrNegativeSqrt) Error() string {
	return fmt.Sprintf("cannot Sqrt negative number: %f", float64(e))
}

var cal_map map[float64]float64 = make(map[float64]float64)

func Sqrt(x float64) (float64, error) {
	if x < 0 {
		return 0, ErrNegativeSqrt(x)
	}
	z := x / 2
	m := float64(0)
	pre_m := cal_sub(z, x)
	step := 0.00001
	for ; m <= pre_m; pre_m = cal_sub(z-step, x) {
		z = z + step
		m = cal_sub(z, x)
	}
	return z - step, nil
}

func cal_sub(z float64, x float64) float64 {
	value, exists := cal_map[z]
	if exists {
		return value
	}
	m := z*z - x
	if m < 0 {
		m = 0 - m
	}
	cal_map[z] = m
	return m
}

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

练习:Reader

题目

实现一个 Reader 类型,它产生一个 ASCII 字符 'A' 的无限流。

代码

Go 复制代码
package main

import "golang.org/x/tour/reader"

type MyReader struct{}

// TODO: 为 MyReader 添加一个 Read([]byte) (int, error) 方法。
func (reader MyReader) Read(b []byte) (int, error) {
	b[0] = 'A'
	return 1, nil
}

func main() {
	reader.Validate(MyReader{})
}

练习:rot13Reader

题目

有种常见的模式是一个 io.Reader 包装另一个 io.Reader,然后通过某种方式修改其数据流。

例如,gzip.NewReader 函数接受一个 io.Reader(已压缩的数据流)并返回一个同样实现了 io.Reader 的 *gzip.Reader(解压后的数据流)。

编写一个实现了 io.Reader 并从另一个 io.Reader 中读取数据的 rot13Reader,通过应用 rot13 代换密码对数据流进行修改。

rot13Reader 类型已经提供。实现 Read 方法以满足 io.Reader。

代码

GO 复制代码
package main

import (
	"io"
	"os"
	"strings"
)

type rot13Reader struct {
	r io.Reader
}

func (reader rot13Reader) Read(p []byte) (n int, err error) {
	n, err = reader.r.Read(p)
	for i := range p {
		p[i] = p[i] - 1
	}
	return
}

func main() {
	s := strings.NewReader("Lbh penpxrq gur pbqr!")
	r := rot13Reader{s}
	io.Copy(os.Stdout, &r)
}

练习:图像

题目

还记得之前编写的图片生成器 吗?我们再来编写另外一个,不过这次它将会返回一个 image.Image 的实现而非一个数据切片。

定义你自己的 Image 类型,实现必要的方法并调用 pic.ShowImage。

Bounds 应当返回一个 image.Rectangle ,例如 image.Rect(0, 0, w, h)。

ColorModel 应当返回 color.RGBAModel。

At 应当返回一个颜色。上一个图片生成器的值 v 对应于此次的 color.RGBA{v, v, 255, 255}。

代码

Go 复制代码
package main

import "golang.org/x/tour/pic"
import "image"
import "image/color"

type Image struct {
	width  int
	height int
}

func (image Image) ColorModel() color.Model {
	return color.RGBAModel
}

func (i Image) Bounds() image.Rectangle {
	return image.Rect(0, 0, i.width, i.height)
}

func (image Image) At(x, y int) color.Color {
	return color.RGBA{uint8(x), uint8(y), 255, 255}
}

func main() {
	m := Image{
		width:  500,
		height: 300,
	}
	pic.ShowImage(m)
}

练习:等价二叉查找树

题目

  1. 实现 Walk 函数。

  2. 测试 Walk 函数。

函数 tree.New(k) 用于构造一个随机结构的已排序二叉查找树,它保存了值 k, 2k, 3k, ..., 10k。

创建一个新的信道 ch 并且对其进行步进:

go Walk(tree.New(1), ch)

然后从信道中读取并打印 10 个值。应当是数字 1, 2, 3, ..., 10.

  1. 用 Walk 实现 Same 函数来检测 t1 和 t2 是否存储了相同的值。

  2. 测试 Same 函数。

Same(tree.New(1), tree.New(1)) 应当返回 true,而 Same(tree.New(1), tree.New(2)) 应当返回 false。

Tree 的文档可在这里找到。

代码

Go 复制代码
package main

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

// Walk 遍历树 t,并树中所有的值发送到信道 ch。
func Walk(t *tree.Tree, ch chan int) {
	data := []*tree.Tree{t}
	for len(data) > 0 {
		p := data[0]
		ch <- p.Value
		if p.Left != nil {
			data = append(data, p.Left)
		}
		if p.Right != nil {
			data = append(data, p.Right)
		}
		data = data[1:]
	}
	close(ch)
}

// Same 判断 t1 和 t2 是否包含相同的值。
func Same(t1, t2 *tree.Tree) bool {
	ch_1 := make(chan int)
	ch_2 := make(chan int)
	go Walk(t1, ch_1)
	go Walk(t2, ch_2)
	for {
		data_t1, ok1 := <-ch_1
		data_t2, ok2 := <-ch_2
		println(data_t1, data_t2)
		println(ok1, ok2)
		if data_t1 != data_t2 {
			return false
		}
		if ok1 != ok2 {
			return false
		}
		if !ok1 && !ok2 {
			return true
		}
	}

}

func main() {
	println(Same(tree.New(1), tree.New(1)))
}

练习:Web 爬虫

题目

在这个练习中,我们将会使用 Go 的并发特性来并行化一个 Web 爬虫。

修改 Crawl 函数来并行地抓取 URL,并且保证不重复。

提示: 你可以用一个 map 来缓存已经获取的 URL,但是要注意 map 本身并不是并发安全的!

代码

Go 复制代码
package main

import (
	"fmt"
	"sync"
)

type Fetcher interface {
	// Fetch 返回 URL 所指向页面的 body 内容,
	// 并将该页面上找到的所有 URL 放到一个切片中。
	Fetch(url string, ch chan fakeResult)
}

// fakeFetcher 是待填充结果的 Fetcher。
type fakeFetcher map[string]*fakeResult

type fakeResult struct {
	body string
	urls []string
}

// 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/",
		},
	},
}

// SafeCounter 是并发安全的
type SafeCounter struct {
	mu sync.Mutex
	v  map[string]int
}

// Inc 对给定键的计数加一
func (c *SafeCounter) Inc(key string) {
	c.mu.Lock()
	// 锁定使得一次只有一个 Go 协程可以访问映射 c.v。
	c.v[key]++
	c.mu.Unlock()
}

// Value 返回给定键的计数的当前值。
func (c *SafeCounter) Value(key string) int {
	c.mu.Lock()
	// 锁定使得一次只有一个 Go 协程可以访问映射 c.v。
	defer c.mu.Unlock()
	return c.v[key]
}

var safe_counter = SafeCounter{v: make(map[string]int)}

func (f fakeFetcher) Fetch(url string, ch chan fakeResult) {
	safe_counter.Inc(url)
	if res, ok := f[url]; ok {
		ch <- *res
	}
	close(ch)
}

// Crawl 用 fetcher 从某个 URL 开始递归的爬取页面,直到达到最大深度。
func Crawl(url string, depth int, fetcher Fetcher) {

	if val := safe_counter.Value(url); val > 0 {
		fmt.Printf("url:%s 已爬取\n", url)
		return
	}
	if depth <= 0 {
		return
	}
	ch := make(chan fakeResult)
	go fetcher.Fetch(url, ch)
	res, ok := <-ch
	if !ok {
		fmt.Printf("未获取到url:%s 内容\n", url)
		return
	}
	fmt.Printf("found: %s %q\n", url, res.body)
	for _, u := range res.urls {
		Crawl(u, depth-1, fetcher)
	}
	return
}

func main() {
	Crawl("https://golang.org/", 4, fetcher)
}

相关文档地址

  1. Go语言包文档 https://pkg.go.dev/fmt
  2. Go教程 https://go.dev/doc/tutorial/
相关推荐
Nan_Shu_6148 小时前
学习: Threejs (2)
前端·javascript·学习
带土18 小时前
4. C++ static关键字
开发语言·c++
C++ 老炮儿的技术栈8 小时前
什么是通信规约
开发语言·数据结构·c++·windows·算法·安全·链表
@大迁世界8 小时前
TypeScript 的本质并非类型,而是信任
开发语言·前端·javascript·typescript·ecmascript
栗子叶8 小时前
Java对象创建的过程
java·开发语言·jvm
Amumu121388 小时前
React面向组件编程
开发语言·前端·javascript
学历真的很重要9 小时前
LangChain V1.0 Context Engineering(上下文工程)详细指南
人工智能·后端·学习·语言模型·面试·职场和发展·langchain
IT=>小脑虎9 小时前
Python零基础衔接进阶知识点【详解版】
开发语言·人工智能·python
wjs20249 小时前
C 标准库 - `<float.h>》详解
开发语言