Golang中没有while、do-while这类循环关键字,所有循环逻辑都通过for关键字实现,这种设计让循环语法更加统一简洁,同时也支持多种不同的使用场景,满足各类迭代需求。

Golang for循环的基础语法结构
Golang的for循环主要有三种基础语法形式,分别对应不同的使用场景,开发者可以根据实际需求选择合适的结构。
1. 完整三段式结构
这种结构和C语言的传统for循环类似,包含初始化语句、条件判断、后置语句三个部分,三个部分都可以省略,但分号不能省略。
package main
import "fmt"
func main() {
// 完整三段式for循环,打印1到5
for i := 1; i <= 5; i++ {
fmt.Println(i)
}
}
初始化语句会在循环开始前执行一次,通常用于声明循环变量;条件判断在每次循环迭代前执行,结果为true则继续迭代;后置语句在每次循环结束后执行,通常用于更新循环变量。
2. 条件判断式结构
这种形式相当于其他语言的while循环,只保留条件判断部分,省略初始化和后置语句,适合不确定迭代次数、仅根据条件控制循环的场景。
package main
import "fmt"
func main() {
num := 1
// 条件判断式for循环,相当于while num <= 5
for num <= 5 {
fmt.Println(num)
num++
}
}
3. 无限循环结构
省略所有三个部分,仅保留for关键字,会进入无限循环,通常需要配合break或者return语句退出循环,常用于服务监听、持续任务处理等场景。
package main
import (
"fmt"
"time"
)
func main() {
// 无限循环,每隔1秒打印一次当前时间,打印5次后退出
count := 0
for {
if count >= 5 {
break
}
fmt.Println(time.Now().Format("2006-01-02 15:04:05"))
time.Sleep(time.Second)
count++
}
}
Golang for循环的特殊用法
for-range遍历结构
for-range是Golang特有的遍历结构,支持遍历数组、切片、字符串、map、通道等数据类型,使用起来更加简洁,不需要手动控制索引。
package main
import "fmt"
func main() {
// 遍历切片
slice := []string{"a", "b", "c"}
for index, value := range slice {
fmt.Printf("索引:%d,值:%sn", index, value)
}
// 遍历map
m := map[string]int{"x": 1, "y": 2}
for key, val := range m {
fmt.Printf("键:%s,值:%dn", key, val)
}
// 遍历字符串,得到的是Unicode字符
str := "hello 世界"
for i, ch := range str {
fmt.Printf("位置:%d,字符:%cn", i, ch)
}
}
需要注意,for-range遍历map时,键值对的返回顺序是不固定的;遍历字符串时返回的是Unicode码点,不是字节索引。
Golang for循环性能优化技巧
循环是程序中执行频率很高的代码段,合理的优化可以显著提升程序性能,以下是几个常用的优化技巧。
1. 提前获取切片/数组长度
在遍历切片或数组时,如果循环条件中每次都调用len()函数,虽然Golang编译器会做部分优化,但提前获取长度可以减少不必要的函数调用开销,尤其是循环嵌套的场景。
package main
import "fmt"
func main() {
slice := make([]int, 10000)
// 优化前
for i := 0; i < len(slice); i++ {
// 处理逻辑
}
// 优化后,提前获取长度
length := len(slice)
for i := 0; i < length; i++ {
// 处理逻辑
}
}
2. 避免在循环内创建大对象
循环内频繁创建大对象会导致大量的内存分配和垃圾回收开销,尽量将对象创建移到循环外部,或者复用对象。
package main
import "fmt"
func main() {
// 不推荐:循环内创建大切片
for i := 0; i < 1000; i++ {
tmp := make([]int, 1000)
// 使用tmp
_ = tmp
}
// 推荐:循环外创建,循环内复用
tmp := make([]int, 1000)
for i := 0; i < 1000; i++ {
// 重置tmp内容后复用
// 使用tmp
_ = tmp
}
}
3. for-range遍历时减少值拷贝
遍历大结构体切片时,for-range的value会拷贝整个结构体,产生额外开销,此时可以使用索引访问,或者遍历指针切片。
package main
import "fmt"
type BigStruct struct {
Data [1024]int
}
func main() {
slice := make([]BigStruct, 1000)
// 不推荐:值拷贝开销大
for _, v := range slice {
_ = v
}
// 推荐:使用索引访问
for i := range slice {
_ = slice[i]
}
// 或者遍历指针切片
ptrSlice := make([]*BigStruct, 1000)
for _, v := range ptrSlice {
_ = v
}
}
4. 合理使用break和continue
在不需要继续迭代的场景下,及时使用break退出循环,避免无效迭代;使用continue跳过不需要处理的逻辑,减少不必要的计算。
package main
import "fmt"
func main() {
slice := []int{1, 2, 3, 4, 5, 6}
// 找到第一个大于3的元素后退出循环
for _, v := range slice {
if v > 3 {
fmt.Println("找到目标:", v)
break
}
}
// 跳过偶数,只处理奇数
for _, v := range slice {
if v%2 == 0 {
continue
}
fmt.Println("奇数:", v)
}
}
常见问题说明
很多开发者在使用for-range时会在闭包中引用循环变量,这会导致闭包捕获的是循环变量的地址,最终所有闭包都会使用循环结束后的变量值,解决方法是每次迭代时重新声明局部变量。
package main
import (
"fmt"
"time"
)
func main() {
// 错误示例
for i := 0; i < 3; i++ {
go func() {
fmt.Println("错误结果:", i)
}()
}
// 正确示例:重新声明局部变量
for i := 0; i < 3; i++ {
i := i // 重新声明,每个闭包捕获不同的变量
go func() {
fmt.Println("正确结果:", i)
}()
}
time.Sleep(time.Second)
}