go语言实践:猜数字| 青训营

111 阅读5分钟

go语言实践

在学习go的基本语法后,就要开始实践了,这篇先记录在第二节课中讲到:猜数字

猜数字

首先先捋清楚这个代码的基本逻辑,很简单,就是用随机函数生成一个数字,然后通过无限循环的方式不断让用户输入一个数字,直到用户输入到正确的数字为止。

生成随机数

go语言中,通过

var res = rand.Intn(maxNum)

来生成一个随机数并且存放在变量res中

maxNum为生成随机数的上限

但是在c语言的学习中,我们知道代码中的随机函数都是伪随机的,本质上是通过一些数字生成随机数种子来进行随机数生成的,常用的随机种子就是当前的时间,同理这里我们也需要设置随机数种子,不然的话就会一直生成同一个数字,生成随机数种子的代码如下

rand.Seed(time.Now().UnixNano()) //用系统时间初始化随机数种子

用户输入

在聊go语言前,我们先回忆一下c++的输入方式,常见的有两种 一种是scanf输入,另一种是cin输入,后者是通过流来输入 在c++中,我们用scanf输入,经常会发现尾部的换行符没有读入,导致影响到了下一次读入。

在go语言中,也有scanf函数输入,也存在同样的问题

比如说如下代码:

for {  
fmt.Println("please input your guess")  
var num int  
fmt.Scanf("%d", &num) 
fmt.Println("your input is:", num)  
if num == res {  
fmt.Println("you are right")  
break  
} else if num > res {  
fmt.Println("please guess a small number")  
} else {  
fmt.Println("please guess a big number")  
}  
}

输出的结果为:

please input your guess
12
your input is: 12
please guess a small number
please input your guess
your input is: 0
please guess a big number
please input your guess

可以发现,在我们输入一个12后,这个循环执行了两次,第一次正常读入12,第二次读入了0

其实第二次的0就是读入时的换行符(或者空格)导致的,当我们把scanf中修改为:

fmt.Scanf("%d%s", &num) 

这时候的输出就是:

please input your guess
12
your input is: 12
please guess a big number
please input your guess

这里我们把后面的空格还有回车读入了,但是不存起来,就不会出现这样的问题。 这里我们看看scanf的原型函数:

image.png 其实scanf两个返回值n和err,n是按指定格式成功输入数据的个数,err是读取过程中遇到的错误,如果没有错误,err的值就为nil。 我们来尝试一下第一个代码到底发送了什么:

fmt.Scanf("%d%s", &num, &s) //需要两个%d后面那个读入回车符号,也可以改成%s  
fmt.Println("your input is:", s)

输入12 输出为:your input is: 虽然看起来啥都没有,但是其实这里是有一个回车符号。

除了scanf,go中还有scan和scanln两个输入。

既然把scanf函数搞懂了,再来看看scan和scanln又是怎么回事 首先,它们的函数原型

image.png

image.png 跟scanf差不多,都是有两个返回值,一个是读取成功个数,另一个是错误值

但是如果像刚刚那样用会发生什么

for {  
  
fmt.Println("please input your guess")  
var num int  
fmt.Scan(&num) //需要两个%d后面那个读入回车符号,也可以改成%s  
fmt.Println("your input is:", num)  
if num == res {  
fmt.Println("you are right")  
break  
} else if num > res {  
fmt.Println("please guess a small number")  
} else {  
fmt.Println("please guess a big number")  
}  
}

输出为:(12是输入)

please input your guess
12
your input is: 12
please guess a big number
please input your guess

用scanln的输出为:

please input your guess
12
your input is: 12
please guess a big number
please input your guess

其实scanln再换行的时候会把缓冲区的回车也收走,但是scan和scanf不会,所以就导致了scanf不能分多行输入数据。但是scan却可以,它虽然没有收走缓冲区的回车符,但是不会把回车符读进去,遇到回车它会继续读取下一个数据,而scanf会按照我们给的格式(如%d去读取数据),但是肯定读不进去的,所以就读取失败了

总结一下 scanf:按照给定的格式依次读取数据(包括非法数据),不能换行输入(如果要换行需要在前面加一个scanln吸收掉回车符,就像c语言中的getchar) scan:比scanf高级,依次读取数据,遇到回车会忽略,可以换行输入(如果要先用了scan输入,再用scanf输入的话,需要在中间加一个scanln) scanln:类似scan,但是遇到换行(回车)立马结束输入,如果要换行输入必须用多个scanln

除了用scanf输入,在c++中我们常用流来输入,就是用cin 在go语言中也有同样的操作,每个程序执行的时候都会打开几个文件,stdin stdout stderr等,stdin 文件(也就是输入流)可以用 os.Stdin 来得到。

但是,直接操作这个文件很不方便,我们会用 bufio.NewReader 把一个文件转换成一个 reader 变量。 reader 会有很多用来操作流的操作,我们可以用它的 ReadString 方法来读取一行。 如果读取失败,则打印错误并退出。注意,ReadString 返回的结果包含结尾的换行符,我们应当先把它去掉,再转换成数字。如果转换失败,则同样打印错误,退出。

代码如下:

reader := bufio.NewReader(os.Stdin)
input, err := reader.ReadString('\n')
if err != nil {
	fmt.Println("An error occured while reading input. Please try again", err)
	return
}
input = strings.Trim(input, "\r\n") //去掉换行符 CR-LF

num, err := strconv.Atoi(input) //尝试将输入转换为数字
if err != nil {
	fmt.Println("Invalid input. Please enter an integer value")
	return
}

这样我们就可以完成输入,当然这里需要增加头文件bufio

完整的猜数字代码如下:(这里是用Scanln的)用reader,ReadString的只需要修改一下即可

package main  
  
import (  
"fmt"  
  
"math/rand"  
  
"time"  
)  
  
func main() {  
maxNum := 105  
rand.Seed(time.Now().UnixNano()) //用系统时间初始化随机数种子  
var res = rand.Intn(maxNum)  
fmt.Println(res)  
for {  
  
fmt.Println("please input your guess")  
var num int  
fmt.Scanln(&num) //需要两个%d后面那个读入回车符号,也可以改成%s  
fmt.Println("your input is:", num)  
if num == res {  
fmt.Println("you are right")  
break  
} else if num > res {  
fmt.Println("please guess a small number")  
} else {  
fmt.Println("please guess a big number")  
}  
}  
  
}