GO语言的实战案例课后作业
1.修改猜谜游戏的最终代码,使用fmt.Scanf来简化代码
(1)课堂代码
package main
import (
"bufio"
"fmt"
"math/rand"
"os"
"strconv"
"strings"
"time"
)
func main() {
maxNum := 100
// 随机种子
rand.Seed(time.Now().UnixNano())
secretNumber := rand.Intn(maxNum)
fmt.Println("Please input your guess")
reader := bufio.NewReader(os.Stdin)
// 带入循环,支持多次猜测数字
for {
input, err := reader.ReadString('\n')
if err != nil {
fmt.Println("An error occured while reading input. Please try again", err)
continue
}
//在这里需要再添加对"\r"的处理,否则将输出Invalid input. Please enter an integer value
input = strings.TrimSuffix(input, "\n")
input = strings.TrimSuffix(input, "\r")
guess, err := strconv.Atoi(input)
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
}
}
}
(2)实现过程
bufio与fmt.Scanf
bufio 和 fmt.Scanf 都是 Go 语言中用于读取用户输入的方法,它们具有不同的特点和适用场景。
bufio:bufio提供了一个用于缓冲读取的接口,可以方便地读取用户输入的文本行或特定分隔符之前的内容。使用bufio的NewReader函数创建一个读取器,可以通过调用其ReadString或ReadBytes等方法进行读取操作。bufio提供了更灵活的读取方式,并且对于处理大量输入以及逐行读取的情况效果更好。fmt.Scanf:fmt.Scanf是fmt包提供的一个函数,用于从标准输入读取格式化的输入数据。它根据指定的格式字符串解析输入并将结果存储在变量中。fmt.Scanf对于简单的格式化输入非常方便,可以针对特定的格式进行快速、类型安全的解析。它适用于对输入格式要求较为严格的情况,比如需要按照特定格式输入的数字、字符串等。
fmt.Scanf用法
golang官方文档对fmt包的详细说明 在 Go 语言官方文档中,
fmt.Scanf 函数的具体描述如下:
func Scanf(format string, a ...interface{}) (n int, err error)
-
Scanf 根据 format 参数指定的格式字符串从标准输入中读取输入,并根据需要将解析的结果存储到传递给函数的参数中。
-
格式字符串 format 包含普通字符(非 '%')和转义序列('%'加上一个特定的字母)。参与解析的参数必须是指针,用于接收解析出的值。对于每个转义序列,Scanf 会读取输入中的连续字符,直到遇到下一个空白字符(默认情况下是空格、制表符和换行符)为止。
Scanf 返回成功解析的参数数量和可能的错误。如果解析过程中发生错误,则停止解析,并返回错误信息。
- 在 format 字符串中,可以使用以下格式化字符:
%d:以十进制解析一个有符号整数。%s:解析一个字符串,直到遇到下一个空白字符为止。%f:解析一个浮点数。%t:解析一个布尔值(true 或 false)。%v:根据参数的类型进行解析(支持大部分基本类型)。
- 除了这些基本的格式化字符之外,还可以使用更复杂的格式化规范,例如指定宽度、精度和填充字符。 此外,Scanf 还支持一些特殊字符,如空白字符、换行符和要求匹配的字符等。
需要注意的是,在使用 fmt.Scanf 进行输入解析时,要确保输入的数据与格式字符串一致,否则可能导致解析错误或得到意外的结果。
修改代码
使用 fmt.Scanf 函数来读取用户输入的整数,避免了使用 bufio.NewReader 和读取字符串的操作。这样可以简化代码并提高用户输入的便捷性。
- 修改后的代码如下
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
maxNum := 100
// 随机种子
rand.Seed(time.Now().UnixNano())
secretNumber := rand.Intn(maxNum)
fmt.Println("Please input your guess")
var guess int
for {
_, err := fmt.Scanf("%d", &guess)
if err != nil {
fmt.Println("Invalid input. Please enter an integer value")
continue
}
fmt.Println("Your 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
}
}
}
(3)最终代码
如果运行上面代码可以发现:在上面的代码中,与原先代码一样,没办法简单消除回车键带来的影响,通过官方文档了解,想要消除回车符(\r)的计入,可以使用 fmt.Scanln 函数替代。fmt.Scanln 函数会读取一行输入,并以空格分隔输入的各项内容。
- -以下为最终代码
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
maxNum := 100
// 随机种子
rand.Seed(time.Now().UnixNano())
secretNumber := rand.Intn(maxNum)
fmt.Println("Please input your guess")
var guess int
for {
_, err := fmt.Scanln(&guess)
if err != nil {
fmt.Println("Invalid input. Please enter an integer value")
continue
}
fmt.Println("Your 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
}
}
}
2.修改命令行字典的最终代码,加入另一种翻译引擎的支持
(1)课堂代码(节选)
(2)实现过程(百度在线翻译为例)
- 导入需要的包:
import "github.com/buger/jsonparser"
- 在
query函数中,创建一个新的 HTTP 请求并发送到百度翻译 API。替换以下代码块:
// 创建请求
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
if err != nil {
log.Fatal(err)
}
为:
// 创建请求
req, err := http.NewRequest("GET", "https://fanyi.baidu.com/v2transapi", nil)
if err != nil {
log.Fatal(err)
}
// 添加查询参数
q := req.URL.Query()
q.Add("from", "en")
q.Add("to", "zh")
q.Add("query", word)
req.URL.RawQuery = q.Encode()
- 发送 HTTP 请求并获取响应。替换以下代码块:
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
为:
// 发送请求
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
// 读取响应内容
bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
// 解析响应JSON
result, _, _, err := jsonparser.Get(bodyText, "trans_result", "[0]", "dst")
if err != nil {
log.Fatal(err)
}
translation := string(result)
- 将翻译结果打印出来。在
query函数的最后添加以下代码:
fmt.Println("Translation:", translation)
(3)最终代码:
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"strings"
"github.com/buger/jsonparser"
)
type DictRequest struct {
TransType string `json:"trans_type"`
Source string `json:"source"`
UserID string `json:"user_id"`
}
/*中间代码省略*/
func query(word string) {
client := &http.Client{}
request := DictRequest{TransType: "en2zh", Source: word}
buf, err := json.Marshal(request)
if err != nil {
log.Fatal(err)
}
var data = strings.NewReader(string(buf))
req, err := http.NewRequest("GET", "https://fanyi.baidu.com/v2transapi", nil)
if err != nil {
log.Fatal(err)
}
q := req.URL.Query()
q.Add("from", "en")
q.Add("to", "zh")
q.Add("query", word)
req.URL.RawQuery = q.Encode()
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
result, _, _, err := jsonparser.Get(bodyText, "trans_result", "[0]", "dst")
if err != nil {
log.Fatal(err)
}
translation := string(result)
fmt.Println("Translation:", translation)
}
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, `usage: simpleDict WORD
example: simpleDict hello
`)
os.Exit(1)
}
word := os.Args[1]
query(word)
}
在上述步骤的基础上,并行请求两个翻译引擎来提高响应速度
(1)Go语言的goroutine和channel机制
通过上面的百度翻译引擎的实现过程,不难发现需要修改的代码段集中在query 函数之中,但是即使设置两个函数分别对应彩云和百度,输出结果是串行而非并行。那么如何实现并行呢?通过搜索,发现了GO之中的goroutine和channel机制。
goroutine和channel机制的官方文档
- Goroutines: golang.org/doc/effecti…
- Channels: golang.org/doc/effecti…
在Go语言的官方文档中,goroutine 和 channel 是并发编程的两个重要概念。它们提供了一种有效的方式来实现并发编程,充分利用多核处理能力,并确保数据的安全传递。下面是对它们的简要描述:
- Goroutine(协程):
- Goroutine 是 Go 语言提供的一种轻量级的并发执行单位。
- 使用关键字
go来启动一个 Goroutine,可以将一个函数调用或函数字面量(匿名函数)放在go后面即可。 - Goroutine 在逻辑上类似于线程,但可以更轻松地创建和管理成千上万个 Goroutine。
- Goroutine 是由 Go 运行时系统调度的,它负责在多个逻辑处理器上进行任务的并发执行。
- Goroutine 之间通过通信来共享数据。
- Channel(通道):
- Channel 是用来实现 Goroutine 之间通信和同步的机制。
- Channel 类似于管道,可以在 Goroutine 之间传递数据。
- 使用
make函数创建一个 Channel,并使用<-操作符进行发送和接收数据。 - Channel 可以是无缓冲的(阻塞型),也可以是有缓冲的(非阻塞型)。
- 无缓冲的 Channel 要求发送和接收操作同时准备好,而有缓冲的 Channel 则允许缓冲一定量的元素。
Goroutine 和 Channel 的用法和简单示例:
- Goroutine 的用法:
- 使用关键字
go启动一个 Goroutine,将函数调用或函数字面量(匿名函数)放在go后面即可。 - Goroutine 在函数返回时自动结束,也可以使用
runtime.Goexit()提前结束 Goroutine。 - Goroutine 可以同时执行成千上万个,不会消耗大量的内存。
简单示例:
package main
import (
"fmt"
"time"
)
func hello() {
fmt.Println("Hello Goroutine!")
}
func main() {
go hello() // 启动一个 Goroutine
time.Sleep(1 * time.Second) // 等待1秒,确保 Goroutine 执行完
fmt.Println("Main goroutine exit")
}
- Channel 的用法:
- 使用
make()函数创建一个 Channel,指定发送和接收数据的类型。 - 使用
<-操作符来发送和接收数据。 - 默认情况下,Channel 是阻塞的,意味着发送和接收操作会阻塞当前 Goroutine 直到对应的操作准备好。
- 可以通过设置缓冲区来创建非阻塞的 Channel。
简单示例:
package main
import "fmt"
func numbers(ch chan int) {
for i := 1; i <= 5; i++ {
ch <- i // 发送数据到通道
}
close(ch) // 关闭通道,表示发送数据完成
}
func main() {
ch := make(chan int) // 创建一个整数类型的无缓冲通道
go numbers(ch) // 启动一个 Goroutine 来发送数据到通道
for num := range ch {
fmt.Println(num) // 从通道接收数据
}
fmt.Println("Main goroutine exit")
}
这个示例中,我们创建了一个无缓冲通道 ch,使用 go numbers(ch) 启动一个 Goroutine 来发送数据到通道。主函数中通过 for range 循环从通道接收数据,直到通道被关闭。
(2)最终代码
因此对原先代码,我们可以使用goroutine来并行发送两个翻译引擎的请求,并将翻译结果发送到一个通道中。然后,我们使用一个循环来等待并接收所有翻译结果,并进行打印处理。
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"strings"
"github.com/buger/jsonparser"
)
type DictRequest struct {
TransType string `json:"trans_type"`
Source string `json:"source"`
UserID string `json:"user_id"`
}
/*中间部分省略*/
func query(word string, translationChan chan string) {
client := &http.Client{}
// 请求百度翻译引擎
baiduReq, err := http.NewRequest("GET", "https://fanyi.baidu.com/v2transapi", nil)
if err != nil {
log.Fatal(err)
}
baiduQuery := baiduReq.URL.Query()
baiduQuery.Add("from", "en")
baiduQuery.Add("to", "zh")
baiduQuery.Add("query", word)
baiduReq.URL.RawQuery = baiduQuery.Encode()
go func() {
resp, err := client.Do(baiduReq)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
result, _, _, err := jsonparser.Get(bodyText, "trans_result", "[0]", "dst")
if err != nil {
log.Fatal(err)
}
translation := string(result)
// 发送翻译结果到通道
translationChan <- translation
}()
// 请求其他(彩云)翻译引擎,这里省略具体请求的代码
// 等待所有并发请求完成
// 这里只请求了两个翻译引擎,如果有多个引擎,可以根据实际情况调整
for i := 0; i < 2; i++ {
select {
case translation := <-translationChan:
fmt.Println("Translation:", translation)
}
}
}
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, `usage: simpleDict WORD
example: simpleDict hello
`)
os.Exit(1)
}
word := os.Args[1]
// 创建翻译结果通道
translationChan := make(chan string)
// 启动查询
query(word, translationChan)
close(translationChan)
}
总结
bufio与fmt.Scanf与fmt.Scanln
当处理用户输入或从标准输入读取数据时,Go语言中的bufio、fmt.Scanf和fmt.Scanln都是常用的工具。下面是对它们的总结性描述:
- bufio包:
- bufio是Go语言标准库中的一个包,提供了高效的I/O缓冲机制。
- 使用bufio可以提高读取和写入性能,减少I/O操作次数。
- bufio主要提供了三种类型的缓冲器:
Scanner、Writer和Reader。 - Scanner用于从输入源读取数据,并将其拆分为指定的分隔符。
- Writer用于将数据写入输出源。
- Reader用于从输入源读取数据。
- fmt.Scanf函数:
- fmt.Scanf是Go语言
fmt包中的一个函数,用于从标准输入中读取数据并根据格式字符串进行解析。 - 它通过格式化的方式来读取输入,可以按照指定的格式从用户输入中提取数据。
- 格式字符串中使用占位符来指定所需数据类型和顺序,并使用空格或其他分隔符分隔各个值。
- fmt.Scanln函数:
- fmt.Scanln是Go语言
fmt包中的一个函数,用于从标准输入中读取数据并按照空格分隔存储到提供的变量中。 - 它会一直等待用户输入,并在用户按下回车键后将数据存储到提供的变量中。
- 它适用于需要从标准输入中读取多个值并将它们以空格分隔的情况。
这些工具在处理用户输入或从标准输入读取数据时非常有用。使用bufio可以在读写大量数据时提高性能,而fmt.Scanf和fmt.Scanln则提供了根据特定格式读取和解析输入的便捷方法。
GO语言中的串行与并行
在Go语言中,串行和并行是两种处理任务的方式。
- 串行处理:
- 串行处理是指按照顺序逐个执行任务或操作。
- 在串行处理中,每个任务必须等待前一个任务完成后才能开始执行。
- 串行处理适合于单核或单线程环境,任务之间可能会有依赖关系。
- 并行处理:
- 并行处理是指同时执行多个任务或操作。
- 在并行处理中,多个任务可以并发执行,不需要等待其他任务完成。
- 并行处理可以充分利用多核处理器的能力,提高程序的执行效率。
- 并行处理适合于需要同时处理大量独立任务的场景。
在Go语言中,可以使用goroutine和channel实现并行处理:
- Goroutine是Go语言中轻量级的执行单元,可以同时执行成千上万个。
- 使用关键字"go"可以启动一个goroutine,在其后放置函数调用或函数字面量(匿名函数)即可。
- Goroutine在函数返回时自动结束,也可以使用
runtime.Goexit()提前结束goroutine。 - 使用channel来进行goroutine之间的通信和同步。
- Channel是一种类型安全的通信机制,可以在goroutine之间传递数据。