Files
the-way-to-go_ZH_CN/eBook/05.4.md
2022-06-25 18:18:09 +08:00

370 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 5.4 for 结构
如果想要重复执行某些语句Go 语言中您只有 `for` 结构可以使用。不要小看它,这个 `for` 结构比其它语言中的更为灵活。
**注意事项** 其它许多语言中也没有发现和 do-while 完全对等的 `for` 结构,可能是因为这种需求并不是那么强烈。
## 5.4.1 基于计数器的迭代
文件 for1.go 中演示了最简单的基于计数器的迭代,基本形式为:
for 初始化语句; 条件语句; 修饰语句 {}
示例 5.6 [for1.go](examples/chapter_5/for1.go)
```go
package main
import "fmt"
func main() {
for i := 0; i < 5; i++ {
fmt.Printf("This is the %d iteration\n", i)
}
}
```
输出:
This is the 0 iteration
This is the 1 iteration
This is the 2 iteration
This is the 3 iteration
This is the 4 iteration
由花括号括起来的代码块会被重复执行已知次数,该次数是根据计数器(此例为 `i`)决定的。循环开始前,会执行且仅会执行一次初始化语句 `i := 0;`;这比在循环之前声明更为简短。紧接着的是条件语句 `i < 5;`,在每次循环开始前都会进行判断,一旦判断结果为 `false`,则退出循环体。最后一部分为修饰语句 `i++`,一般用于增加或减少计数器。
这三部分组成的循环的头部,它们之间使用分号 `;` 相隔,但并不需要括号 `()` 将它们括起来。例如:`for (i = 0; i < 10; i++) { }`,这是无效的代码!
同样的,左花括号 `{` 必须和 for 语句在同一行,计数器的生命周期在遇到右花括号 `}` 时便终止。一般习惯使用 i、j、z 或 ix 等较短的名称命名计数器。
特别注意,永远不要在循环体内修改计数器,这在任何语言中都是非常差的实践!
您还可以在循环中同时使用多个计数器:
```go
for i, j := 0, N; i < j; i, j = i+1, j-1 {}
```
这得益于 Go 语言具有的平行赋值的特性(可以查看[第 7 章](07.0.md) [string_reverse.go](./examples/chapter_7/string_reverse.go) 中反转数组的示例)。
您可以将两个 for 循环嵌套起来:
```go
for i:=0; i<5; i++ {
for j:=0; j<10; j++ {
println(j)
}
}
```
如果您使用 for 循环迭代一个 Unicode 编码的字符串,会发生什么?
示例 5.7 [for_string.go](examples/chapter_5/for_string.go)
```go
package main
import "fmt"
func main() {
str := "Go is a beautiful language!"
fmt.Printf("The length of str is: %d\n", len(str))
for ix :=0; ix < len(str); ix++ {
fmt.Printf("Character on position %d is: %c \n", ix, str[ix])
}
str2 := "日本語"
fmt.Printf("The length of str2 is: %d\n", len(str2))
for ix :=0; ix < len(str2); ix++ {
fmt.Printf("Character on position %d is: %c \n", ix, str2[ix])
}
}
```
输出:
The length of str is: 27
Character on position 0 is: G
Character on position 1 is: o
Character on position 2 is:
Character on position 3 is: i
Character on position 4 is: s
Character on position 5 is:
Character on position 6 is: a
Character on position 7 is:
Character on position 8 is: b
Character on position 9 is: e
Character on position 10 is: a
Character on position 11 is: u
Character on position 12 is: t
Character on position 13 is: i
Character on position 14 is: f
Character on position 15 is: u
Character on position 16 is: l
Character on position 17 is:
Character on position 18 is: l
Character on position 19 is: a
Character on position 20 is: n
Character on position 21 is: g
Character on position 22 is: u
Character on position 23 is: a
Character on position 24 is: g
Character on position 25 is: e
Character on position 26 is: !
The length of str2 is: 9
Character on position 0 is: æ
Character on position 1 is: —
Character on position 2 is: ¥
Character on position 3 is: æ
Character on position 4 is: œ
Character on position 5 is: ¬
Character on position 6 is: è
Character on position 7 is: ª
Character on position 8 is: ž
如果我们打印 `str``str2` 的长度,会分别得到 `27``9`
由此我们可以发现ASCII 编码的字符占用 1 个字节,既每个索引都指向不同的字符,而非 ASCII 编码的字符(占有 2 到 4 个字节)不能单纯地使用索引来判断是否为同一个字符。我们会在[第 5.4.4 节](05.4.md) 解决这个问题。
### 练习题
**练习 5.4** [for_loop.go](exercises/chapter_5/for_loop.go)
1. 使用 `for` 结构创建一个简单的循环。要求循环 15 次然后使用 `fmt` 包来打印计数器的值。
2. 使用 `goto` 语句重写循环,要求不能使用 `for` 关键字。
**练习 5.5** [for_character.go](exercises/chapter_5/for_character.go)
创建一个程序,要求能够打印类似下面的结果(尾行达 25 个字符为止):
G
GG
GGG
GGGG
GGGGG
GGGGGG
1. 使用 2 层嵌套 for 循环。
2. 仅用 1 层 for 循环以及字符串连接。
**练习 5.6** [bitwise_complement.go](exercises/chapter_5/bitwise_complement.go)
使用按位补码从 0 到 10使用位表达式 `%b` 来格式化输出。
**练习 5.7** Fizz-Buzz 问题:[fizzbuzz.go](exercises/chapter_5/fizzbuzz.go)
写一个从 1 打印到 100 的程序,但是每当遇到 3 的倍数时,不打印相应的数字,但打印一次 "Fizz"。遇到 5 的倍数时,打印 `Buzz` 而不是相应的数字。对于同时为 3 和 5 的倍数的数,打印 `FizzBuzz`(提示:使用 switch 语句)。
**练习 5.8** [rectangle_stars.go](exercises/chapter_5/rectangle_stars.go)
使用 `*` 符号打印宽为 20高为 10 的矩形。
## 5.4.2 基于条件判断的迭代
for 结构的第二种形式是没有头部的条件判断迭代(类似其它语言中的 while 循环),基本形式为:`for 条件语句 {}`
您也可以认为这是没有初始化语句和修饰语句的 for 结构,因此 `;;` 便是多余的了。
Listing 5.8 [for2.go](examples/chapter_5/for2.go)
```go
package main
import "fmt"
func main() {
var i int = 5
for i >= 0 {
i = i - 1
fmt.Printf("The variable i is now: %d\n", i)
}
}
```
输出:
The variable i is now: 4
The variable i is now: 3
The variable i is now: 2
The variable i is now: 1
The variable i is now: 0
The variable i is now: -1
## 5.4.3 无限循环
条件语句是可以被省略的,如 `i:=0; ; i++``for { }``for ;; { }``;;` 会在使用 gofmt 时被移除):这些循环的本质就是无限循环。最后一个形式也可以被改写为 `for true { }`,但一般情况下都会直接写 `for { }`
如果 for 循环的头部没有条件语句,那么就会认为条件永远为 true因此循环体内必须有相关的条件判断以确保会在某个时刻退出循环。
想要直接退出循环体,可以使用 break 语句(第 5.5 节)或 return 语句直接返回(第 6.1 节)。
但这两者之间有所区别break 只是退出当前的循环体,而 return 语句提前对函数进行返回,不会执行后续的代码。
无限循环的经典应用是服务器,用于不断等待和接受新的请求。
```go
for t, err = p.Token(); err == nil; t, err = p.Token() {
...
}
```
## 5.4.4 for-range 结构
这是 Go 特有的一种的迭代结构,您会发现它在许多情况下都非常有用。它可以迭代任何一个集合(包括数组和 `map`,详见第 [7](07.0.md) 和 [8](08.0.md) 章)。语法上很类似其它语言中的 foreach 语句,但您依旧可以获得每次迭代所对应的索引。一般形式为:`for ix, val := range coll { }`
要注意的是,`val` 始终为集合中对应索引的值拷贝,因此它一般只具有只读性质,对它所做的任何修改都不会影响到集合中原有的值(**译者注:如果 `val` 为指针,则会产生指针的拷贝,依旧可以修改集合中的原值**)。一个字符串是 Unicode 编码的字符(或称之为 `rune`)集合,因此您也可以用它迭代字符串:
```go
for pos, char := range str {
...
}
```
每个 `rune` 字符和索引在 for-range 循环中是一一对应的。它能够自动根据 UTF-8 规则识别 Unicode 编码的字符。
示例 5.9 [range_string.go](examples/chapter_5/range_string.go)
```go
package main
import "fmt"
func main() {
str := "Go is a beautiful language!"
fmt.Printf("The length of str is: %d\n", len(str))
for pos, char := range str {
fmt.Printf("Character on position %d is: %c \n", pos, char)
}
fmt.Println()
str2 := "Chinese: 日本語"
fmt.Printf("The length of str2 is: %d\n", len(str2))
for pos, char := range str2 {
fmt.Printf("character %c starts at byte position %d\n", char, pos)
}
fmt.Println()
fmt.Println("index int(rune) rune char bytes")
for index, rune := range str2 {
fmt.Printf("%-2d %d %U '%c' % X\n", index, rune, rune, rune, []byte(string(rune)))
}
}
```
输出:
```
The length of str is: 27
Character on position 0 is: G
Character on position 1 is: o
Character on position 2 is:
Character on position 3 is: i
Character on position 4 is: s
Character on position 5 is:
Character on position 6 is: a
Character on position 7 is:
Character on position 8 is: b
Character on position 9 is: e
Character on position 10 is: a
Character on position 11 is: u
Character on position 12 is: t
Character on position 13 is: i
Character on position 14 is: f
Character on position 15 is: u
Character on position 16 is: l
Character on position 17 is:
Character on position 18 is: l
Character on position 19 is: a
Character on position 20 is: n
Character on position 21 is: g
Character on position 22 is: u
Character on position 23 is: a
Character on position 24 is: g
Character on position 25 is: e
Character on position 26 is: !
The length of str2 is: 18
character C starts at byte position 0
character h starts at byte position 1
character i starts at byte position 2
character n starts at byte position 3
character e starts at byte position 4
character s starts at byte position 5
character e starts at byte position 6
character : starts at byte position 7
character starts at byte position 8
character 日 starts at byte position 9
character 本 starts at byte position 12
character 語 starts at byte position 15
index int(rune) rune char bytes
0 67 U+0043 'C' 43
1 104 U+0068 'h' 68
2 105 U+0069 'i' 69
3 110 U+006E 'n' 6E
4 101 U+0065 'e' 65
5 115 U+0073 's' 73
6 101 U+0065 'e' 65
7 58 U+003A ':' 3A
8 32 U+0020 ' ' 20
9 26085 U+65E5 '日' E6 97 A5
12 26412 U+672C '本' E6 9C AC
15 35486 U+8A9E '語' E8 AA 9E
```
请将输出结果和 Listing 5.7[for_string.go](examples/chapter_5/for_string.go))进行对比。
我们可以看到,常用英文字符使用 1 个字节表示,而汉字(**译者注严格来说“Chinese: 日本語”的 Chinese 应该是 Japanese**)使用 3 个字符表示。
**练习 5.9** 以下程序的输出结果是什么?
```go
for i := 0; i < 5; i++ {
var v int
fmt.Printf("%d ", v)
v = 5
}
```
**问题 5.2** 请描述以下 for 循环的输出结果:
1.
```go
for i := 0; ; i++ {
fmt.Println("Value of i is now:", i)
}
```
2.
```go
for i := 0; i < 3; {
fmt.Println("Value of i:", i)
}
```
3.
```go
s := ""
for ; s != "aaaaa"; {
fmt.Println("Value of s:", s)
s = s + "a"
}
```
4.
```go
for i, j, s := 0, 5, "a"; i < 3 && j < 100 && s != "aaaaa"; i, j,
s = i+1, j+1, s + "a" {
fmt.Println("Value of i, j, s:", i, j, s)
}
```
## 链接
- [目录](directory.md)
- 上一节:[switch 结构](05.3.md)
- 下一节:[Break 与 continue](05.5.md)