第七章 做猜数字游戏(for循环)

上次我们学习了条件分支,踏进编程门槛了。这次我们将学习如何重复处理同一件事。

7.1 重复

使用 for 可以编写重复(循环)处理。Go 的 for 语句有以下三种形式。

其他语言中的 whileforeach 的功能,也集中在 for 中。

最重要的,是记住的第一和第二条。第三点,目前只需在脑海的角落里留个印象。

  • for 条件 { ... } - 满足条件的情况下循环处理。相当于其他语言的 while
  • for range 値 { ... } - 按顺序提取数组或切片等并循环。相当于其他语言中的 foreach
  • for 初始化; 循环条件; 循环后处理 { ... } - 按“初期化、条件、循环后处理”的流程进行处理。

7.1.1 只有一个条件语句的 for 语句

首先介绍的,是在满足条件时重复的 for 语句。相当于其他语言中的 while

package main

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

func main() {
    for rand.N(2) == 1 {
        fmt.Println("循环")
    }
    fmt.Println("结束")
}
$ go run .
循环
循环
循环
结束

如果 rand.N(2) 的返回值为 1,则执行 {} 的内容。

执行后再次执行 rand.N(2) ,检查返回值是否为 1,如果是,则执行 {} 的内容。

这样循环重复。如果不再满足条件,则退出 {} 并显示 结束 。运气好的话,此语句会循环很多次,但也可能直接结束。

这样在满足条件的情况下,重复执行 {} 里面的内容的语句就是 for 语句。

条件可以省略不写。在这种情况下,将始终被视为满足条件,形成所谓的“无限循环”。以下代码会输出大量文本,请准备好随时按“Ctrl+C”强制结束程序。

package main

import "fmt"

func main() {
    for {
        fmt.Println("无限循环")
    }
}

7.1.2 用 range 指定循环范围

接下来介绍的是使用 range (范围)的 for 循环。

range 中还有不同的用法,这些将在后面介绍。本章节中首先介绍的是**“指定次数重复”的 range 用法**。

请看代码:

package main

import "fmt"

func main() {
    for range 5 { // 执行5次
        fmt.Println("循环")
    }
    fmt.Println("结束")
}	
package main

import "fmt"

func main() {
	var i = 0
	for i = range 5 { // range 前面用等号的例子
		fmt.Println(i)
	}
  fmt.Println("循环结束后会执行这里")
}
$ go run .
循环
循环
循环
循环
循环
结束

通过在 range 后面写上重复次数,就可以重复执行 {} 中的处理。 此外,您还可以将“重复几次”放入变量中使用。这可以通过在 range 前面写 :== 语句来实现。

package main

import "fmt"

func main() {
    for i := range 5 { // 用变量 i 当次数
        fmt.Println(i)
    }
    fmt.Println("循环结束后会执行这里")
}
$ go run .
0
1
2
3
4
循环结束后会执行这里

请注意,此处 i 的起始值是 0 。

变量名可以随意起,但表示循环次数的值,通常会使用字母 i 。这个习惯,据说源自编程语言“APL”中使用的希腊字母 ι(伊奥塔)。

7.1.3 初期化・循环条件・循环后处理的用法

最后介绍的,是最经典的 for 循环用法。很多人可能在 C 语言中熟悉了这种写法。我们来写一段代码,像在 range 中那样重复执行 5 次。

package main

import "fmt"

func main() {
    for i := 0; i < 5; i++ {
        fmt.Println(i)
    }
}
$ go run .
0
1
2
3
4

初期化语句; 循环条件; 循环后处理 的形式,用英文分号 ; 分隔三个表达式。

这种情况下,这个 for 语句的意思是:

  • 将变量 i 初始化为 0,
  • i 小于 5 的时候重复循环
  • 每次循环将 i 加 1( ++ 是表示将 i 加 1 的符号)

如您所见,同样的功能,使用 range 可以更写得更短,感觉不会有人故意写得这么啰嗦。

但是,这个用 range 指定次数循环的功能,是最近才加入的。在此之前我们一直使用这种经典写法。为了阅读老代码,有必要了解这个语法。

7.2 循环控制

for 循环,可以在中途停止或跳过。

读到这里,感觉编程用的关键词变多了呢。

虽然可能会让你感到有些吃力,但这些关键词,并不需要一记住。

只需要在实践中多写几次,逐渐掌握即可!

7.2.1 break 中断

使用关键词 break ,可以在中途停止循环。

package main

import "fmt"

func main() {
    for i := range 5 {
        if i == 2 {
            break
        }
        fmt.Println(i)
    }
    fmt.Println("循环后执行次语句")
}
$ go run .
0
1
循环后执行次语句

可以看到,range 指定执行5 次重复的循环,满足 if i == 2 这个条件时执行了 break ,在途中被中断。

7.2.2 continue 继续

使用 continue 语句,可以跳过本次循环的其余部分,继续下一次循环。

package main

import "fmt"

func main() {
    for i := range 5 {
        if i == 2 {
            fmt.Println("跳过此次循环")
            continue
        }
        fmt.Println(i) // 如果上面的continue执行了,这次将不会执行这一句
    }
}
$ go run .
0
1
跳过此次循环
3
4

循环本身虽然被没有中断,但 continue 时,本该执行的 fmt.Println(i) 却在该次循环中被跳过。

您能理解发生了什么吗?

[面向有经验的人] 多重循环和带标签的 break、continue

break 和 continue ,通常只能中断或跳过最内层的循环。在多重嵌套的情况下,如果想要中断或继续外层循环,就需要与标签结合使用循环语句。

package main

import "fmt"

func main() {
loop: // "loop"标签 
    for i := range 3 {
        for j := range 3 {
            if i == 2 {
                break loop // 指定标签,跳出特定循环的 break 语句
            }
        }
    }
}

行头中写的 loop: ,可以为接下来的句子(在这种情况下是 for i := ... )添加标签。虽然 loop: 的额外缩进,可能会让你感到不适,但请适应。

学习 break 和 continue 等关键字时,作者本人也曾经觉得非常吃力。但在不断查阅资料与使用的过程中,逐渐就习惯了,所以不要着急,逐步推进吧。

7.3 输入

赶快用所学的知识,做一个猜数字游戏吧……

虽然我想这么说,但如果程序无法接收玩家输入,就没法做游戏。接受来自终端的输入的函数,只在本章用一次,所以我的介绍会比较简略。

在终端接收输入,可以使用 fmt.Scanln 。将变量的指针作为参数传给 fmt.Scanlnfmt.Scanln 就会将用户输入的值,赋值给该变量。

至于指针是什么,稍后会解释。

package main

import "fmt"

func main() {
    fmt.Println("请输入数字")
    input := 0
    fmt.Scanln(&input) //加个 & ,将 input的指针传给fmt.Scanln
    fmt.Println("你输入的值是", input)
}
$ go run .
请输入数字
(在这里输入数值,然后按回车键结束)
你输入的值是 42

7.4 制作猜数字游戏

前提知识准备好了。接下来我们制作一个猜数字游戏吧。

用程序生成一个随机的秘密值,玩家需要猜出这个值。如果猜错了,就继续猜,直到猜对为止。

同时作为提示,程序会显示“答案你比猜的数更大”或“答案你比猜的数更小”。

package main

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

func main() {
	answer := rand.N(100) // 生成 0 到 99 的数字

	for {
		fmt.Println("请输入数字")
		input := 0
		fmt.Scanln(&input)

		if input == answer {
			fmt.Println("答对了!")
			break
		} else if input < answer {
			fmt.Println("答案你比猜的数更大")
		} else {
			fmt.Println("答案你比猜的数更小")
		}
	}
	fmt.Println("游戏结束")
}
$ go run .
请输入数字
50
答案你比猜的数更大
请输入数字
75
答案你比猜的数更大
请输入数字
87
答案你比猜的数更小
请输入数字
81
答案你比猜的数更大
请输入数字
84
答案你比猜的数更大
请输入数字
86
答案你比猜的数更小
请输入数字
85
答对了!
游戏结束

综合运用学到的知识,我们已经能够制作猜数字游戏了。

能够使用 if 和 for 做东西,已经可以称得上是合格的程序员了。

如果心有余力,可以设定猜测次数限制,或统计玩家猜了几次,尝试各种玩法。

此外,这次做的是一个非常简单的数字猜测游戏,但不要小看这个猜数字游戏。

结合接下来将要说明的“数组・切片”结功能,这个游戏可以变成真正的推理游戏,就像“Hit and Blow”那样。

真正的游戏所需的精髓,你已经掌握不少了。

本章总结

  • 使用 for 可以编写循环处理。
  • for 条件 { ... } 在条件满足的情况下重复循环。
  • for range 回数 { ... } 固定次数的循环。
  • break , continue 可以控制循环流程。