目录 [−]
本章介绍Go语言的各种语句。
Go语言的语法定义罗列了所有的语句类型:
Statement = Declaration | LabeledStmt | SimpleStmt | GoStmt | ReturnStmt | BreakStmt | ContinueStmt | GotoStmt | FallthroughStmt | Block | IfStmt | SwitchStmt | SelectStmt | ForStmt | DeferStmt . SimpleStmt = EmptyStmt | ExpressionStmt | SendStmt | IncDecStmt | Assignment | ShortVarDecl .
语句控制程序逻辑的运行。终止语句(terminating statement)代表结束当前程序逻辑单元的运行。
1、 A "return" or "goto" statement.
2、A call to the built-in function panic.
3、A block in which the statement list ends in a terminating statement.
4、 An "if" statement in which:
- the "else" branch is present, and
- both branches are terminating statements.
5、A "for" statement in which:
- there are no "break" statements referring to the "for" statement, and
- the loop condition is absent.
6、A "switch" statement in which:
- there are no "break" statements referring to the "switch" statement,
- there is a default case, and
- the statement lists in each case, including the default, end in a terminating statement, or a possibly labeled "fallthrough" statement.
7、A "select" statement in which:
- there are no "break" statements referring to the "select" statement, and
- the statement lists in each case, including the default if present, end in a terminating statement.
8、A labeled statement labeling a terminating statement.
其它的语句都不会终止程序单元的执行。
参考
空语句
空语句不错任何事
标签语句
增加一个标签, 为 goto、break、continue 使用
|
|
表达式语句
除了特别的内置函数, 一般函数和方法调用、receive 操作都可以出现在语句的上下文中,
这样的语句可以用括号括起来。
下面的内置的函数不能在语句中:
append cap complex imag len make new real unsafe.Alignof unsafe.Offsetof unsafe.Sizeof
下面的操作都可以作为语句:
|
|
但是下面的内置函数调用就不可以:
|
|
send语句
可以通过send的语句往channel发送一个值。
自增和自减语句
注意,Go语言中"++"和"--"是语句(statement),而不是表达式(expression),并且只能放在操作数的后面(postfix)。操作数必须是可寻址的或者是map索引表达式。
x++
等价于 x += 1
,x--
等价于 x -= 1
。
因为它们不是表达式,所以不能以表达式的方式给其它变量赋值,下面的代码是错误的:
|
|
赋值语句
赋值语句左边的操作数必须是可寻址的、或者是map索引表达式、抑或是空标识符_。
操作数可以用括号括起来,不管是左边的操作数还是右边的操作数。
|
|
复合的赋值操作 x op= y
等价于x = x op (y)
,但是x只会被计算一次。运算符的左右两边只能是单值的表达式,而且左边的操作符不能是空标识符。
前面也有代码举例,Go支持多值赋值(tuple),左边的操作数的数量必须和右边的值的数量相同。
赋值过程分为两阶段:
- 计算左边的索引表达式的操作数和指针, 右边的表达式
- 从左到右赋值
下面是赋值语句的一些例子,你可以琢磨琢磨。
a, b = b, a // exchange a and b x := []int{1, 2, 3} i := 0 i, x[i] = 1, 2 // set i = 1, x[0] = 2 i = 0 x[i], i = 2, 1 // set x[0] = 2, i = 1 x[0], x[0] = 1, 2 // set x[0] = 1, then x[0] = 2 (so x[0] == 2 at end) x[1], x[3] = 4, 5 // set x[1] = 4, then panic setting x[3] = 5. type Point struct { x, y int } var p *Point x[2], p.x = 6, 7 // set x[2] = 6, then panic setting p.x = 7 i = 2 x = []int{3, 5, 7} for i, x[i] = range x { // set i, x[2] = 0, x[0] break } // after this loop, i == 0 and x == []int{3, 5, 3}
if 语句
if语句类似其它语言中比如Java、C的if语句,但是也有不同:
1、if语句中的条件表达式可以有简单的语句:
|
|
2、代码块必须以大括号括起来,即使是单行代码块不能省略大括号
|
|
3、if 中的条件表达式可以省略括号,而且一般都省略括号
switch 语句
switch提供了多路处理的机制。switch有两种:表达式switch和type switch。 类型switch在前一章中已经介绍了,本节只介绍表达式switch。
表达式switch(以下直接叫switch)中的表达式会被计算,然后和case clause进行比较。case clause中的表达式不一定是常数。表达式的计算都是从左到右,从上而下的计算的。
如果遇到第一个满足的case,其它的case就不会执行,如果没有满足条件的case,并且有一个default的case的话,会执行default代码块,相当于default用来兜底。最多有一个default,而且一般都将它放在底部,尽管它的位置和其它case cluase没有顺序限制。
在case表达式中,对于未声明类型的bool常量,它首先会被转换成bool类型,对于其它常量,会被转换成缺省类型进行比较。
编译器可能不允许有重复的 case 常量 clause,但是非常量的表达式可以:
|
|
fallthrough
语句可以是case语句或者default的代码块的最后一个语句,只要当前的case语句或者default语句不是最后一个case语句。 但是fallthrough
不能出现在代码块的中间(非最后一个语句)。
fallthrough
将直接跳到下一个case clause的代码块的第一个语句中,不会和那个case 表达式再进行比较。
|
|
上面的代码输出 1 和 2。
switch 表达式的前面可以有简单的表达式,它在switch表达式计算之前执行。
|
|
switch表达式也可以省略,省略的时候意味着表达式为 true,所以会进入case 表达式为true的代码块:
|
|
switch表达式也一般省略括号。
for 语句
for 语句也不用括号,而且在三段式的for语句中,加上括号反而报错。
Go扩展了其它语言中的for语句饿功能,它有以下的形式:
1、普通的三段式的for语句 for init; condition; post {……}
|
|
其中的三段中的任意部分都可以省略,但是分号不能省略。
2、只包含条件表达式的for语句 for condition {}
Go语言中没有while语句,所以for的这种形式类似while语句。
|
|
3、for ... range语句
对于数组、slice、字符串、map对象以及 从channel中接收的值,可以使用这种语句进行遍历。
|
|
对于不同的类型, range迭代的结果可能是一个值,也可能是两个值。具体如下:
Range 表达式 | 第一个值 | 第二个值 |
---|---|---|
array or slice a [n]E, *[n]E, or []E | index i int | a[i] E |
string s string type | index i int | see below rune |
map m map[K]V | key k K | m[k] V |
channel c chan E, <-chan E | element e E |
4、 无任何条件和语句的 for
|
|
- 对于数组,数组指针、slice,索引从0开始,止于 len(a) -1
- 对于字符串, range将迭代unicode code point,而不是字节byte, 因此索引值为当前unicode字符的起始位置,值为rune。如果不是unicode字符,第二个值为0xFFFD
- 对于map的迭代每次迭代可能不同。入股迭代的过程中还没有被访问的对象被移除了,后续的迭代中不会出现这些删除的值。如果迭代的过程中往map中增加值,则增加的值可能出现后续的迭代中,也可能不出现。
- 对于channel,迭代一直会执行,直到channel被关闭。如果channel为nil,则range表达式永远被阻塞,一定要注意。
select 语句
select语句从一组send操作和receive操作中选择一个执行。
它类似switch但是只用来对channel进行操作。
- channel操作数如果是一个表达式,那么表达式只会被计算一次
- 如果有多个case可以被执行,只有一个case会被选择执行。选择算法是伪随机算法。如果没有case可以执行,并且有一个default case,则这个default会被选择执行, 如果没有default, select会被阻塞直到有一个case可以被执行。
select一次只有一个case会执行,所以很多情况下我们把它放入到一个 for循环中。
|
|
select 语句也经常加入超时的case:
|
|
return 语句
return语句从函数中返回。
如果函数没有返回类型, return语句不能返回任何值。
如果函数有返回类型,那么有几种情况
1、返回值可以显示地写在return的后面,每个表达式必须是单值的
|
|
2、return语句中的表达式列表可以是对返回多只函数的调用
|
|
3、如果函数的返回类型参数指定了名字,则return 可以返回空。这些返回类型参数就像本地变量一样。
|
|
所有的结果值在函数的开始时都被初始化为它们的零值。
编译器可能不允许下面的scope中的return返回空:
|
|
break 语句
break用来终止执行最内层的 for、 switch 或者 select语句。
如果break后面跟着一个标签,则标签应该紧贴着要终止的for、switch或者selector语句,下面的代码中 break label2就是错的。
|
|
为什么要用标签呢?因为不带标签的break只能终止最内层(innermost)的循环,如果像终止外部的循环,就得用标签了:
|
|
continue 语句
continue语句开始最内层的 for循环的下一次迭代,终止本次迭代。
同样,如果后面跟着标签,将开始外层的标签指定的for循环的下一次迭代。
goto语句
goto跳转太强大,在很多语言中虽然都提供但是不推荐使用,Go也一样。
下面的goto是不允许的,因为标签L跳过了变量v等声明和赋值,如果后面的代码访问v会有问题。
|
|
代码块外部的goto不能跳到一个代码块内部的标签上:
|
|
defer 语句
defer语句调用一个函数,这个函数将会在当前函数返回的时候才被调用,货站当前函数执行了一个return语句,或者发生panic。
无论是否有panic, defer函数都会调用。
函数可以是普通函数或者方法。调用内置函数有限制,限制条件和上面的表达式语句一样。
当defer语句执行时,函数的参数就会被计算一次,但是函数还没有被调用。
|
|
当一个函数中有多个defer语句时,最后的defer语句中函数最先被执行,也就是defer函数的执行是和它的定义成反序。比如上面的代码先输出50,再输出0.
defer语句中函数也可以有返回值,但是返回值会被忽略。
defer语句中如果定义了一个函数字面量(匿名函数),而且函数中对外部的函数返回参数有所修改的话,修改结果会生效,比如下面的结果,虽然函数最后一个语句返回0,但是defer函数又修改了result的结果,所以f的最终结果为1。
|
|
defer语句虽然挺有用处,但是也需要注意它的函数会被推迟到函数返回的时候才会执行。如果defer函数中有对共享资源的占用的释放,比如锁、文件、连接等,等到函数返回才释放会影响程序的性能,所以对资源能今早释放则今早释放,未必非得放在defer函数中释放。
go 语句
go语句将一个函数调用在一个新的goroutine中独立执行。
goroutine是值得仔细介绍的内容,而且goroutine 调度也非常的有趣,这会在下一章中独立介绍。