Golang课后项目分析实现 | 青训营笔记

213 阅读5分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记

直接上课堂中讲的项目

猜谜游戏

rand.Intn()为什么每次执行都是同一个数字

  • 随机数种子
  • 使用rand.Seed(time.Now().UnixNano())函数可以设置种子,这样每次返回的随机数就不相同

直接上课堂中将的项目

  • 猜数字
        • 主要知识点
      • rand
        • rand.Intn(n int) int为什么每次执行都是同一个数字?
        • 从函数描述中看,Intn返回一个[0,n)的int数字,如果n小于等于0就会发生panic
        • 使用相同的值播种会导致每次运行的随机序列相同,不设置rand.Seed()则默认设置的为rand.Seed(1),可以从函数描述中看到
var globalRand = New(&lockedSource{src: NewSource(1).(*rngSource)})

// Seed 使用提供的种子值将默认 Source 初始化为
// 确定性状态。 如果没有调用 Seed,则生成器的行为如下
// 如果由 Seed(1) 播种。 具有相同余数的种子值
// 除以 2³¹-1 生成相同的伪随机序列。
// 与 Rand.Seed 方法不同,Seed 对并发使用是安全的。
func Seed(seed int64) { globalRand.Seed(seed) }
        • 对于不同的数字,使用不同的值作为种子
        • 例如 time.Now().UnixNano(),它会产生一个不断变化的数字。
        • 所以在每次执行程序生成随机数之前调用rand.Seed(time.Now().UnixNano())使得每次执行程序获得的函数都不一样
      • 命令行输入
        • 方法
          • bufio
            • 创建一个reader对象,reader := bufio.NewReader(os.Stdin)
            • 这里os.Stdin是系统标准输入,就是从命令行中读取
            • 然后读取一行的数据input, err := reader.ReadString('\n'),这里的一行是用'\n'来控制的,也就是这个原因,不同系统中的换行符不一样,在linux系统中使用\n,在windows中应使用\r\n``reader.ReadString(b byte)是读到第一个b字节出现为止,且会将那一个字节读入字符串,返回读到的字符串
            • 除了使用ReadString(b byte)方法读取一行以外,还可使用func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error)来实现,返回一个字节数组,不会包括换行符
  • ReadLine 是一个低级的行读取原语。 大多数调用者应该改用 ReadBytes('\n') 或 ReadString('\n') 或使用 Scanner。
  • ReadLine 尝试返回单行,不包括行尾字节。 如果该行对于缓冲区来说太长,则设置 isPrefix 并返回该行的开头。 该行的其余部分将从以后的调用中返回。 返回该行的最后一个片段时,isPrefix 将为 false。 返回的缓冲区仅在下一次调用 ReadLine 之前有效。 ReadLine 要么返回非零行,要么返回错误,从不返回两者。
  • 从 ReadLine 返回的文本不包括行尾(“\r\n”或“\n”)。 如果输入结束时没有最后一行结束,则不会给出指示或错误。 在 ReadLine 之后调用 UnreadByte 将始终未读取读取的最后一个字节(可能是属于行尾的字符),即使该字节不是 ReadLine 返回的行的一部分。
          • fmt
            • fmt.Scan()直接传入指针,从标准输入中读取数据
            • fmt.Scanf()传入format格式和相应需要赋值的变量的指针,根据格式去读取数据
// Scan 扫描从标准输入读取的文本,存储连续的
// 空格分隔的值到连续的参数中。 换行数
// 作为空间。 它返回成功扫描的项目数。
// 如果小于参数个数,err 会报告原因。
func Scan(a ...interface{}) (n int, err error)

// Scanf 扫描从标准输入读取的文本,存储连续的
// 以空格分隔的值到由下式确定的连续参数中
// 格式。 它返回成功扫描的项目数。
// 如果小于参数个数,err 会报告原因。
// 输入中的换行符必须与格式中的换行符匹配。
// 一个例外:动词 %c 总是扫描
// 输入,即使是空格(或制表符等)或换行符。
func Scanf(format string, a ...interface{}) (n int, err error) {
	return Fscanf(os.Stdin, format, a...)
}
  • 字典查询
    • 两个工具
    • 发送请求部分比较简单,主要关注两个翻译引擎并发查询
    • golang中启动协程直接使用go关键字即可
    • 为了父协程等待子协程执行完后才能结束,使用sync.WaitGroup来控制
    • sync.WaitGroup适合于知道几个子协程的情况下使用
    • 使用Wait()方法来等待子协程执行完成
    • 因为子协程中两个都需要打印到标准输出,在并行的情况下会出现乱序的情况,所以我们引入channel来管理
func main() {
	if len(os.Args) != 2 {
		fmt.Fprintf(os.Stderr, `usage: simpleDict WORD
example: simpleDict hello
		`)
		os.Exit(1)
	}
	word := os.Args[1]
	var wg sync.WaitGroup
	ch := make(chan struct{})
	wg.Add(2)
	go query(word, &wg, ch)
	go queryByYouDao(word, &wg, ch)
	wg.Wait()
}
    • 使用Add(n int)设置需要等待的子协程个数,Wait()进行阻塞等待
    • 这里因为只需要控制打印输出的顺序,channel只需要传输一个信号即可,不需要传输具体的数据,所以这里定义的是一个传输空结构体的channel来提升效率
    • 在子协程中则是以下逻辑
func query(word string, wg *sync.WaitGroup, c chan struct{}) {
	defer wg.Done()
	// HTTP请求并打印翻译结果
    
    // 传输一个信号表示打印完成,避免结果乱序
	c <- struct{}{}
}

func queryByYouDao(word string, wg *sync.WaitGroup, c chan struct{}) {
	defer wg.Done()
	// http请求数据

    // 阻塞等待,这里channel传输的数据无意义,只作为一个信号量使用
	<-c
	// 打印翻译结果
}