一、if语句
Go的流程控制主要包括条件分支、循环和并发。
if语句一般由if关键字、条件表达式和由花括号包裹的代码块组成。在Go中,代码块必须由花括号包裹。这里的条件表达式是结果类型为bool的表达式。例:if number > 10 { number += 3 }
else分支:
if number > 10 { } else { }
if语句还支持串联
if number > 100 { } else if number < 100 { } else { }
上面的number变量可以用单独的语句来声明也可以直接加入到if子句中:
if number := 1; number > 100 { }
这里的number := 1被叫做if语句的初始化子句。它应被放置在if关键字和条件表达式之间,并与前者由空格间隔,与后者由英文分号间隔。我们在这里使用了短变量声明语句,即:在声明变量number的同时为它赋值。它的作用域仅在这条if语句所代表的代码块中。也可以说,变量number对于该if语句之外的代码来说是不可见的。
如果我们在if语句之外声明了number变量,那么number := 1也是合法的。var number intif number := 1; number > 100 { }
这种写法有一个专有名词:标识符的重声明。只要对同一个标识符的两次声明各自所在的代码块之间存在包含的关系,就会形成对该标识符的重声明。具体到这里,第一次声明的number变量所在的是该if语句的外层代码块,而number := 1所声明的number变量所在的是该if语句的代码块。它们之间存在包含关系,所以对number的重声明就形成了。
这种情况造成的结果就是,if语句内部对number的访问和赋值都只会涉及到第二次声明的那个number变量。这种现象叫做标识符的遮蔽。二、switch语句
switch语句提供了一个多分支条件执行的方法。每一个case可以携带一个表达式或一个类型说明符。前者又可被简称为case表达式。因此,Go语言的switch语句又分为表达式switch语句和类型switch语句。
1、表达式switch语句
var name string...switch name {case "Golang": fmt.Println("Golang")case "Rust": fmt.Println("Rust") default: fmt.Println("PHP是世界上最好的语言") }
Go会依照从上至下的顺序对每一条case语句中case表达式进行求值,只要被发现其表达式与switch表达式的结果相同,该case语句就会被选中。其余的case语句会被忽略。 与if相同,switch语句还可以包含初始化字句,且其出现位置和写法如出一辙:
names := []string{ "Golang","java","PHP"}switch name:=names[0];name { case "Golang": fmt.Println("Golang") ... default: fmt.Println("Unknown") }
2、类型switch语句
类型switch语句与一般形式有两点差别。第一点,紧随case关键字的不是表达式,而是类型说明符。类型说明符由若干个类型字面量组成,且多个类型字面量之间由英文逗号分隔。第二点,它的switch表达式是非常特殊的。这种特殊的表达式也起到了类型断言的作用,但其表现形式很特殊,如:v.(type),其中v必须代表一个接口类型的值。该类表达式只能出现在类型switch语句中,且只能充当switch表达式。一个类型switch语句的示例如下:
v := 11switch i := interface{}(v).(type) {case int, int8, int16, int32, int64: fmt.Println("A signed integer:%d. The type is %T. \n", v, i) case uint, uint8, uint16, uint32, uint64: fmt.Println("A unsigned integer: %d. The type is %T. \n", v, i) default: fmt.Println("Unknown!") }
我们这里把switch表达式的结果赋给了一个变量。如此以来,我们就可以在该switch语句中使用这个结果了。这段代码被执行后,输出:"A signed integer:11. The type is int."
最后说一下fallthrough。它既是一个关键字,又可以代表一条语句。fallthrough语句可被包含在表达式switch语句中的case语句中。它的作用是使控制权流转到下一个case。不过要注意fallthrough语句仅能作为case语句中的最后一条语句出现。并且,包含它的case语句不是其所属switch语句的最后一条case语句。三、for
一条for语句通常由关键词for、初始化字句、条件表达式、后置子句和以花括号包裹的代码块组成。其中,初始化字句、条件表达式和后置子句之间需用分号分隔。例:
for i := 0; i < 10; i++ { fmt.Print(i, " ")}
初始化子句、条件表达式、后置子句中的任何一个或多个,不过起到分隔作用的分号一般需要被保留下来。除非在仅有条件表达式或三者全被省略时分号才可以被一同省略。
可以把上述的初始化子句、条件表达式、后置子句合称为for子句。for语句还有另外一种编写方式,那就是用range字句替换掉for字句。range子句包含一个或两个迭代变量(用于与迭代出的值绑定)、特殊标记:=或=、关键字range以及range表达式。其中,range表达式的结果值的类型应该是能够被迭代的,包括:字符串类型、数组类型、数组的指针类型、切片类型、字典类型和通道类型。例:for i, v := range "Go语言" { fmt.Printf("%d: %c\n", i, v) }
对于字符串类型的被迭代值来说,for语句每次会迭代出两个值,第一个值代表第二个值在字符串中的索引,而第二个值则代表该字符串中的某一个字符。迭代是以索引递增的顺序进行的。上面的代码输出:
0: G1: o2: 语 5: 言
可以看到迭代出的索引值并不是连续的,字符串的底层是以字节数组的形式存储的。而在Go中,字符串到字节数组的转换是通过对其中的每个字符进行UTF-8编码来完成的。字符串"Go语言"中的每一个字符与相应的字节数组之间的对应关系如下:
一个中文字符在经过UTF-8编码之后会表现为三个字节。所以我们用语[0],语[1]、语[2]分别表示字符'语'经编码后的第一、二、三个字节。
这就可以解释为什么会打印出如上内容了:每次迭代出的第一个值所代表的是第二个字符值经编码后的第一个字节在该字符串经编码后的字节数组中的索引值。 对于数组值、数组的指针值和切片来说,range子句每次也会被迭代出两个值。其中,第一个值会是第二个值在迭代值中的索引,而第二个值则是被迭代值中的某一个元素。同样的,迭代是以索引递增的顺序进行的。 对于字典值来说,range子句每次仍然会迭代出两个值。显然,第一个值是字典中的某一个键,而第二个值则是该键对应的那个值。注意,对字典值上的迭代,Go语言是不保证其顺序的。 携带range子句的for语句还可以应用在一个通道值之上。其作用是不断地从该通道值中接受数据,不过每次只会接收一个值。如果通道值中没有数据,那么for语句的执行会处于阻塞状态。无论怎样,这样的循环会一直进行下去。直至该通道值被关闭,for语句的执行才会结束。四、select语句
select语句属于条件分支流程控制语句,不过它只能用于通道。它可以包含若干条case语句,并根据条件选择其中之一执行。select语句的case关键词只能后跟用于通道的发送操作的表达式以及接受操作的表达式或语句。例:
ch1 := make(chan int, 1)ch2 := make(chan int, 1) ... select { case e1 := <-ch1: fmt.Printf("1th case is selected. e1=%v.\n", e1) case e2 := <-ch2: fmt.Printf("2th case is selected. e2=%v.\n", e2) default: fmt.Printf("No data!") }
如果该select语句执行的时候通道ch1\ch2都没有任何数据,那么肯定只有default case会被执行。但是,只要有一个通道在当时有数据就不会轮到default case执行了。显然,对于包含通道接收操作的case来讲,其执行条件就是通道中存在数据。如果在当时有数据的通道多于一个,那么Go会通过一种伪随机的算法来决定哪一个case将被执行。
另外,对于包含通道发送操作的case来讲,其执行条件就是通道中至少还能缓冲一个数据(或者说通道未满)。类似,当有多个case中的通道未满时,Go会随机选择:ch3 := make(chan int, 100)...select {case ch3 <- 1: fmt.Printf("Sent %d\n", 1) case ch3 <- 2: fmt.Printf("Send %d\n", 2) default: fmt.Println("Full channel!") }
该条select语句的两个case中包含的都是针对通道ch3的发送操作。如果我们把这条语句置于一个循环中,那么就相当于用有限范围的随机整数集合去填满一个通道。
**如果一个select语句中不存在default case,并且被执行时其中的所有case都不满足条件,那么它的执行就会被阻塞!**当前进程的进行也会因此而停滞。直到其中一个case满足了执行条件,执行才会继续。我们一直在说case 执行条件的满足与否取决于其操作的通道在当时的状态。未被初始化的通道会使操作它的case永远满足不了执行条件。 break语句也可以被包含在select语句中的case语句中。它的作用是立即结束当前的select语句的执行。不论其所属的case语句中是否还有未被执行的语句。