Go语言中只有一种循环结构——for循环,但它足够灵活,可以替代其他语言中的while、do-while循环。另外,range关键字可以简便地遍历数组、切片、Map、字符串和通道等数据结构。本章将详细讲解for循环的各种形式以及控制循环的break、continue和标签语句。
语法结构:for 初始化语句; 条件表达式; 后置语句 { ... },其中三个部分都可以省略。
package main
import "fmt"
func main() {
for i := 0; i < 5; i++ {
fmt.Println(i)
}
}
执行流程:先执行i := 0;然后判断i < 5为true进入循环体;执行fmt.Println(i);最后执行i++,重复判断条件。
for块内部。省略初始化语句和后置语句,仅保留条件,即实现while循环的效果。
sum := 1
for sum < 100 {
sum += sum
}
fmt.Println(sum) // 128
如果条件部分也省略,则成为无限循环(见后文)。
省略所有三个部分(连分号都不需要),形成无限循环。常用于服务器监听、事件循环等,通常配合break或return退出。
count := 0
for {
count++
if count > 3 {
break
}
fmt.Println("循环中...")
}
// 输出: 循环中... (三次)
break或return。
range用于遍历数组、切片、字符串、map或通道。每次迭代返回两个值:索引(或键)和对应的元素值。
nums := []int{10, 20, 30, 40}
for index, value := range nums {
fmt.Printf("索引:%d 值:%d\n", index, value)
}
// 若只需索引:for index := range nums
// 若只需值:for _, value := range nums
message := "Go语言"
for i, ch := range message {
fmt.Printf("%d: %c (类型: %T)\n", i, ch, ch) // ch 是 rune (int32)
}
// 输出:
// 0: G
// 1: o
// 2: 语
// 5: 言 (注意索引不连续,因为中文字符占用3个字节)
特别注意:range遍历字符串是按照UTF-8码点(rune)遍历,而非字节。如果只需要处理字节,可以使用for i := 0; i < len(s); i++。
ages := map[string]int{
"Alice": 30,
"Bob": 25,
"Carol": 28,
}
for name, age := range ages {
fmt.Printf("%s 年龄 %d\n", name, age)
}
// 注意:map遍历顺序是随机的
ch := make(chan int)
go func() {
ch <- 1
ch <- 2
close(ch)
}()
for v := range ch {
fmt.Println(v)
}
// 循环会在通道关闭并且所有元素被接收后结束
break:立即终止最内层的for、switch或select语句,跳出循环。continue:跳过本次循环体剩余语句,进入下一次迭代(只用于循环)。for i := 1; i <= 10; i++ {
if i%2 == 0 {
continue // 不打印偶数
}
if i == 5 {
break // 遇到5跳出循环
}
fmt.Print(i, " ")
}
// 输出: 1 3
通过标签可以配合break或continue跳出/跳过任意层循环(并非只能针对最内层)。标签以labelName:的形式定义在循环语句上方。
outer:
for i := 0; i < 3; i++ {
for j := 0; j < 5; j++ {
if j == 2 {
break outer // 直接跳出外层循环
}
fmt.Printf("(%d,%d) ", i, j)
}
}
// 输出: (0,0) (0,1)
类似地,continue outer会跳过外层循环的本次迭代,直接进入外层循环的下一次迭代。
items := []string{"a", "b", "c"}
for i, v := range items {
// 使用 i 和 v
}
for {
select {
case msg := <-ch:
fmt.Println(msg)
case <-time.After(5 * time.Second):
fmt.Println("timeout")
return
}
}
for i := len(arr) - 1; i >= 0; i-- {
fmt.Println(arr[i])
}
for i := range nums {
nums[i] *= 2
}
// 注意:直接使用 value 修改不会影响原切片
package main
import "fmt"
func main() {
// 打印前10个斐波那契数
a, b := 0, 1
fmt.Print("斐波那契数列: ")
for i := 0; i < 10; i++ {
fmt.Print(a, " ")
a, b = b, a+b
}
fmt.Println()
// 使用range遍历字符串,统计中文字符
str := "Hello, 世界"
chineseCount := 0
for _, ch := range str {
if ch >= 0x4e00 && ch <= 0x9fff {
chineseCount++
}
}
fmt.Printf("字符串 '%s' 包含 %d 个中文字符\n", str, chineseCount)
// 带标签的嵌套循环,找出二维数组中的第一个负数
matrix := [][]int{
{1, 2, 3},
{4, -5, 6},
{7, 8, 9},
}
found:
for i, row := range matrix {
for j, val := range row {
if val < 0 {
fmt.Printf("找到负数 %d 位于 [%d][%d]\n", val, i, j)
break found
}
}
}
}
for和range的用法后,可以优雅地处理绝大多数迭代需求。下一章我们将学习函数与方法,包括多返回值、闭包、延迟调用等高级特性。