猜数游戏 - fmt.Scanf 换行符处理 | 青训营笔记

108 阅读2分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天。今天试着写了下课后作业,结果遇到了个 BUG,在这里记录下。

猜数游戏:初始时程序随机生成一个 0 到 100 之间的整数,玩家每次输入一个猜测的整数,如果没有猜中,程序返回玩家猜测的数是偏大还是偏小,接下来玩家继续猜测,如此循环直到猜中为止。

下面是我写的简化版的猜数游戏,使用 fmt.Scanf 读取输入。

package main

import (
	"fmt"
	"math/rand"
)

func main() {
	maxNum := 100
	secretNum := rand.Intn(maxNum)
	for {
		var inputNum int
		fmt.Print("Your guess number: ")
                fmt.Scanf("%d", &inputNum)
		if inputNum < secretNum {
			fmt.Println("Your guess is smaller than the secret number. Please try again!")
		} else if inputNum > secretNum {
			fmt.Println("Your guess is bigger than the secret number. Please try again!")
		} else {
			fmt.Printf("The secret number is %d\n", secretNum)
			break
		}
	}
}

在运行时,发现输出并不符合预期:

image-20230115232124693.png

明明只输入一个数字,却输出了两行 "try again"。

因为之前学 C 语言的时候遇到过很多次类似的问题,所以很快确定不是代码逻辑问题,而应该是换行符的问题,于是写了下面的代码验证一下:

func main() {
	var a int
	_, err := fmt.Scanf("%d", &a)
	fmt.Println(err)
	fmt.Printf("input: %d\n", a)
	_, err = fmt.Scanf("%d", &a)
	fmt.Println(err)
	fmt.Printf("input: %d\n", a)
}

同样是输入一个数,输出了两条语句:

image-20230115235120554.png

第一次 fmt.Scanf 没的返回错误信息,而第二次则返回了 unexpected newline.

那么很明显了,应该是第一个 fmt.Scanf 读取时将换行符保留在了 stdin 中,后面又被第二个 fmt.Scanf 读取了(因为读取时出错了,所以 a 的值不变)。

其实这个 fmt.Scanf 函数介绍已经解答了这个问题,来看看 fmt.Scanf 的函数介绍:

image-20230116001929425.png

划重点:"Newline in the input must match newlines in the format."

也就是说,在使用 fmt.Scanf 读取标准输入中包含的换行符时需要用 '\n' 与之匹配。这一点与 C 语言中的 scanf 是不一样的,C 语言中的 scanf 遇到换行符时会视为空白符,直接忽略,并不需要 '\n' 与之匹配。

所以在这里应该给 Scanf 补上 '\n':

func main() {
	var a int
	_, err := fmt.Scanf("%d\n", &a)
	fmt.Println(err)
	fmt.Printf("input: %d\n", a)
	_, err = fmt.Scanf("%d\n", &a)
	fmt.Println(err)
	fmt.Printf("input: %d\n", a)
}

image-20230116003132579.png

嗯,这下输出正常了。

像这种题目在之前学 C 语言的时候已经写了很多遍,本来没打算写的,没想到随手写了下还是能发现问题,所以学写代码还是得多尝试多动手。