在本节GO语言工程实践课后作业中,我的任务是:修改上一篇文章中猜谜游戏的最终代码,使用fml.Scanf来简化代码的实现。
修改猜谜游戏
实现
注释掉的代码是原本的代码,注释的代码下面是我修改的代码。这样更方便对比和分析。
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')
_, err := fmt.Scanf("%d", &guess)
if err != nil {
fmt.Println("An error occured while reading input. Please try again", err)
continue
}
//input = strings.Trim(input, "\r\n")
//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
}
}
}
分析
在原来的代码中:
-
reader := bufio.NewReader(os.Stdin)创建一个reader读取器。 -
input, err := reader.ReadString('\n')用于从控制带读取一行文本直到换行,所得到的input字符串不包括换行。如果读取期间发生错误,err会被设置为非空值;otherwise,err为nil。这个方法的调用有err生成的可能,所以需要处理一下错误。 -
input = strings.Trim(input, "\r\n")去除字符串两端的回车符和换行符,然后guess, err :=strconv.Atoi(input)将字符串转化为整型存储到guess里,后续进行比较。这个方法的调用有err生成的可能,所以需要处理一下错误。
使用fml.Scanf的代码:
_, err := fmt.Scanf("%d", &guess)代替了原来代码中的那么多操纵。- 前面为
_是因为我们并不关心这个函数的返回值,即这个函数的返回值我们不需要用,所以就用空白标识符。这个函数的返回值是成功读取的值的数量。 - guess前加
&是因为fmt.Scanf操作需要修改传入的变量的值,但GO语言中函数参数的传递默认是值传递(即函数内部操作的是参数的副本,而不是原始参数本身),所以需要使用指针使得控制台的输入可以传递给guess变量,否则修改的数无法映射到变量上。 %d是一个格式化动词,用于在格式化字符串中表示一个整数占位符。所以,如果从控制台读取的数不是一个整数,那么就会出现err(即err为非空值)。
实现效果
实现的效果和修改前的代码是一样的。
讨论
既然
fml.Scanf可以如此简化代码,那么它是否可以替代修改前的代码所用到的那些读取控制行输入的包?
当然是NO!HOW DARE YOU!
不过,还是要视情况而定。比如,在这个例子中,我只需要输入简单的整型数字,那么我可以考虑使用fml.Scanf来降低代码量。但是,fmt.Scanf 只能读取指定格式的数据,比如:
%d:十进制整数。%f:浮点数。%s:字符串。%c:单个字符。%t:布尔值(true 或 false)。%v:自动推断类型。
而当更复杂的输入出现时,它是无法处理的。
所以,这时候我们考虑使用bufio 和 strconv 包等,它们虽然需要更大的代码量,但是对更复杂的输入格式的处理、对输入进行更多的验证和处理的时候,它们的灵活度要比fmt.Scanf高很多。
In conclusion, 它们各有各的优点,我们不能“一棒子打死”某一个。我们所能做的,就是视情况对它们的使用进行抉择,找到更适合我们当前代码处理情况的,该简洁的时候简洁,该具体的时候具体,合理利用。
小小的感慨
因为我自身是学Java的,两年前接触过一点C和C++,我觉得GO会更像C一点点,比如指针啦、数据格式的输出啦、scan操作以及代码的整体结构包括main方法、import等,看起来比Java的要求更多一些,但是整体的代码长度也会比Java更简洁一点。
继续学习!感觉强度稍微有一点大,入门指南才刚刚跟上就开始上一个level实战小项目(虽然只是在原来的基础上改改代码)。