Go 语言循环语句
只有 for 一种循环,以及它的四种形态和 range。
Key PointsGo 没有 while 和 do-while,全部用 `for` 的四种写法表达。
四种形态
Go
// 1. 标准三段式
for i := 0; i < 3; i++ {
fmt.Print(i, " ")
}
fmt.Println()
// 2. 只有条件(相当于 while)
n := 3
for n > 0 {
fmt.Print(n, " ")
n--
}
fmt.Println()
// 3. 无条件(相当于 while(true))
count := 0
for {
count++
if count == 3 {
break
}
}
fmt.Println("count =", count)
// 4. range
for i, v := range []string{"a", "b"} {
fmt.Print(i, v, " ")
}
fmt.Println()Output
0 1 2
3 2 1
count = 3
0a 1b break 与 continue
Go
// continue:跳过本次
for i := 1; i <= 5; i++ {
if i%2 == 0 {
continue
}
fmt.Print(i, " ")
}
fmt.Println()
// 带标签的 break:跳出多层
outer:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if i*j == 2 {
fmt.Println("找到", i, j)
break outer
}
}
}Output
1 3 5
找到 1 2Labels are Go's proper way out of nested loops. Without one, break exits only the innermost loop — a common source of "I thought it broke out" bugs.
range 的细节
Go
// 数组 / 切片:索引 + 值
for i, v := range []int{10, 20} {
fmt.Println(i, v)
}
// 只要值
for _, v := range []int{10, 20} {
fmt.Println(v)
}
// 字符串:索引 + rune
for i, r := range "Go语" {
fmt.Printf("%d:%c ", i, r)
}
fmt.Println()
// map:键值顺序随机
for k, v := range map[string]int{"a": 1} {
fmt.Println(k, v)
}Output
0 10
1 20
10
20
0:G 1:o 2:语
a 1range 字符串时索引是字节偏移,不是字符序号。所以「语」的索引是 2 而不是 2 之后的 3——因为 G、o 各占 1 字节。1.22 的循环变量变化
Go
// Go 1.21 及之前:所有闭包共享同一个 i,输出 3 3 3
// Go 1.22 起:每轮独立变量,输出 0 1 2
for i := 0; i < 3; i++ {
go func() { fmt.Print(i, " ") }()
}
time.Sleep(10 * time.Millisecond)Output
0 1 2 This is a significant Go 1.22 semantic change. The old i := i workaround can be deleted; on 1.21 and earlier the same code prints 3 3 3.
goto
Go
i := 0
loop:
if i < 3 {
fmt.Print(i, " ")
i++
goto loop
}
fmt.Println()Output
0 1 2 goto exists but is rarely used — mainly to escape deep nesting or in generated code. One hard rule: it cannot jump over a variable declaration.
四种形态
Go
// 1. 标准三段式
for i := 0; i < 3; i++ {
fmt.Print(i, " ")
}
fmt.Println()
// 2. 只有条件(相当于 while)
n := 3
for n > 0 {
fmt.Print(n, " ")
n--
}
fmt.Println()
// 3. 无条件(相当于 while(true))
count := 0
for {
count++
if count == 3 {
break
}
}
fmt.Println("count =", count)
// 4. range
for i, v := range []string{"a", "b"} {
fmt.Print(i, v, " ")
}
fmt.Println()Output
0 1 2
3 2 1
count = 3
0a 1b break 与 continue
Go
// continue:跳过本次
for i := 1; i <= 5; i++ {
if i%2 == 0 {
continue
}
fmt.Print(i, " ")
}
fmt.Println()
// 带标签的 break:跳出多层
outer:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if i*j == 2 {
fmt.Println("找到", i, j)
break outer
}
}
}Output
1 3 5
找到 1 2标签是 Go 跳出嵌套循环的正式方式。不用标签时 break 只跳出最内层,很容易写出「以为跳出了其实没有」的 bug。
range 的细节
Go
// 数组 / 切片:索引 + 值
for i, v := range []int{10, 20} {
fmt.Println(i, v)
}
// 只要值
for _, v := range []int{10, 20} {
fmt.Println(v)
}
// 字符串:索引 + rune
for i, r := range "Go语" {
fmt.Printf("%d:%c ", i, r)
}
fmt.Println()
// map:键值顺序随机
for k, v := range map[string]int{"a": 1} {
fmt.Println(k, v)
}Output
0 10
1 20
10
20
0:G 1:o 2:语
a 1When ranging a string the index is a byte offset, not a character position. That is why 语 sits at index 2 while the next would start at 5.
1.22 的循环变量变化
Go
// Go 1.21 及之前:所有闭包共享同一个 i,输出 3 3 3
// Go 1.22 起:每轮独立变量,输出 0 1 2
for i := 0; i < 3; i++ {
go func() { fmt.Print(i, " ") }()
}
time.Sleep(10 * time.Millisecond)Output
0 1 2 这是 Go 1.22 的重要语义变更。老代码里那些 i := i 的补丁现在可以删掉了;但**在 1.21 及更早的版本上跑这段代码,输出是 3 3 3**。
goto
Go
i := 0
loop:
if i < 3 {
fmt.Print(i, " ")
i++
goto loop
}
fmt.Println()Output
0 1 2 goto 存在但极少用,主要用于从深层嵌套里跳出,或自动生成的代码。有一条硬限制:不能跳过变量声明。