这是本节的多页打印视图。 点击此处打印.

返回本页常规视图.

声明

Golang 声明

1 - Block

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 声明

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中,函数参数和返回值的作用域与函数主体相同,尽管它们在词法上出现在包围主体的括号之外。