语句
20 分钟阅读
Statements 语句
语句控制执行。
Statement =
Declaration | LabeledStmt | SimpleStmt |
GoStmt | ReturnStmt | BreakStmt | ContinueStmt | GotoStmt |
FallthroughStmt | Block | IfStmt | SwitchStmt | SelectStmt | ForStmt |
DeferStmt .
SimpleStmt = EmptyStmt | ExpressionStmt | SendStmt | IncDecStmt | Assignment | ShortVarDecl .
Terminating statements 终止语句
终止语句中断了一个块中的常规控制流。下列语句是终止性的:
- “
return
“或 “goto
“语句。 - 对内置函数
panic
的调用。 - 语句列表以终止语句结束的块。
- 一个 “
if
“语句,其中:- 存在 “
else
“分支,并且 - 两个分支都是终止语句。
- 存在 “
- 一个 “
for
“语句,其中:- 没有 “
break
“语句引用”for
“语句,并且 - 循环条件不存在,并且
- 这个”
for
“语句没有使用range
子句。
- 没有 “
- 一个 “
switch
“语句,其中:- 没有 “
break
“语句引用 “switch
“语句 - 有一个默认的分支,并且
- 每个分支下的语句列表,包括默认分支,都以一个终止语句结束,或者是一个可能标有 “fallthrough “的语句。
- 没有 “
- 一个 “
select
“语句,其中:- 没有”
break
“语句引用”select
“语句,并且 - 每个分支下的语句列表,包括默认分支(如果存在),都以一个终止语句结束
- 没有”
- 标记终止语句的标签语句。
所有其他语句都不是终止性的。
如果语句列表不是空的,并且其最后的非空语句是终止性的,则该列表以终止性语句结束。
Empty statements 空语句
空语句不做任何事情。
EmptyStmt = .
Labeled statements 标签语句
标签语句可以是goto
、break
或continue
语句的目标。
LabeledStmt = Label ":" Statement .
Label = identifier .
|
|
Expression statements 表达式语句
除了特定的内置函数外,函数和方法调用以及接收操作可以出现在语句上下文中。这样的语句可以用圆括号括起来。
ExpressionStmt = Expression .
以下内置函数不允许出现在语句上下文中:
|
|
|
|
Send statements 发送语句
发送语句在通道上发送一个值。通道表达式的核心类型必须是一个通道,通道方向必须允许发送操作,而且要发送的值的类型必须是可以分配给通道的元素类型。
SendStmt = Channel "<-" Expression .
Channel = Expression .
在通信开始之前,通道和值表达式都被求值。通信阻塞,直到发送(操作)可以进行。如果有接收端准备好了,那么在一个没有缓冲的通道上的发送可以继续进行。在缓冲通道上的发送可以在缓冲区有空间的情况下进行。在关闭的通道上进行发送会引起运行时恐慌。在nil
通道上的发送会永远阻塞。
|
|
IncDec statements 自增自减语句
++
和--
语句通过无类型常量1
来增加或减少其操作数。与赋值一样,操作数必须是可寻址的,或者是一个映射索引表达式。
IncDecStmt = Expression ( "++" | "--" ) .
下面的赋值语句在语义上是等同的:
IncDec statement Assignment
x++ x += 1
x-- x -= 1
Assignment statements 赋值语句
赋值是用一个表达式指定的新值来替换存储在变量中的当前值。赋值语句可以为单个变量赋值,也可以将多个值赋给匹配数量的变量。
Assignment = ExpressionList assign_op ExpressionList .
assign_op = [ add_op | mul_op ] "=" .
每个左操作数必须是可寻址的,或是一个映射索引表达式,或是(仅对=
赋值)空白标识符,即_
。操作数可以用圆括号括起来。
|
|
赋值操作x
op=
y
,其中op
是一个二元算术运算符,相当于x
=
x
op (y)
,但只对x
进行一次求值。op=
结构是一个单一的标记。在赋值操作中,左表达式和右表达式列表都必须正好包含一个单值表达式,并且左表达式不能是空白标识符。
|
|
多元赋值将多值运算的各个元素分配给一个变量列表。有两种形式。在第一种形式中,右操作数是单个多值表达式,如一个函数调用、一个通道或映射操作,或一个类型断言。左操作数必须与值的数量相匹配。例如,如果f
是一个返回两个值的函数,
|
|
将第一个值赋给 x
,将第二个值赋给 y
。在第二种形式中,左操作数必须等于右表达式数量,每个表达式必须是单值的,右边的第n
个表达式被分配给左边的第n
个操作数:
|
|
空白标识符,即_
提供了一种在赋值中忽略右值的方法:
|
|
赋值分两个阶段进行。第一阶段,左边的索引表达式和指针间接(包括选择器中的隐式指针间接)的操作数以及右边的表达式都按照通常的顺序被求值。第二阶段,赋值是按照从左到右的顺序进行的。
|
|
在赋值中,每个值必须可以赋给它所赋的操作数的类型,但有以下特殊情况:
- 任何类型的值都可以被分配给
空白标识符
。 - 如果无类型的常量被分配给接口类型的变量或空白标识符,那么该常量首先被隐式地转换为其默认类型。
- 如果无类型的布尔值被分配给接口类型的变量或空白标识符,它首先被隐式转换为
bool
。
If statements - if 语句
“if
“语句根据布尔表达式的值指定两个分支的条件性执行。如果表达式的值为真,则执行 “if
“分支,否则,执行 “else
“分支(如果存在)。
IfStmt = "if" [ SimpleStmt ";" ] Expression Block [ "else" ( IfStmt | Block ) ] .
if x > max {
x = max
}
表达式前面可以有一个简单的语句,它在表达式被求值之前执行。
|
|
Switch statements - switch 语句
“switch
“语句提供多路执行。表达式或类型与 “switch “内部的 “case “进行比较,以确定执行哪个分支。
SwitchStmt = ExprSwitchStmt | TypeSwitchStmt .
有两种形式:表达式开关
和类型选择
。在表达式开关
中,case 包含与开关表达式的值进行比较的表达式。在类型选择
中,case 包含类型,这些类型与特别说明的表达式开关的类型进行比较。在switch语句
中,switch 表达式
被精确地求值一次。
Expression switches 表达式开关
在表达式开关中,switch 表达式
被求值,case表达式(不需要是常量)被从左到右和从上到下求值;第一个等于switch表达式的表达式会触发相关case语句的执行;其他case被跳过。如果没有匹配的case,但有一个 “default” case,那么这个语句将被执行。“default” case 最多只能有一个,它可以出现在 “switch “语句的任何地方。缺省的switch 表达式
等同于布尔值true
。
ExprSwitchStmt = "switch" [ SimpleStmt ";" ] [ Expression ] "{" { ExprCaseClause } "}" .
ExprCaseClause = ExprSwitchCase ":" StatementList .
ExprSwitchCase = "case" ExpressionList | "default" .
如果switch 表达式
求值为无类型的常量,它首先被隐式地转换为其默认类型。预先声明的无类型值nil
不能作为switch 表达式
使用。switch 表达式
的类型必须是可比较的。
如果一个case 表达式是无类型的,它首先被隐式地转换为switch 表达式
的类型。对于每个(可能转换过的)case 表达式x
和switch 表达式
的值t
,x == t
必须是一个有效的比较。
换句话说,switch 表达式
被当作是用来声明和初始化一个没有明确类型的临时变量t
;每个case表达式x
都是用t
的值来测试是否相等。
在case或default子句中,最后一个非空语句可以是一个(可能被标记的)“fallthrough “语句,表示控制应该从这个子句的结尾流向下一个子句的第一个语句。否则,控制将流向 “switch “语句的末尾。“fallthrough"语句可以作为表达式开关
中除最后一个子句之外的所有子句的最后一个语句出现。
switch 表达式
前面可以有一个简单的语句,它在表达式被求值之前执行。
|
|
实现限制:编译器可能不允许多个case 表达式求值为同一个常量。例如,目前的编译器不允许在case表达式中出现重复的整型常量、浮点常量或字符串常量。
Type switches 类型选择
类型选择
比较的是类型
而不是值。它与表达式开关
类似。它由一个特殊的switch 表达式
标记,该表达式具有类型断言的形式,使用关键字type
而不是实际的类型:
|
|
然后,case 将实际类型T
与表达式x
的动态类型相匹配。与类型断言一样,x
必须是接口类型,但不是类型参数,而且case 中列出的每个非接口类型T
必须实现x
的类型。在类型选择的case 中,列出的类型都必须是不同的。
|
|
TypeSwitchGuard 可能包括一个短变量声明。当使用这种形式时,该变量在每个子句的隐含块中的TypeSwitchCase的末尾被声明。在子句中,如果case正好列出了一种类型,那么变量就有这种类型;否则,变量就有TypeSwitchGuard中表达式的类型。
case 可以使用预先声明的标识符nil来代替类型;当TypeSwitchGuard中的表达式是一个nil
接口值时,该case被选中。最多只能有一个nil
case。
给定一个interface{}
类型的表达式x
,下面的类型选择:
|
|
可以重写:
|
|
类型参数或泛型可以作为 case 中的一个类型。如果在实例化时,该类型被发现与开关中的另一个条目重复,则选择第一个匹配的case。
|
|
类型选择防护(guard )前可以有一个简单的语句,该语句在防护(guard )被求值前执行。
在类型选择中不允许使用
“fallthrough “语句。
For statements - for 语句
“for
“语句指定重复执行一个块。有三种形式:迭代可以由单个条件、“for “子句或 “range “子句控制。
ForStmt = "for" [ Condition | ForClause | RangeClause ] Block .
Condition = Expression .
For statements with single condition 带有单一条件的for语句
在其最简单的形式中,“for “语句指定重复执行一个块,只要一个布尔条件被求值为真。该条件在每次迭代前被求值。如果条件不存在,它就等同于布尔值true
。
|
|
For statements with for
clause 带有for子句的for语句
带有for子句的 “for “语句也受其条件的控制,但另外它可以指定一个init
和一个post
语句,如一个赋值,一个增量或减量语句。init语句可以是一个短变量声明,但post语句则不能。由init语句声明的变量会在每次迭代中重复使用。
ForClause = [ InitStmt ] ";" [ Condition ] ";" [ PostStmt ] .
InitStmt = SimpleStmt .
PostStmt = SimpleStmt .
for i := 0; i < 10; i++ {
f(i)
}
如果(init语句)非空,则它在求值第一个迭代的条件之前被执行一次;post语句在每次执行块之后被执行(而且只有当该块被执行时)。For子句的任何元素都可以是空的,但是(除非只有一个条件)分号是必须的。如果没有条件,则等同于布尔值true
。=> 仍有疑问??
for cond { S() } is the same as for ; cond ; { S() }
for { S() } is the same as for true { S() }
For statements with range
clause 带有range子句的for语句
带有 “range
“子句的 “for “语句遍历一个数组、切片、字符串或映射的所有条目,或在一个通道上收到的值。对于每个条目,如果存在的话,它将迭代值分配给相应的迭代变量,然后执行该块。
RangeClause = [ ExpressionList "=" | IdentifierList ":=" ] "range" Expression .
在 “range
“子句中右边的表达式
称为范围表达式
,其核心类型必须是数组、指向数组的指针、切片、字符串、映射或允许接收操作的通道。和赋值一样,如果左操作数存在的话,那么它必须是可寻址的或映射索引表达式;它们(即左操作数)表示迭代变量
。如果range 表达式
是一个通道,最多允许一个迭代变量,其他情况下最多可以有两个迭代变量。如果最后一个迭代变量是空白标识符,即_
,那么range 子句就等同于没有该空白标识符的相同子句。
在开始循环之前,range 表达式x
被求值一次,但有一个例外:如果最多只有一个迭代变量,并且len(x)
是常量,那么range 表达式不被求值。
Function calls on the left are evaluated once per iteration. For each iteration, iteration values are produced as follows if the respective iteration variables are present:
左边的函数调用在每个迭代中被求值一次(=>仍有疑问??)。对于每个迭代,如果各自的迭代变量存在,则迭代值按以下产生:
Range expression 1st value 2nd value
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
- 对于一个数组值、数组指针值或切片值
a
,索引迭代值按递增顺序产生,从元素索引0开始。如果最多只有一个迭代变量,range 循环产生从0
到len(a)-1
的迭代值,并且不对数组或切片本身进行索引。对于一个nil
切片,迭代次数为0。 - 对于一个字符串值,“range “子句在字符串中的Unicode码点上进行迭代,从字节索引0开始。在连续的迭代中,索引值将是字符串中连续的UTF-8编码码点的第一个字节的索引,第二个值,类型为
rune
,将是相应码点的值。如果迭代遇到一个无效的UTF-8序列
,第二个值将是(Unicode替换字符)0xFFFD
,下一次迭代将在字符串中推进一个字节。 - 对映射的迭代顺序没有指定,不保证每次迭代都是一样的。如果在迭代过程中删除了一个尚未到达的映射条目,将不会产生相应的迭代值。如果在迭代过程中创建了一个映射条目,该条目可能在迭代过程中被产生,也可能被跳过。对于每个创建的条目,以及从一个迭代到另一个迭代,选择可能有所不同。如果映射为
nil
,迭代次数为0。 - 对于通道,产生的迭代值是通道上连续发送的值,直到通道关闭。如果通道为
nil
,则 range 表达式永远阻塞。
迭代值被分配给各自的迭代变量,就像在赋值语句中一样。
迭代变量可以由 “range “子句使用短变量声明的形式(:=
)来声明。在这种情况下,它们的类型被设置为各自的迭代值的类型,它们的作用域是 “for “语句的块;它们在每个迭代中被重复使用。如果迭代变量是在 “for “语句之外声明的,执行后它们的值将是最后一次迭代的值。
|
|
Go statements - go 语句
go
语句作为一个独立的并发控制线程(或称为goroutine),在同一地址空间内
开始执行一个函数调用。
GoStmt = "go" Expression .
(go语句中的)表达式必须是一个函数或方法调用
;其不能用圆括号括起来。对内置函数的调用与表达式语句一样受到限制。
函数值和参数在调用的goroutine中像往常一样被求值,但与普通调用不同的是,程序执行不会等待被调用的函数完成。相反,该函数开始在一个新的goroutine中独立执行。当函数终止时,其goroutine也会终止。如果该函数有任何返回值,当函数完成时,它们会被丢弃。
|
|
Select statements - select 语句
“select
“语句选择一组可能的发送或接收操作中的一个来进行。它看起来类似于 “switch “语句,但其 case 都是只涉及通信操作。
SelectStmt = "select" "{" { CommClause } "}" .
CommClause = CommCase ":" StatementList .
CommCase = "case" ( SendStmt | RecvStmt ) | "default" .
RecvStmt = [ ExpressionList "=" | IdentifierList ":=" ] RecvExpr .
RecvExpr = Expression .
一个有 RecvStmt 的 case 可以将 RecvExpr 的结果分配给一个或两个变量,这些变量可以用短变量声明来声明。RecvExpr 必须是一个(可能是圆括号内的)接收操作。最多可以有一个default case,它可以出现在 case 列表的任何地方。
“select
“语句的执行分几个步骤进行:
- 对于语句中的所有情况,在进入 “
select
“语句时,接收操作的通道操作数、通道、发送语句的右侧表达式(按源代码出现的顺序)被求值一次。结果是一组要接收或发送的通道,以及对应的要发送的值。无论选择哪一个(如果有)通信操作来进行,在这次求值中的任何副作用都会发生。在 RecvStmt 左侧的表达式有一个短变量声明或赋值,还没有被求值。 - 如果一个或多个通信可以进行,则通过统一的伪随机选择来选择一个可以进行的通信。否则,如果有一个default case,就会选择该情况。如果没有default case,"
select
“语句就会阻塞,直到至少有一项通信可以进行。 - 除非选择的情况是default case,否则将执行相应的通信操作。
- 如果选择的 case 是一个带有短变量声明或赋值的RecvStmt,左边的表达式会被求值,接收到的值(或多个值)被用于赋值。
- 所选 case 语句列表被执行。
由于在nil
通道上的通信永远不能进行,所以只有nil
通道且没有default case 的select
语句会永远阻塞。
|
|
Return statements - return 语句
函数F
中的 “return
“语句终止了F
的执行,并且可以选择提供一个或多个结果值。在F
返回给它的调用者之前,任何由F
延迟的函数都会被执行。
ReturnStmt = "return" [ ExpressionList ] .
在没有结果类型的函数中,"return
“语句必须不指定任何结果值。
|
|
有三种方法可以从一个有结果类型的函数中返回值:
可以在 “
return
“语句中明确列出一个或多个返回值。每个表达式必须是单值的,并且可以分配给函数的结果类型的相应元素。1 2 3 4 5 6 7
func simpleF() int { return 2 } func complexF1() (re float64, im float64) { return -7.0, -4.0 }
“
return
“语句中的表达式列表可能是对一个多值函数的单一调用。其效果就像从该函数返回的每个值都被分配到一个临时变量中,其类型为相应的值,随后的 “return
“语句列出了这些变量,此时,前一种情况的规则适用。1 2 3
func complexF2() (re float64, im float64) { return complexF1() }
如果函数的结果类型为其结果参数指定了名称,表达式列表可能是空的。结果参数作为普通的局部变量,函数可以根据需要给它们赋值。
return
语句会返回这些变量的值。1 2 3 4 5 6 7 8 9 10
func complexF3() (re float64, im float64) { re = 7.0 im = 4.0 return } func (devnull) Write(p []byte) (n int, _ error) { n = len(p) return }
不管它们是如何被声明的,所有的结果值在进入函数时都被初始化为
其类型的零值。指定结果的 “return
“语句在执行任何延迟函数之前
设置结果参数。
实现限制:如果在返回的地方有一个与结果参数同名的不同实体(常量、类型或变量)在作用域内,编译器可能不允许在 “return
“语句中出现空表达式列表。
|
|
Break statements - break 语句
“break
“语句可以终止同一函数中最里面的 “for"、"switch “或 “select “语句的执行。
BreakStmt = "break" [ Label ] .
如果有一个标签,它必须是一个封闭的 “for
"、"switch
“或 “select
“语句的标签,而且是执行终止的那一个。
|
|
Continue statements - continue 语句
“continue
“语句通过将控制推进到循环块的末端来开始最内层的 “for “循环的下一次迭代。"for
“循环必须是在同一个函数中。
ContinueStmt = "continue" [ Label ] .
如果有一个标签,它必须是一个封闭的 “for
“语句的标签,而且是执行前进的那一个。
|
|
Goto statements 语句 goto
“goto
“语句将控制转移到同一函数中具有相应标签的语句。
GotoStmt = "goto" Label .
goto Error
执行 “goto
“语句不能导致任何变量进入goto处的作用域之外的作用域。例如,这个例子:
|
|
是错误的,因为跳转到标签L
时,跳过了创建v
的过程。
块外的 “goto
“语句不能跳到该块内的标签。例如,这个例子:
|
|
是错误的,因为标签L1
在 “for “语句的块内,而goto
不在其中。
Fallthrough statements 语句 fallthrough
在表达式开关语句中,"fallthrough
“语句将控制转移到下一个case
子句的第一个语句。它只能作为这种子句中的最后一个非空语句使用。
FallthroughStmt = "fallthrough" .
Defer statements 语句 defer
“defer
“语句调用一个函数,该函数的执行被推迟到外层函数返回的那一刻,或者是因为外层函数执行了 return 语句,达到了其函数体的末端,或者是因为相应的goroutine正在恐慌。
DeferStmt = "defer" Expression .
这个表达式必须是一个函数或方法的调用;它不能是被圆括号括起来的。对内置函数的调用与表达式语句一样受到限制。
每次执行 “defer
“语句时,函数值和调用的参数像往常一样被求值并重新保存,但实际的函数不会被调用。相反,被延迟函数
在外层的函数返回之前立即被调用,其顺序与它们被延迟的顺序相反
。也就是说,如果外层的函数通过一个明确的 return 语句返回,被延迟函数
在任何结果参数被该 reurn 语句
设置后执行,但在外层函数返回给其调用者之前
。如果被延迟函数
值被求值为nil
,那么在调用该被延迟函数时会出现执行恐慌(而不是当 “defer
“语句被执行时)。
例如,如果被延迟函数
是一个函数字面量,并且外层的函数有在该字面量的作用域内的命名结果参数,那么该被延迟函数
可以在这些结果参数被返回之前访问和修改它们。如果被延迟函数
有任何返回值,这些返回值将在函数完成时被丢弃。(参见处理恐慌一节)。
|
|