Go语言实战案例 课后作业
1. 修改第一个例子猜谜游戏里面的最终代码,使用 fmt.Scanf 来简化代码实现
fmt.Scanf是 Go 语言中用于从标准输入(通常是键盘)读取格式化输入的函数。
n, err := fmt.Scanf(format string, a...interface{})
format:这是一个格式化字符串,用于指定输入的格式,类似于printf中的格式化字符串,但用于解析输入。它决定了如何解释输入的数据。a:这是一个可变参数列表,用于接收解析后的值。这些参数必须是指针类型,这样Scanf函数才能将读取的值赋给它们。n:表示成功读取的输入项的数量。err:如果在读取过程中出现错误,例如输入格式不匹配,则err会包含相应的错误信息。
格式化字符串示例
package main
import (
"fmt"
)
func main() {
// 示例1:读取单个整数
var num int
_, err := fmt.Scanf("%d", &num)
if err == nil {
fmt.Printf("读取的整数是: %d\n", num)
}
// 示例2:读取两个整数
var num1, num2 int
_, err = fmt.Scanf("%d %d", &num1, &num2)
if err == nil {
fmt.Printf("读取的两个整数分别是: %d 和 %d\n", num1, num2)
}
// 示例3:读取一个字符串
var str string
_, err = fmt.Scanf("%s", &str)
if err == nil {
fmt.Printf("读取的字符串是: %s\n", str)
}
}
注意事项
- 输入格式必须匹配:如果输入的内容与
format指定的格式不匹配,Scanf函数会返回错误,并且变量可能不会被正确赋值。 - 指针参数:传递给
Scanf的参数必须是指针类型,这样函数才能修改它们的值。如果忘记使用指针,程序在编译时不会报错,但在运行时可能会出现意外行为。 - 处理换行符:
Scanf在读取输入时,会根据格式化字符串中的格式来处理输入。对于%d、%s等格式,它会跳过前面的空白字符(包括换行符)开始读取。但如果需要精确控制换行符的处理,可以使用%c格式来读取单个字符,包括换行符。
使用fmt.Scanf 简化后的代码
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
maxNum := 100
rand.Seed(time.Now().UnixNano())
secretNumber := rand.Intn(maxNum)
// fmt.Println("The secret number is ", secretNumber)
fmt.Println("Please input your guess")
var guess int
for {
_, err := fmt.Scanf("%d\n", &guess)
if err != nil {
fmt.Println("Invalid input. Please enter an integer value")
continue
}
fmt.Println("You guess is", guess)
if guess > secretNumber {
fmt.Println("Your guess is bigger than the secret number. Please try again")
} else if guess < secretNumber {
fmt.Println("Your guess is smaller than the secret number. Please try again")
} else {
fmt.Println("Correct, you Legend!")
break
}
}
}
这个代码有一个缺点:在读入错误时按照输入字符数量会多次输出错误提示(如下图)
所以不建议使用%d直接读取整数,应该用%s (代码如下)
// 使用fmt.Scanf读取用户输入的字符串,格式为%s,即读取一个字符串直到遇到换行符
_, err := fmt.Scanf("%s\n", &input)
if err != nil {
fmt.Println("读取输入时出错:", err)
return
}
guess, err := strconv.Atoi(input)
if err != nil {
fmt.Println("Invalid input. Please enter an integer value")
continue
}
2. 修改第二个例子命令行词典里面的最终代码,增加另一种翻译引擎的支持
- 谷歌翻译,抓包,鼠标右击拷贝请求代码
- 通过 curlconverter.com/go/ 转换为go语言
- 修改代码,写数据类型,封装函数,提取字段。
3. 在上一步骤的基础上,修改代码实现并行请求两个翻译引擎来提高响应速度
整体思路
通过并行地调用两个不同的 “翻译函数”(这里简化为query1和query2,实际可对应真实的翻译引擎接口函数)来翻译同一个文本内容,以期望提高获取翻译结果的速度。并行处理的关键在于利用 Go 语言中的协程(goroutine)和等待组(sync.WaitGroup)机制来同时发起两个独立的翻译请求,并在两个请求都完成后再统一处理结果。
关键组件及作用
1. 协程(goroutine)
在 Go 语言中,协程是一种轻量级的线程,可以让函数在独立的执行路径上并发运行
2. 等待组(sync.WaitGroup)
sync.WaitGroup是用于协调多个协程同步执行的一个工具。它主要有三个方法:Add、Done和Wait。
3. 互斥锁(sync.Mutex)
-
sync.Mutex用于保护共享资源的并发访问安全。下面代码中,results数组就是共享资源,因为两个协程都需要向这个数组中添加自己获取到的翻译结果。 -
当一个协程要向
results数组添加结果时,需要先通过mutex.Lock()获取互斥锁,这样就可以保证在这个协程添加结果的过程中,其他协程无法同时访问和修改results数组。 -
sync.WaitGroup是用于协调多个协程同步执行的一个工具
package main
import (
"fmt"
"sync"
)
// 略
func query1(word string) (string, error) {
return "好1", nil
}
func query2(word string) (string, error) {
return "好2", nil
}
func main() {
textToTranslate := "Hello"
var mutex sync.Mutex
var results []string
var wg sync.WaitGroup
// 为两个翻译引擎请求各添加一个等待组计数
wg.Add(2)
// 并行请求百度翻译引擎
go func() {
defer wg.Done()
translatedText, err := query1(textToTranslate)
if err != nil {
fmt.Println("百度翻译出错:", err)
return
}
mutex.Lock()
results = append(results, translatedText)
mutex.Unlock()
}()
// 并行请求有道翻译引擎
go func() {
defer wg.Done()
translatedText, err := query2(textToTranslate)
if err != nil {
fmt.Println("有道翻译出错:", err)
return
}
mutex.Lock()
results = append(results, translatedText)
mutex.Unlock()
}()
// 等待两个翻译引擎的请求都完成
wg.Wait()
// 这里可以根据具体需求处理两个翻译结果,比如选择其中一个作为最终结果
fmt.Println("翻译结果:", results)
}