1 - Block
备注:摘录自 Golang语言规范 https://golang.org/ref/spec#Blocks
Blocks
Block/块是在匹配的大括号内的声明和语句序列(可能为空):
Block = "{" StatementList "}" .
StatementList = { Statement ";" } .
除了源码中的显性块,还有隐性块:
- universe block (宇宙块)包含了所有的go源代码文本。
- 每个包都有一个 package block (包块),包含该包的所有go源代码文本。
- 每个文件都有一个 file block (文件块),包含该文件中的所有go源代码文本。
- 每个 “if”、“for “和 “switch “语句都被认为是在自己的隐含块中。
- 在 “switch “或 “select “语句中的每个子句都作为一个隐式块。
块可以嵌套,会影响scope/范围。
2 - 常量
Constant declaration
https://golang.org/ref/spec#Constant_declarations
常量声明将标识符列表(常量的名称)与常量表达式列表的值绑定。标识符的数量必须等于表达式的数量,左边的第n个标识符与右边的第n个表达式的值绑定。
ConstDecl = "const" ( ConstSpec | "(" { ConstSpec ";" } ")" ) .
ConstSpec = IdentifierList [ [ Type ] "=" ExpressionList ] .
IdentifierList = identifier { "," identifier } .
ExpressionList = Expression { "," Expression } .
如果存在类型,所有常量都采用指定的类型,并且表达式对于该类型必须是 assignable /可分配的。如果类型被省略,则常量取对应表达式各自的类型。如果表达式的值是无类型的常量,则声明的常量保持无类型,而常量标识符表示常量值。例如,如果表达式是浮点字面量,常量标识符表示浮点常量,即使字面量的小数部分为零。
const Pi float64 = 3.14159265358979323846
const zero = 0.0 // untyped floating-point constant
const (
size int64 = 1024
eof = -1 // untyped integer constant
)
const a, b, c = 3, 4, "foo" // a = 3, b = 4, c = "foo", untyped integer and string constants
const u, v float32 = 0, 3 // u = 0.0, v = 3.0
在带括号的const声明列表中,除了第一个ConstSpec之外,表达式列表可以省略。这样的空列表相当于前面第一个非空的表达式列表及其类型(如果有的话)的文本替换。因此,省略表达式列表相当于重复前面的列表。标识符的数量必须等于前一个列表中表达式的数量。与iota常量生成器一起,这种机制允许轻量级的顺序值声明。
const (
Sunday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Partyday
numberOfDays // this constant is not exported
)
3 - 变量
Variable declaration
https://golang.org/ref/spec#Variable_declarations
变量声明创建一个或多个变量,将相应的标识符绑定到它们上面,并给每个变量一个类型和初始值。
VarDecl = "var" ( VarSpec | "(" { VarSpec ";" } ")" ) .
VarSpec = IdentifierList ( Type [ "=" ExpressionList ] | "=" ExpressionList ) .
var i int
var U, V, W float64
var k = 0
var x, y float32 = -1, -2
var (
i int
u, v, s = 2.0, 3.0, "bar"
)
var re, im = complexSqrt(-1)
var _, found = entries[name] // map lookup; only interested in "found"
如果给定了一个表达式列表,则按照赋值规则用表达式初始化变量。否则,每个变量初始化为零值。
如果存在类型,每个变量被赋予该类型。否则,每个变量被赋予赋值中相应初始化值的类型。如果该值是一个无类型的常量,则首先隐式转换为其默认类型;如果是一个无类型的布尔值,则首先隐式转换为类型bool。预先声明的值nil不能用于初始化一个没有显式类型的变量。
var d = math.Sin(0.5) // d is float64
var i = 42 // i is int
var t, ok = x.(T) // t is T, ok is bool
var n = nil // illegal
实现限制:如果一个变量从未被使用,编译器可能会规定在函数体中声明一个变量是非法的。
短变量声明
短变量声明使用这样的语法:
ShortVarDecl = IdentifierList ":=" ExpressionList .
它是有初始化表达式但没有类型的正则变量声明的简写:
"var" IdentifierList = ExpressionList .
i, j := 0, 10
f := func() int { return 7 }
ch := make(chan int)
r, w, _ := os.Pipe() // os.Pipe() returns a connected pair of Files and an error, if any
_, y, _ := coord(p) // coord() returns three values; only interested in y coordinate
与普通变量声明不同,短变量声明可以重新声明变量,但前提是这些变量原来在同一个块(如果块是函数体,则在参数列表中)中早先声明过,类型相同,而且至少有一个非空变量是新的。因此,重声明只能出现在多变量的短声明中。重新声明并不引入一个新的变量,它只是给原来的变量分配一个新的值。
field1, offset := nextField(str, 0)
field2, offset := nextField(str, offset) // redeclares offset
a, a := 1, 2 // illegal: double declaration of a or no new variable if a was declared elsewhere
短变量声明只能出现在函数内部。在某些情况下,例如 “if”、“for “或 “switch “语句的初始化器,它们可以用来声明本地临时变量。
备注:详见 “重新声明” 一节
4 - iota
iota
https://golang.org/ref/spec#Iota
在常量声明中,预先声明的标识符 iota 代表连续的非类型整数常量。它的值是该常量声明中各自ConstSpec的索引,从0开始。它可以用来构造一组相关的常量。
const (
c0 = iota // c0 == 0
c1 = iota // c1 == 1
c2 = iota // c2 == 2
)
const (
a = 1 << iota // a == 1 (iota == 0)
b = 1 << iota // b == 2 (iota == 1)
c = 3 // c == 3 (iota == 2, unused)
d = 1 << iota // d == 8 (iota == 3)
)
const (
u = iota * 42 // u == 0 (untyped integer constant)
v float64 = iota * 42 // v == 42.0 (float64 constant)
w = iota * 42 // w == 84 (untyped integer constant)
)
const x = iota // x == 0
const y = iota // y == 0
根据定义,iota在同一个ConstSpec中的多次使用都具有相同的值:
const (
bit0, mask0 = 1 << iota, 1<<iota - 1 // bit0 == 1, mask0 == 0 (iota == 0)
bit1, mask1 // bit1 == 2, mask1 == 1 (iota == 1)
_, _ // (iota == 2, unused)
bit3, mask3 // bit3 == 8, mask3 == 7 (iota == 3)
)
最后一个例子利用了最后一个非空表达式列表的隐式重复。
5 - 函数
Function declaration
https://golang.org/ref/spec#Function_declarations
函数声明将标识符,即函数名,与函数绑定。
FunctionDecl = "func" FunctionName Signature [ FunctionBody ] .
FunctionName = identifier .
FunctionBody = Block .
如果函数的签名声明了结果参数,那么函数体的语句列表必须以终止语句结束。
func IndexRune(s string, r rune) int {
for i, c := range s {
if c == r {
return i
}
}
// invalid: missing return statement
}
函数声明可以省略主体。这样的声明提供了在Go之外实现的函数的签名,例如汇编例程。
func min(x int, y int) int {
if x < y {
return x
}
return y
}
func flushICache(begin, end uintptr) // implemented externally
6 - 类型
Type declaration
https://golang.org/ref/spec#Type_declarations
类型声明将标识符,即类型名称,绑定到类型上。类型声明有两种形式:别名声明和类型定义。
TypeDecl = "type" ( TypeSpec | "(" { TypeSpec ";" } ")" ) .
TypeSpec = AliasDecl | TypeDef .
Alias declarations
别名声明将标识符绑定到给定的类型上:
AliasDecl = identifier "=" Type .
在标识符的范围内,它作为类型的别称:
type (
nodeList = []*Node // nodeList and []*Node are identical types
Polar = polar // Polar and polar denote identical types
)
Type definitions
类型定义创建了一个新的、独特的类型,它与给定的类型具有相同的底层类型和操作,并为它绑定了一个标识符。
TypeDef = identifier Type .
新类型被称为 defined type / 定义类型。它不同于任何其他类型,包括它创建时来源的类型。
type (
Point struct{ x, y float64 } // Point and struct{ x, y float64 } are different types
polar Point // polar and Point denote different types
)
type TreeNode struct {
left, right *TreeNode
value *Comparable
}
type Block interface {
BlockSize() int
Encrypt(src, dst []byte)
Decrypt(src, dst []byte)
}
defined type / 定义类型可以有与之相关的方法。它不继承任何与给定类型绑定的方法,但接口类型的方法集或复合类型的元素保持不变。
// A Mutex is a data type with two methods, Lock and Unlock.
type Mutex struct { /* Mutex fields */ }
func (m *Mutex) Lock() { /* Lock implementation */ }
func (m *Mutex) Unlock() { /* Unlock implementation */ }
// NewMutex has the same composition as Mutex but its method set is empty.
type NewMutex Mutex
// The method set of PtrMutex's underlying type *Mutex remains unchanged,
// but the method set of PtrMutex is empty.
type PtrMutex *Mutex
// The method set of *PrintableMutex contains the methods
// Lock and Unlock bound to its embedded field Mutex.
type PrintableMutex struct {
Mutex
}
// MyBlock is an interface type that has the same method set as Block.
type MyBlock Block
类型定义可以用来定义不同的布尔、数值或字符串类型,并与它们相关联的方法:
type TimeZone int
const (
EST TimeZone = -(5 + iota)
CST
MST
PST
)
func (tz TimeZone) String() string {
return fmt.Sprintf("GMT%+dh", tz)
}
7 - 方法
Method declaration
https://golang.org/ref/spec#Method_declarations
方法是一个带有 receiver (接收者)的函数。方法声明将标识符,即方法名,绑定到一个方法上,并将该方法与接受者的基本类型联系起来。
MethodDecl = "func" Receiver MethodName Signature [ FunctionBody ] .
Receiver = Parameters .
接收者是通过方法名前的额外参数部分指定的。这个参数部分必须声明一个非变量参数,即 receiver(接收者)。它的类型必须是一个 defined type(定义类型)T或者一个指向定义类型T的指针。T被称为接收者的基本类型。接收者基类类型不能是指针或接口类型,它必须与方法定义在同一个包中。该方法被称为绑定到它的接收者基本类型上,并且该方法名称只有在类型T或*T的选择器中才可见。
非空白的接收者标识符必须在方法签名中是唯一的。如果接收者的值没有在方法主体中引用,那么它的标识符可以在声明中省略。一般来说,这也适用于函数和方法的参数。
对于基础类型来说,与它绑定的方法的非空名称必须是唯一的。如果基础类型是结构体类型,则非空的方法和字段名必须是不同的。
给定定义类型Point,声明:
func (p *Point) Length() float64 {
return math.Sqrt(p.x * p.x + p.y * p.y)
}
func (p *Point) Scale(factor float64) {
p.x *= factor
p.y *= factor
}
将方法 Length 和 Scale(接收者类型为*Point)绑定到基本类型Point上。
方法的类型是以接收者为第一参数的函数类型。例如,方法Scale的类型是:
func(p *Point, factor float64)
当然,这样声明的函数不是方法。
备注:可以参考文章 函数——go世界中的一等公民 中的 “方法的本质” 一节
“go里面其实方法就是语法糖,实际上Method就是将receiver作为函数的第一个参数输入的语法糖而已,本质上和函数没有区别”
8 - 重新声明
Redeclaration and reassignment
https://golang.org/doc/effective_go.html#redeclaration
f, err := os.Open(name)
if err != nil {
return err
}
d, err := f.Stat()
if err != nil {
f.Close()
return err
}
codeUsing(f, d)
旁白一下。上一节的最后一个例子演示了 := 短声明形式的工作细节。调用os.Open的声明如下。
f, err := os.Open(name)
这条语句声明了两个变量,f和err。几行之后,对f.Stat的调用为:
d, err := f.Stat()
这看起来就像它声明了d和err。但请注意,两个语句中都出现了err。这种重复是合法的:err 是由第一条语句声明的,但只是在第二条语句中重新赋值。这意味着对 f.Stat 的调用使用了上面声明的现有err变量,只是给它一个新的值。
在 := 声明中,即使已经声明了一个变量 v,也可以出现,但前提是:
- 这个声明与v的现有声明在同一个作用域中(如果v已经在外部作用域中声明了,则声明将创建一个新的变量§)。
- 初始化中的相应值可分配给v,并且
- 至少还有一个变量是由声明创建的。
这个不寻常的属性是纯粹的实用主义,使得它很容易使用一个单一的err值,例如,在一个长的if-else链中。你会看到它经常被使用。
§ 这里值得注意的是,在Go中,函数参数和返回值的作用域与函数主体相同,尽管它们在词法上出现在包围主体的括号之外。