Go语言实战案例 课后作业

66 阅读5分钟

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
       }
    }
}

这个代码有一个缺点:在读入错误时按照输入字符数量会多次输出错误提示(如下图)

image.png 所以不建议使用%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. 在上一步骤的基础上,修改代码实现并行请求两个翻译引擎来提高响应速度

整体思路

通过并行地调用两个不同的 “翻译函数”(这里简化为query1query2,实际可对应真实的翻译引擎接口函数)来翻译同一个文本内容,以期望提高获取翻译结果的速度。并行处理的关键在于利用 Go 语言中的协程(goroutine)和等待组(sync.WaitGroup)机制来同时发起两个独立的翻译请求,并在两个请求都完成后再统一处理结果。

关键组件及作用

1. 协程(goroutine

在 Go 语言中,协程是一种轻量级的线程,可以让函数在独立的执行路径上并发运行

2. 等待组(sync.WaitGroup

sync.WaitGroup是用于协调多个协程同步执行的一个工具。它主要有三个方法:AddDoneWait

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)
}