Go 没有 while 关键字。在 for 后面写上一个条件,就得到了一个 while 循环:
条件在每次迭代之前检查,所以如果一开始就为 false,循环体一次也不会执行。这和 C、Java 或 JavaScript 里的 while (balance < 2000) 完全相同,只是去掉了括号和关键字。Go 的设计者用一个循环关键字涵盖所有循环形式;四种写法请参阅 for 循环页面。
while(true):无限循环
完全省略条件,循环就会一直运行,直到内部的某个语句结束它:
for { } 是惯用写法。for true { } 也能编译,但读起来像是从别的语言转过来的人写的。
有四种方式可以结束无限循环:break(离开循环)、return(离开函数)、panic,以及 os.Exit。如果一个程序的 main 停在 for {} 里,又没有以上任何一种,它会一直运行到被终止为止,这对服务器或 worker 来说正合适。
do-while
do-while 会先执行一次循环体,再检查条件。Go 没有专门的语法,把检查放在无限循环的末尾即可:
无论如何,循环体至少会执行一次,这正是 do-while 的本质特征。这里的随机数源使用了固定种子,所以每次运行的输出都相同;需要真正的随机数时,使用不带种子源的 rand.IntN。
第二种写法通过强制执行第一轮,把条件保留在循环头部:
for first := true; first || condition(); first = false {
// body
}
这样也行,但末尾带 break 的无限循环更容易阅读,大多数 Go 代码也是这样写的。
一直读到结束
实际 Go 代码中最常见的 while 循环,是不断读取输入直到读完。bufio.Scanner 就是为此设计的:Scan 在输入结束或出错时返回 false,所以它本身就是循环条件:
把 strings.NewReader(input) 换成 os.Stdin,同样的循环就会逐行读取用户输入。循环结束后要检查 scanner.Err():Scan 返回 false 并不能告诉你输入是正常结束还是读取失败。
break 和 continue
两者在每种 for 写法中都可以使用:
在只带条件的循环中,continue 会直接跳回条件检查。这很容易引出一个隐蔽的 bug:如果计数器在循环体的末尾更新,它上方的 continue 会跳过更新,循环就永远不会结束。像上面那样在开头更新计数器,或者使用三段式的 for,它的后置语句在 continue 之后也会执行。
等待某个条件
等待条件成立的循环应该阻塞或休眠,而不是空转:
// Wrong: burns a CPU core while waiting
for !ready {
}
// Better: poll with a delay
for !isReady() {
time.Sleep(100 * time.Millisecond)
}
轮询一个由其他 goroutine 写入的普通变量还会造成数据竞争。在 goroutine 之间,应该改为等待通道、sync.WaitGroup 或 sync.Cond,或者使用带定时器的 select。对于有次数上限的重试循环,优先使用计数的 for,这样循环就不可能无限运行下去:
for attempt := 1; attempt <= 5; attempt++ {
if err := connect(); err == nil {
break
}
time.Sleep(time.Duration(attempt) * 200 * time.Millisecond)
}
常见错误
- 忘记修改条件变量。
for i < 10 { fmt.Println(i) }永远不会结束。每个 while 循环的循环体里都需要有让它逐步走向退出的语句。 - 写成
while。while x < 5 {}会报错syntax error: unexpected name x at end of statement,因为while不是关键字,编译器把它当成了普通的名字。 - 习惯性地加括号。
for (x < 5) {}能编译,gofmt会把括号去掉。 - 在循环里声明变量。
for n := 10; n > 0 {}是语法错误;带初始化语句的循环必须写全三个部分。要么在循环前声明n,要么写成完整的三段式。
常见问题
Go 有 while 循环吗?
没有 while 这个关键字。Go 的所有循环都用 for,而只带条件的 for 正是 while 循环:
for count < 10 {
count++
}
条件在每次迭代之前检查,和 C 或 Java 里的 while (count < 10) 完全一样。
在 Go 中如何写 while(true)?
只写 for,后面什么都不加:for { ... }。它会一直循环,直到被 break、return、panic 或 os.Exit 结束。for true { ... } 也能编译,但 for { } 才是惯用写法。
在 Go 中如何写 do-while 循环?
使用无限循环,并把条件检查放在循环体的末尾:
for {
// body runs at least once
if !condition {
break
}
}
循环体总会在检查条件之前先执行一次,这正是 do-while 的含义。