写在前面
第一节课《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运行程序时会产生一样的输出:
因此,需要使用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,手动去判断和丢弃字符,因此两种方法各有优劣。