Go语言第一课:猜数游戏 | 青训营

461 阅读3分钟

写在前面

第一节课《Go 语言基础语法》课程中,老师带着我写了一个猜数游戏。我在学习编程语言的时候对用户交互输入都比较疏忽,因此这是我第一次接触go的Reader以及Scanf函数。这里把我上课过程中的思考以及课后作业记录下来。

代码回顾

rand.Seed() 已经弃用

这个程序我是跟着老师一点点敲下来的,刚开始老师先写了一个生成随机数的例子:

func main() {
	maxNum := 100
	secretNumber := rand.Intn(maxNum)
	fmt.Println("The secret number is ", secretNumber)
}

他提到反复使用go run main.go运行程序时会产生一样的输出

image.png

因此,需要使用rand.Seed()设置当前时间戳为随机数种子。但是,我在实际运行的时候并非如此,甚至这个函数已经有deprecated标签了,这里应该是出现了一些版本差异。

我在文档中找到了答案(可以鼠标悬浮在包名上导航到对应的文档)。 在文档里写道

函数文档链接 pkg.go.dev/math/rand#S…

If Seed is not called, the generator is seeded randomly at program startup.

Prior to Go 1.20, the generator was seeded like Seed(1) at program startup. ...

Deprecated: As of Go 1.20 there is no reason to call Seed with a random value. ...

这段提到在Go 1.20版本之前,程序启动时总是使用Seed(1)来初始化随机数序列,导致了每次运行生成的随机数一致。但是Go 1.20已经更改了默认的初始化行为,避免了这个问题,因此这个函数已经被弃用了。

而我的本地环境使用的是Go 1.20.6版本,老师在录制视频时还没有这个版本更新,导致我们之间的代码出现行为差异。

控制台换行行为差异

老师使用的平台在命令行换行时插入\n,但Windows在换行时会插入\r\n。这使得范例代码中strings.Trim(input, "\n")替换不完全,在之后做ASCII转换时一定会出现报错。替换内容修改为\r\n"可以解决这个问题。如果之后使用Scanf,似乎不会遭遇这个问题,大概粉装的时候做过智能处理。

改进猜数游戏

任务要求:修改第一个猜谜游戏里面的最终代码,使用fmt.Scanf来简化代码实现。

fmt.Scanf函数的定义如下,它和C语言的scanf用法相似,但是在返回成功扫描的数量的基础上,会额外返回失败的原因。

func Scanf(format string, a ...any) (n int, err error)

因此,这里需要处理这个可能的err,其余部分不用做修改,全部代码如下:

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    maxNum := 100
    // version newer than 1.20 no longer need this line
    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
        }
    }
}

这里有一个坑点是如果输入非法的内容,会多次触发Scanf的错误,猜测可能是Scanf在没有扫描到数字时就直接报错返回了,而代码在continue之后再次扫描到buffer中余下的\n,再次导致报错。

我搜索了一下,发现Go没有提供清空缓冲区的函数,不像C语言一样可以flush一下。因此,要解决这个问题,确实只能使用bufio创建Reader,手动去判断和丢弃字符,因此两种方法各有优劣。