Golang实战案例(一) | 青训营笔记

109 阅读5分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第4天

猜谜游戏

(1). 随机数生成

前面我们已经学习了Golang的基础语法,现在通过归纳之前的知识,已经可以开始完成一些具有一定功能的小程序了,这节课首先完成的是猜谜游戏实战案例。
首先我们可以实现一个简单的基础随机程序,文件命名为 main.go

package main

import (
	"fmt"
	"math/rand"
)

上面导入了fmt输入输出包,以及math数学函数包的rand,用于生成伪随机数。
下面设置了最大数为100,类似于Java的 random.nextInt(100); ,它的作用是用于函数返回 0 ~ n 之间的随机数,n是 rand.Intn() 中的参数。这里用它生成一个整形数据secretNumber,然后用fmt包输出内容。

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

我们可以通过多次运行程序观察结果发现,这个程序通过 rand.Intn(maxNum) 生成的整型数字每一次都相同,这是因为rand的种子默认值是1,且这个值没有被改变,其结果是按照这个Seed值来改变的,不能保证正确的随机,因此添加一个变化的种子才能保证随机。这一点和C++的伪随机库函数类似。
默认值为1的种子资源,会在程序每次运行时都产生确定的序列。而如果需要每次运行产生不同的序列,应使用Seed函数进行初始化,这里引入包 "time",在 rand.Seed() 函数的参数中填入 time.Now().UnixNano() 值,这个值是纳秒级别的时间戳,利用每时刻时间的唯一性来生成不同的种子。

package main

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

func main() {
	maxNum := 100
	rand.Seed(time.Now().UnixNano())
	secretNumber := rand.Intn(maxNum)
	fmt.Println("The secret number is ", secretNumber)
}
(2). 输入与输出

这是一个功能为猜数字的小程序,因此需要实现“猜”的环节,即为输入,并用输出来反应猜后的结果。 导入一些需要的包:

import (
	"bufio"
	"fmt"
	"math/rand"
	"os"
	"strconv"
	"strings"
	"time"
)

在上一个随机数程序的基础上,加入输入的内容:

fmt.Println("请输入你要猜测的数字:")
reader := bufio.NewReader(os.Stdin)
input, err := reader.ReadString('\n')
if err != nil {
    fmt.Println("读入输入数据时发生了错误,请重新运行程序", err)
    return
}
input = strings.Trim(input, "\r\n")

这里还可以使用fmt.Scan()函数读入数据,但如果数据规模很大的情况下,使用该函数会造成性能问题,时间变长;使用 bufio.NewReader(os.Stdin) 可以实现快读,优化输入输出数据的速度。
bufio包的NewReader在实现时,首先进行打开文件的操作,然后使用打开的文件返回的文件句柄作为函数参数 传入NewReader,这里文件句柄采用的是操作系统的标准输入 Stdin 。

接下来使用 NewReader 返回的 reader 对象调用 ReadString() 来读取输入的字符串。
文件读取结束的标志是返回值为0,如果需要读取整个文件内容,那么应使用for循环轮询读取文件,直到n等于0为止,这里就不需要了,如果读取发生错误,则产生一个非空的err。如果发生错误,则结束提前程序运行,并输出错误提示。

在完成输入后,对输入的内容还需要进行一个规范化的操作,调用strings包中的 Trim() 函数。这个函数有两个参数,第一个参数是待处理字符串,第二个参数是要去除的字符串,它的功能是对待处理的字符串,删除其首尾连续的包含在第二个参数中的字符,若删除了字符,则返回一个新的值。这一点和Java的语法不同,Java该函数用于去除首尾算起连续的小于十进制32的所有字符(若有修改则返回新对象而不是原对象),这些是包括空格在内的ASCII字符,夹在中间的空格无法去除。

接下来进行输出操作:

guess, err := strconv.Atoi(input)
if err != nil {
    fmt.Println("非法的输入,请输入整形数据")
    return
}
fmt.Println("你猜的数字是:", guess)

由于上面的输入采用快读字符串的方式,但我们要的是一个数字,因此需要判断读入的字符串是否是一个规范的整形数据,不是则输出错误并结束。调用strconv包的 Atoi() 函数,用于将字符串类型转换为int类型。除了这个函数,还可以使用该包下的ParseInt函数,将字符串转为数字,这个函数有三个参数:

func ParseInt(s string, base int, bitSize int) (i int64, err error)
/*
    s:源字符串,数字的字符串形式  
    base:s的进制,比如二进制、八进制、十进制、十六进制,填入数字0/2/8/10/16,如果这个值为0,基数由符号后面的字符串前缀是否存在来解释,2 - "0b",8 - "0"/"0o",16 - "0x",否则默认为 10。
    bitSize:返回结果的bit大小,可以有0、8、16、32、64,说明是int、int8、int16、int32、int64
*/
(3). 猜数字过程

上面已经完成了随机数的生成、即为待猜测的数字;完成了输入和输出,即为猜测的数字;接下来需要告诉程序的使用者,是否猜中数字,进行条件语句判断比较:

if guess > secretNumber {
    fmt.Println("你猜测的数字有点大,请再试一次")
} else if guess < secretNumber {
    fmt.Println("你猜测的数字有点小,请再试一次)
} else {
    fmt.Println("恭喜你,猜对了!")
}

完善程序,设置当使用者猜中数字结束游戏,若猜不中将会一直执行,直到猜中为止,添加循环和break语句、continue语句,修改语句结构,完整程序如下:

package main

import (
	"bufio"
	"fmt"
	"math/rand"
	"os"
	"strconv"
	"strings"
	"time"
)

func main(){
    maxNum := 100
    rand.Seed(time.Now().UnixNano())
    secretNumber := rand.Intn(maxNum)
    
    reader := bufio.NewReader(os.Stdin)
    for {
        fmt.Print("请输入你要猜测的数字: ")
        input, err := reader.ReadString('\n')
        if err != nil {
            fmt.Println("读入输入数据时发生了错误,请重新运行程序", err)
            continue
        }
        input = strings.Trim(input, "\r\n")

        guess, err := strconv.Atoi(input)
        if err != nil {
            fmt.Println("非法的输入,请输入整形数据")
            continue
        }
        fmt.Println("你猜的数字是:", guess)

        if guess > secretNumber {
            fmt.Println("你猜测的数字有点大,请再试一次")
        } else if guess < secretNumber {
            fmt.Println("你猜测的数字有点小,请再试一次)
        } else {
            fmt.Println("恭喜你,猜对了!")
            break
        }
    }
}