Go 借用了 C 语言家族中的一些控制流语法。它支持所有预期的控制结构,包括if...else
、switch
、for
循环,甚至goto
。然而,while
或do...while
声明明显缺失。本章中的以下主题将研究 Go 的控制流元素,其中一些您可能已经熟悉,而另一些则带来了其他语言中没有的一组新功能:
if
声明switch
声明- 类型
Switch
for
声明
Go 中的[T0]语句借用了其他类似 C 语言的基本结构形式。当[T1]关键字后面的布尔表达式计算为[T2]时,该语句有条件地执行代码块,如以下缩略程序所示,该程序显示有关世界货币的信息:
import "fmt"
type Currency struct {
Name string
Country string
Number int
}
var CAD = Currency{
Name: "Canadian Dollar",
Country: "Canada",
Number: 124}
var FJD = Currency{
Name: "Fiji Dollar",
Country: "Fiji",
Number: 242}
var JMD = Currency{
Name: "Jamaican Dollar",
Country: "Jamaica",
Number: 388}
var USD = Currency{
Name: "US Dollar",
Country: "USA",
Number: 840}
func main() {
num0 := 242
if num0 > 100 || num0 < 900 {
fmt.Println("Currency: ", num0)
printCurr(num0)
} else {
fmt.Println("Currency unknown")
}
if num1 := 388; num1 > 100 || num1 < 900 {
fmt.Println("Currency:", num1)
printCurr(num1)
}
}
func printCurr(number int) {
if CAD.Number == number {
fmt.Printf("Found: %+v\n", CAD)
} else if FJD.Number == number {
fmt.Printf("Found: %+v\n", FJD)
} else if JMD.Number == number {
fmt.Printf("Found: %+v\n", JMD)
} else if USD.Number == number {
fmt.Printf("Found: %+v\n", USD)
} else {
fmt.Println("No currency found with number", number)
}
}
golang.fyi/ch03/ifstmt.go
Go 中的[T0]语句与其他语言类似。但是,它在强制执行新规则的同时,也摆脱了一些语法规则:
-
测试表达式周围的括号不是必需的。虽然下面的
if
语句将被编译,但它不是惯用语句:if (num0 > 100 || num0 < 900) { fmt.Println("Currency: ", num0) printCurr(num0) }
-
改为使用以下内容:
if num0 > 100 || num0 < 900 { fmt.Println("Currency: ", num0) printCurr(num0) }
-
代码块始终需要大括号。以下代码段将不会编译:
if num0 > 100 || num0 < 900 printCurr(num0)
-
但是,这将编译:
if num0 > 100 || num0 < 900 {printCurr(num0)}
-
然而,在多行上编写[T0]语句是惯用的(无论语句块多么简单)。这鼓励了良好的风格和清晰度。以下代码段将编译,不会出现任何问题:
if num0 > 100 || num0 < 900 {printCurr(num0)}
-
但是,语句的首选惯用布局是使用多行,如下所示:
if num0 > 100 || num0 < 900 { printCurr(num0) }
-
if
语句可以包括可选的else
块,当if
块中的表达式计算为false
时执行。else
块中的代码必须用多行大括号括起来,如以下代码片段所示:if num0 > 100 || num0 < 900 { fmt.Println("Currency: ", num0) printCurr(num0) } else { fmt.Println("Currency unknown") }
-
else
关键字后面可能紧跟着另一条if
语句,形成if...else...if
链,如前面列出的源代码if CAD.Number == number { fmt.Printf("Found: %+v\n", CAD) } else if FJD.Number == number { fmt.Printf("Found: %+v\n", FJD) }
中的函数
printCurr()
中所用
if...else...if
语句链可以根据需要增长,也可以通过可选的else
语句终止,以表示所有其他未测试的条件。同样,这是在printCurr()
函数中完成的,该函数使用if...else...if
块测试四个条件。最后,它包括一个else
语句块,用于捕获任何其他未测试的条件:
func printCurr(number int) {
if CAD.Number == number {
fmt.Printf("Found: %+v\n", CAD)
} else if FJD.Number == number {
fmt.Printf("Found: %+v\n", FJD)
} else if JMD.Number == number {
fmt.Printf("Found: %+v\n", JMD)
} else if USD.Number == number {
fmt.Printf("Found: %+v\n", USD)
} else {
fmt.Println("No currency found with number", number)
}
}
然而,在 Go 中,编写如此深刻的[T0]代码块的惯用、更简洁的方法是使用无表情的[T1]语句。这将在后面的开关语句部分中介绍。
if
语句支持复合语法,其中测试表达式前面有一条初始化语句。在运行时,在计算测试表达式之前执行初始化,如本代码段(来自前面列出的程序)所示:
if num1 := 388; num1 > 100 || num1 < 900 {
fmt.Println("Currency:", num1)
printCurr(num1)
}
初始化语句遵循普通变量声明和初始化规则。初始化变量的作用域绑定到if
语句块,超过该语句块,变量将无法访问。这是 Go 中常用的习惯用法,本章介绍的其他流控制结构也支持这一习惯用法。
Go 还支持类似于其他语言(如 C 或 Java)中的[T0]语句。Go 中的switch
语句通过对case
子句中的值或表达式求值来实现多路分支,如以下缩写源代码所示:
import "fmt"
type Curr struct {
Currency string
Name string
Country string
Number int
}
var currencies = []Curr{
Curr{"DZD", "Algerian Dinar", "Algeria", 12},
Curr{"AUD", "Australian Dollar", "Australia", 36},
Curr{"EUR", "Euro", "Belgium", 978},
Curr{"CLP", "Chilean Peso", "Chile", 152},
Curr{"EUR", "Euro", "Greece", 978},
Curr{"HTG", "Gourde", "Haiti", 332},
...
}
func isDollar(curr Curr) bool {
var bool result
switch curr {
default:
result = false
case Curr{"AUD", "Australian Dollar", "Australia", 36}:
result = true
case Curr{"HKD", "Hong Kong Dollar", "Hong Koong", 344}:
result = true
case Curr{"USD", "US Dollar", "United States", 840}:
result = true
}
return result
}
func isDollar2(curr Curr) bool {
dollars := []Curr{currencies[2], currencies[6], currencies[9]}
switch curr {
default:
return false
case dollars[0]:
fallthrough
case dollars[1]:
fallthrough
case dollars[2]:
return true
}
return false
}
func isEuro(curr Curr) bool {
switch curr {
case currencies[2], currencies[4], currencies[10]:
return true
default:
return false
}
}
func main() {
curr := Curr{"EUR", "Euro", "Italy", 978}
if isDollar(curr) {
fmt.Printf("%+v is Dollar currency\n", curr)
} else if isEuro(curr) {
fmt.Printf("%+v is Euro currency\n", curr)
} else {
fmt.Println("Currency is not Dollar or Euro")
}
dol := Curr{"HKD", "Hong Kong Dollar", "Hong Koong", 344}
if isDollar2(dol) {
fmt.Println("Dollar currency found:", dol)
}
}
golang.fyi/ch03/switchstmt.go
Go 中的[T0]语句具有一些有趣的属性和规则,使其易于使用,并可对以下内容进行推理:
- 从语义上讲,Go 的
switch
语句可以在两种上下文中使用:- 表达方式
switch
陈述 - 类型
switch
语句
- 表达方式
break
语句可用于提前退出开关代码块。switch
语句可以在没有其他大小写表达式计算为匹配时包含默认大小写。只能有一个默认情况,它可以放置在开关块内的任何位置。
表达式开关非常灵活,可以在程序的控制流需要遵循多条路径的许多上下文中使用。表达式开关支持许多属性,如下所示:
-
表达式开关可以测试任何类型的值。例如,以下代码段(来自上一个程序清单)测试类型为
struct
func isDollar(curr Curr) bool { var bool result switch curr { default: result = false case Curr{"AUD", "Australian Dollar", "Australia", 36}: result = true case Curr{"HKD", "Hong Kong Dollar", "Hong Koong", 344}: result = true case Curr{"USD", "US Dollar", "United States", 840}: result = true } return result }
的变量
Curr
-
case
子句中的表达式从左到右、从上到下求值,直到找到与switch
表达式相等的值(或表达式)。 -
当遇到第一个匹配
switch
表达式的情况时,程序将执行case
块的语句,然后立即退出switch
块。与其他语言不同,Gocase
语句不需要使用中断来避免陷入下一个案例(请参见故障案例部分)。例如,调用isDollar(Curr{"HKD", "Hong Kong Dollar", "Hong Kong", 344})
将匹配前面函数中的第二条case
语句。代码将结果设置为true
并立即退出switch
代码块。 -
Case
子句可以有多个值(或表达式),这些值(或表达式)之间用逗号分隔,并隐含一个逻辑OR
运算符。例如,在下面的代码片段中,switch
表达式curr
使用一个 case 子句根据值currencies[2]
、currencies[4]
或currencies[10]
进行测试,直到找到匹配项:func isEuro(curr Curr) bool { switch curr { case currencies[2], currencies[4], currencies[10]: return true default: return false } }
-
switch
语句是在 Go 中编写复杂条件语句的更干净、更可取的惯用方法。当前面的代码片段与下面的代码片段进行比较时,这一点很明显,下面的代码片段使用if
语句进行相同的比较:func isEuro(curr Curr) bool { if curr == currencies[2] || curr == currencies[4], curr == currencies[10]{ return true }else{ return false } }
Go 的case
子句中没有自动的跳转,就像 C 或 Javaswitch
语句中一样。回想一下,switch
块将在执行其第一个匹配案例后退出。代码必须明确地放置fallthrough
关键字,作为case
块中的最后一条语句,以强制执行流通过后续的case
块。下面的代码片段显示了一个在每个 case 块中都带有一个[T7]的[T6]语句:
func isDollar2(curr Curr) bool {
switch curr {
case Curr{"AUD", "Australian Dollar", "Australia", 36}:
fallthrough
case Curr{"HKD", "Hong Kong Dollar", "Hong Kong", 344}:
fallthrough
case Curr{"USD", "US Dollar", "United States", 840}:
return true
default:
return false
}
}
golang.fyi/ch03/switchstmt.go
当匹配一个 case 时,fallthrough
语句级联到后续case
块的第一个语句。因此,如果curr = Curr{"AUD", "Australian Dollar", "Australia", 36}
,则第一个案例将匹配。然后,流级联到第二个 case 块的第一个语句,这也是一个fallthrough
语句。这导致执行第三个 case 块的第一条语句返回true
。这在功能上等同于以下代码段:
switch curr {
case Curr{"AUD", "Australian Dollar", "Australia", 36},
Curr{"HKD", "Hong Kong Dollar", "Hong Kong", 344},
Curr{"USD", "US Dollar", "United States", 840}:
return true
default:
return false
}
Go 支持一种不指定表达式的switch
语句形式。在此格式中,每个case
表达式的计算结果必须为布尔值true
。下面的缩写源代码说明了无表达式的switch
语句的用法,如函数find()
中所列。函数循环通过Curr
值的切片,根据传入的struct
函数中的字段值搜索匹配项:
import (
"fmt"
"strings"
)
type Curr struct {
Currency string
Name string
Country string
Number int
}
var currencies = []Curr{
Curr{"DZD", "Algerian Dinar", "Algeria", 12},
Curr{"AUD", "Australian Dollar", "Australia", 36},
Curr{"EUR", "Euro", "Belgium", 978},
Curr{"CLP", "Chilean Peso", "Chile", 152},
...
}
func find(name string) {
for i := 0; i < 10; i++ {
c := currencies[i]
switch {
case strings.Contains(c.Currency, name),
strings.Contains(c.Name, name),
strings.Contains(c.Country, name):
fmt.Println("Found", c)
}
}
}
golang.fyi/ch03/switchstmt2.go
请注意,在前面的示例中,函数find()
中的switch
语句不包含表达式。每个case
表达式由逗号分隔,并且必须计算为布尔值,每个表达式之间包含一个隐含的OR
运算符。前面的switch
语句相当于下面使用if
语句来实现相同的逻辑:
func find(name string) {
for I := 0; i < 10; i++ {
c := currencies[i]
if strings.Contains(c.Currency, name) ||
strings.Contains(c.Name, name) ||
strings.Contains(c.Country, name){
fmt.Println""Foun"", c)
}
}
}
switch
关键字后面可能紧跟着一个简单的初始化语句,在此语句中可以声明和初始化switch
代码块的局部变量。这种方便的语法在 initializer 语句和[T2]表达式之间使用分号来声明变量,变量可能出现在[T3]代码块的任何地方。下面的代码示例显示了如何通过初始化两个变量name
和curr
来实现这一点,作为switch
声明的一部分:
func assertEuro(c Curr) bool {
switch name, curr := "Euro", "EUR"; {
case c.Name == name:
return true
case c.Currency == curr:
return true
}
return false
}
golang.fyi/ch03/switchstmt2.go
前面的代码段使用带有初始值设定项的无表达式[T0]语句。请注意,后面的分号表示初始化语句和开关的表达式区域之间的分隔。然而,在本例中,switch
表达式为空。
考虑到 Go 强大的类型支持,该语言支持查询类型信息的能力就不足为奇了。类型switch
是一条语句,它使用 Go 接口类型来比较值(或表达式)的底层类型信息。关于接口类型和类型断言的完整讨论超出了本节的范围。您可以在第 8 章、方法、接口和对象中找到关于该主题的更多详细信息。
然而,为了完整起见,这里对类型开关进行了简短的讨论。现在,您需要知道的是,Go 提供了类型interface{}
或空接口,作为一个超级类型,由类型系统中的所有其他类型实现。当一个值被指定为类型interface{}
时,可以使用类型switch
查询该值,如下面代码段中的函数findAny()
所示,以查询其底层类型的信息:
func find(name string) {
for i := 0; i < 10; i++ {
c := currencies[i]
switch {
case strings.Contains(c.Currency, name),
strings.Contains(c.Name, name),
strings.Contains(c.Country, name):
fmt.Println("Found", c)
}
}
}
func findNumber(num int) {
for _, curr := range currencies {
if curr.Number == num {
fmt.Println("Found", curr)
}
}
}
func findAny(val interface{}) {
switch i := val.(type) {
case int:
findNumber(i)
case string:
find(i)
default:
fmt.Printf("Unable to search with type %T\n", val)
}
}
func main() {
findAny("Peso")
findAny(404)
findAny(978)
findAny(false)
}
golang.fyi/ch03/switchstmt2.go
函数findAny()
以interface{}
为参数。类型switch
用于使用类型断言表达式确定变量val
的基础类型和值:
switch i := val.(type)
注意前面的类型断言表达式中使用了关键字type
。将根据val.(type)
查询的类型信息对每个 case 子句进行测试。变量i
将被分配基础类型的实际值,并用于调用具有相应值的函数。调用默认块是为了防止分配给参数val
的任何意外类型。然后可以使用不同类型的值调用函数findAny
,如以下代码段所示:
findAny("Peso")
findAny(404)
findAny(978)
findAny(false)
作为与 C 族相关的语言,Go 还支持for
循环式控制结构。然而,正如你现在可能已经预料到的那样,Go 的for
语句以有趣的方式简单地工作。Go 中的for
语句支持四种不同的习惯用法,如下表所示:
for x < 10 {
...
}
| | 无限循环 | 可以省略条件表达式以创建无限循环:
for {
...
}
|
| 传统的 | 这是带有初始值设定项、测试和更新子句的 C 族for
循环的传统形式:
for x:=0; x < 10; x++ {
...
}
| | 射程 | 用于迭代表示存储在数组、字符串(符文数组)、切片、映射和通道中的项集合的表达式:
for i, val := range values {
...
}
|
注意,与 Go 中的所有其他控制语句一样,for
语句在其表达式周围不使用括号。循环代码块的所有语句都必须用花括号括起来,否则编译器将产生错误。
for
条件使用的构造在语义上等同于其他语言中的while
循环。它使用关键字for
,后跟一个布尔表达式,只要计算结果为 true,循环就可以继续进行。以下缩写源代码列表显示了这种形式的for
循环的示例:
type Curr struct {
Currency string
Name string
Country string
Number int
}
var currencies = []Curr{
Curr{"KES", "Kenyan Shilling", "Kenya", 404},
Curr{"AUD", "Australian Dollar", "Australia", 36},
...
}
func listCurrs(howlong int) {
i := 0
for i < len(currencies) {
fmt.Println(currencies[i])
i++
}
}
golang.fyi/ch03/forstmt.go
函数listCurrs()
中的for
语句只要条件表达式i < len(currencencies)
返回true
就进行迭代。必须注意确保i
的值随每次迭代而更新,以避免创建意外的无限循环。
当在for
语句中省略布尔表达式时,循环将无限期运行,如下例所示:
for {
// statements here
}
这相当于其他语言(如 C 或 Java)中的[T0]或[T1]。
Go 还支持传统形式的for
语句,包括初始化语句、条件表达式和 update 语句,所有语句都用分号分隔。这是传统上在其他类似 C 语言中发现的语句形式。下面的源代码片段演示了在函数sortByNumber
中使用传统的 for 语句:
type Curr struct {
Currency string
Name string
Country string
Number int
}
var currencies = []Curr{
Curr{"KES", "Kenyan Shilling", "Kenya", 404},
Curr{"AUD", "Australian Dollar", "Australia", 36},
...
}
func sortByNumber() {
N := len(currencies)
for i := 0; i < N-1; i++ {
currMin := i
for k := i + 1; k < N; k++ {
if currencies[k].Number < currencies[currMin].Number {
currMin = k
}
}
// swap
if currMin != i {
temp := currencies[i]
currencies[i] = currencies[currMin]
currencies[currMin] = temp
}
}
}
golang.fyi/ch03/forstmt.go
前面的示例实现了一个选择排序,通过比较每个struct
值的Number
字段对slice
货币进行排序。for
语句的不同部分使用以下代码片段突出显示(来自前面的函数):
事实证明,传统的for
语句是到目前为止讨论的循环的其他形式的超集,如下表所示:
k:=initialize()
for ; k < 10;
++{
...
}
| 省略初始化语句。变量k
在for
语句之外初始化。然而,惯用的方法是用[T2]语句初始化变量。 |
|
for k:=0; k < 10;{
...
}
| 此处省略update
语句(在最后一个分号之后)。开发人员必须在其他地方提供更新逻辑,否则可能会创建无限循环。 |
|
for ; k < 10;{
...
}
| 这相当于for
条件表(前面讨论过)for k < 10 { ... }
。同样,变量k
应该在循环之前声明。必须小心更新k
,否则可能会造成无限循环。 |
|
for k:=0; ;k++{
...
}
| 这里,省略条件表达式。与前面一样,这将条件求值为true
,如果在循环中没有引入适当的终止逻辑,将产生一个无限循环。 |
|
for ; ;{ ... }
| 这相当于形式[T0],并产生一个无限循环。 |
for
循环中的初始化语句和update
语句是常规 Go 语句。因此,它们可以用于初始化和更新多个变量,正如 Go 所支持的那样。为了说明这一点,下一个示例在语句子句中同时初始化和更新两个变量w1
和w2
:
import (
"fmt"
"math/rand"
)
var list1 = []string{
"break", "lake", "go",
"right", "strong",
"kite", "hello"}
var list2 = []string{
"fix", "river", "stop",
"left", "weak", "flight",
"bye"}
func main() {
rand.Seed(31)
for w1, w2:= nextPair();
w1 != "go" && w2 != "stop";
w1, w2 = nextPair() {
fmt.Printf("Word Pair -> [%s, %s]\n", w1, w2)
}
}
func nextPair() (w1, w2 string) {
pos := rand.Intn(len(list1))
return list1[pos], list2[pos]
}
golang.fyi/ch03/forstmt2.go
初始化语句通过调用函数nextPair()
来初始化变量w1
和w2
。该条件使用一个复合逻辑表达式,只要其计算结果为 true,该表达式就会保持循环运行。最后,变量w1
和w2
在循环的每次迭代中都会通过调用nextPair()
进行更新。
最后,for
语句支持另一种形式,它使用关键字range
迭代计算为数组、切片、映射、字符串或通道的表达式。for range 循环具有以下通用形式:
对于<标识符列表>:=]范围<表达式>{…}
根据range
表达式产生的类型,每次迭代最多可以产生两个变量,如下表所示:
for i, v := range []V{1,2,3} {
...
}
| 该范围产生两个值,其中i
是循环索引,v
是来自集合的值v[i]
。有关阵列和切片的进一步讨论,请参见第 7 章、复合类型。 |
| 循环字符串值:
for i, v := range "Hello" {
...
}
| 该范围产生两个值,其中i
是字符串中字节的索引,v
是作为符文返回的v[i]
处 UTF-8 编码字节的值。有关字符串类型的进一步讨论,请参见第 4 章、数据类型。 |
| 循环映射:
for k, v := range map[K]V {
...
}
| range
产生两个值,其中k
被分配K
类型的映射键的值,v
被存储在V
类型的map[k]
中。有关 map 的进一步讨论,请参见第 7 章、复合类型。 |
| 循环通道值:
var ch chan T
for c := range ch {
...
}
| 第 9 章、并发对通道进行了充分的讨论。通道是能够接收和发射值的双向导管。for...range
语句在每次迭代中将从通道接收到的每个值分配给变量c
。 |
您应该知道,每次迭代发出的值都是存储在源中的原始项的副本。例如,在以下程序中,循环完成后切片中的值不会更新:
import "fmt"
func main() {
vals := []int{4, 2, 6}
for _, v := range vals {
v--
}
fmt.Println(vals)
}
要使用for...range
循环更新原始值,请使用索引表达式访问原始值,如下所示。
func main() {
vals := []int{4, 2, 6}
for i, v := range vals {
vals[i] = v - 1
}
fmt.Println(vals)
}
在前面的示例中,在切片索引表达式vals[i]
中使用值i
来更新切片中存储的原始值。如果只需要访问数组、切片或字符串(或映射键)的索引值,则可以省略迭代值(赋值中的第二个变量)。例如,在下面的示例中,for...range
语句只在每次迭代时发出当前索引值:
func printCurrencies() {
for i := range currencies {
fmt.Printf("%d: %v\n", i, currencies[i])
}
}
golang.fyi/ch03/for-range-stmt.go
最后,在某些情况下,您可能对迭代生成的任何值都不感兴趣,而是对迭代机制本身感兴趣。引入了 for 语句的下一种形式(从 Go 的 1.4 版开始),以表示不带任何变量声明的 for 范围,如以下代码段所示:
func main() {
for range []int{1,1,1,1} {
fmt.Println("Looping")
}
}
前面的代码将在标准输出上打印"Looping"
四次。当范围表达式位于通道上方时,有时会使用这种形式的for...range
循环。它仅用于通知通道中存在值。
Go 支持一组专门设计用于突然退出正在运行的代码块的语句,例如 switch 和 for 语句,并将控制权转移到代码的不同部分。这三条语句都可以接受标签标识符,该标识符在代码中指定要传输控制的目标位置。
在深入本节的核心部分之前,有必要先看看这些语句使用的标签。在 Go 中声明标签需要标识符后跟冒号,如以下代码段所示:
DoSearch:
给标签命名是一个风格问题。但是,应遵循上一章中介绍的标识符命名准则。标签必须包含在函数中。Go 编译器将不允许未使用的标签在代码中悬空。与变量类似,如果声明了标签,则必须在代码中引用它。
与其他类似 C 的语言一样,Gobreak
语句终止并退出最里面的switch
或for
语句代码块,并将控制权转移到运行程序的另一部分。break
语句可以接受一个可选的标签标识符,该标识符在封闭函数中指定一个标签位置,程序将在该位置继续运行。以下是break
语句需要记住的标签属性:
- 标签必须在
break
语句所在的同一运行函数中声明 - 声明的标签后面必须紧跟着嵌套中断的封闭控制语句(一个
for
循环或switch
语句)
如果一个break
语句后面有一个标签,控制权将被转移,而不是转移到标签所在的位置,而是转移到紧跟在标签块后面的语句。如果没有提供标签,break
语句会突然退出,并将控制权转移到其封闭的for
语句(或switch
语句)块后面的下一个语句。
下面的代码是一个过度夸张的线性搜索,它演示了[T0]语句的工作原理。它执行单词搜索,并在切片中找到单词的第一个实例后退出:
import (
"fmt"
)
var words = [][]string{
{"break", "lake", "go", "right", "strong", "kite", "hello"},
{"fix", "river", "stop", "left", "weak", "flight", "bye"},
{"fix", "lake", "slow", "middle", "sturdy", "high", "hello"},
}
func search(w string) {
DoSearch:
for i := 0; i < len(words); i++ {
for k := 0; k < len(words[i]); k++ {
if words[i][k] == w {
fmt.Println("Found", w)
break DoSearch
}
}
}
}
golang.fyi/ch03/breakstmt.go
在前面的代码片段中,break DoSearch
语句将基本上退出最内层的for
循环,并导致执行流在最外层标记的for
语句之后继续,在本例中,该语句将简单地结束程序。
continue
语句使控制流立即终止封闭的for
循环的当前迭代,并跳到下一个迭代。continue
语句也可以采用可选标签。该标签具有与break
声明类似的属性:
- 标签必须在
continue
语句所在的同一运行函数中声明 - 声明的标签后面必须紧跟一个包含 continue 语句的
for
循环语句
当存在时,在for
语句块内到达continue
语句,for
循环将突然终止,控制权将转移到标记为for
的最外层循环块继续。如果未指定标签,continue
语句将简单地将控制转移到其封闭的for
循环块的开始,以继续下一次迭代。
为了说明这一点,让我们回顾一下前面的单词搜索示例。此版本使用了一个continue
语句,这会导致搜索在切片中找到多个已搜索单词:
func search(w string) {
DoSearch:
for i := 0; i < len(words); i++ {
for k := 0; k < len(words[i]); k++ {
if words[i][k] == w {
fmt.Println("Found", w)
continue DoSearch
}
}
}
}
golang.fyi/ch03/breakstmt2.go
continue DoSearch
语句使最内层循环的当前迭代停止,并将控制转移到标记的外部循环,使其继续进行下一次迭代。
goto
语句更灵活,因为它允许将流控制传输到函数内部的任意位置,在该位置定义了目标标签。goto
语句导致突然将控制转移到goto
语句引用的标签。下面以一个简单但功能性的示例展示了 Go 的goto
语句的作用:
import "fmt"
func main() {
var a string
Start:
for {
switch {
case a < "aaa":
goto A
case a >= "aaa" && a < "aaabbb":
goto B
case a == "aaabbb":
break Start
}
A:
a += "a"
continue Start
B:
a += "b"
continue Start
}
fmt.Println(a)
}
golang.fyi/ch03/gotostmt.go
代码使用goto
语句跳转到main()
函数的不同部分。注意,goto
语句可以针对代码中任何地方定义的标签。出于完整性考虑,Start:
标签的多余用法保留在代码中,在本上下文中没有必要(因为如果没有标签,continue 将具有相同的效果)。以下内容为使用goto
声明提供了一些指导:
- 避免使用
goto
语句,除非所实现的逻辑只能通过goto
分支实现。这是因为过度使用[T2]语句会使代码更难推理和调试。 - 尽可能将
goto
语句及其目标标签放在同一封闭代码块中。 - 避免在[T0]语句会导致流跳过新变量声明或导致重新声明它们的地方放置标签。
- Go 将允许您从内部跳转到外部封闭代码块。
- 如果试图跳转到对等方或封闭的代码块,则是编译错误。
本章提供了 Go 中控制流机制的演练,包括if
、switch
和for
语句。虽然 Go 的流控制结构看起来简单易用,但它们功能强大,实现了现代语言所期望的所有分支原语。每一个概念都向读者介绍了大量的细节和例子,以确保主题的清晰。下一章通过向读者介绍 Go 类型系统,继续我们对 Go 基本原理的研究。