00 目录

01 Introduction

  • Introduction - 官方文档
  • 这篇官方文档是参考手册。go是专为系统编程而设计。它是强类型、垃圾回收并明确支持并发编程。从包开始构建程序,这个特性让管理依赖非常高效。

02 符号

Production = production_name “=” [ Expression ] “.” .

Expression = Alternative { “|” Alternative } .

Alternative = Term { Term } .

Term = production_name | token [ “…” token ] | Group | Option | Repetition .

Group = “(” Expression “)” .

Option = “[” Expression “]” .

Repetition = “{” Expression “}” .

  Productions是由术语(terms)和以下运算符(operators)构成的表达式,优先级越来越高:

| alternation

() grouping

[] option (0 or 1 times)

{} repetition (0 to n times)

  小写名称的产生被用来标识词汇,非终端使用驼峰,词汇象征在双引号”“或者双后引号``。

  a … b这种形式这一组字符,从a至b作为替代。水平省略号…同样被用于在规范中非正式地表示各种枚举或者代未进一步指定的代码段。这个字符串…不是go语言的一个令牌。

03 源码表示

  • Source code representation - 官方文档
  • 源码文本使用UTF-8编码,该文本不是规范化的,因此单个带重音的代码点与通过组合重音和字母构造的相同字符不同;这些被视为两个代码点。为了简单起见,文本使用非限定术语字符来引用源码文本中的Unicode代码。

  每个代码点是不同的;比如,大小写字符是不同的字符。

  实现限制:为了和其他工具兼容,编译器也许会允许空字符(U+0000)在源码中。

  实现限制:为了和其他工具兼容,如果它在源码文本中是第一个Unicode代码点,编译器也许会忽略utf-8编码字节顺序标记 (U+FEFF) 。源中的任何其他位置都可能不允许使用字节顺序标记。

3.1 字符串

1
2
3
4
newline        = /* the Unicode code point U+000A */ .
unicode_char   = /* an arbitrary Unicode code point except newline */ .
unicode_letter = /* a Unicode code point classified as "Letter" */ .
unicode_digit  = /* a Unicode code point classified as "Number, decimal digit" */ .

  在Unicode标准8.0中,第4.5节“常规类别”定义了一组字符类别。 Go将任何字母类别Lu,Ll,Lt,Lm或Lo中的所有字符视为Unicode字母,将数字类别Nd中的字符视为Unicode数字。

3.2 字母和数字

1
2
3
4
letter        = unicode_letter | "_" .
decimal_digit = "0"  "9" .
octal_digit   = "0"  "7" .
hex_digit     = "0"  "9" | "A"  "F" | "a"  "f" .

04 词汇元素

4.01 注释

  • Comments - 官方文档
  • 注释作为程序文档服务。它有两种形式:
    • 单行注释,以//字符开始,结束与行尾
    • 多行注释,以/*字符串开始,以*/字符串结束。

  注释不能在符文或字符串字面量内部或注释内部开始。不包含换行符的常规注释就像空格一样。 任何其他评论都像换行符。

4.02 令牌

  • Tokens - 官方文档
  • 令牌构成了Go语言的词汇。
    • 有四个类:标识符(identifiers),关键字(keywords),运算符(operators)和标点符号(punctuation)以及文字(literals)。
    • 由空格(U + 0020),水平制表符(U + 0009),回车符(carriage returns)(U + 000D)和换行符(U + 000A)组成的空格将被忽略,除非它将标记分开,否则它们将组合成单个标记令牌。在将输入分解为标记时,下一个标记是形成有效标记的最长字符序列。

4.03 分号

  • Semicolons - 官方文档
  • 常见语法使用分号”;“作为语句的终结者。Go程序依照以下规则可以删除大部分的分号。
    • 当输入被分解为令牌时,如果该令牌是,则在行的最终令牌之后立即自动将分号插入到令牌流中
    • 一个标识符(identifier)
    • 一个 integer, floating-point, imaginary, rune, or string 字面量
    • 一个关键字:break, continue, fallthrough, or return
    • 一个操作符或标点 ++, –, ), ], or }
    • 为了允许复杂语句占用一行,在结束“)”或“}”之前可以省略分号。

  为了反映惯用语,本文档中的代码示例使用这些规则省略分号。

4.04 标识符

  • Identifiers - 官方文档
  • 标识符命名程序实体,比如变量和类型。标识符是一个或多个字母和数字的序列,标识符第一个字母必须是字母。

identifier = letter { letter | unicode_digit } .

一些标识符是预先声明的。

4.05 关键字

1
2
3
4
5
break        default      func         interface    select
case         defer        go           map          struct
chan         else         goto         package      switch
const        fallthrough  if           range        type
continue     for          import       return       var

4.06 操作符和标点

1
2
3
4
5
6
+    &     +=    &=     &&    ==    !=    (    )
-    |     -=    |=     ||    <     <=    [    ]
*    ^     *=    ^=     <-    >     >=    {    }
/    <<    /=    <<=    ++    =     :=    ,    ;
%    >>    %=    >>=    --    !     ...   .    :
     &^          &^= 

4.07 数字

  • Integer literals - 官方文档
  • 整数文字是表示整数常量的数字序列。 可选前缀设置非十进制基数:0表示八进制,0x或0表示十六进制。 在十六进制文字中,字母a-f和A-F表示值10到15。
1
2
3
4
5
6
7
8
9
int_lit     = decimal_lit | octal_lit | hex_lit .
decimal_lit = ( "1"  "9" ) { decimal_digit } .
octal_lit   = "0" { octal_digit } .
hex_lit     = "0" ( "x" | "X" ) hex_digit { hex_digit } .

// 42
// 0600
// 0xBadFace
// 170141183460469231731687303715884105727

4.08 浮点数

  • Floating-point literals - 官方文档
  • 浮点文字是浮点常量的十进制表示。 它有一个整数部分,一个小数点,一个小数部分和一个指数部分。 整数和小数部分包括十进制数字; 指数部分是e或E,后跟可选的带符号十进制指数。 可以省略整数部分或小数部分中的一个; 可以省略小数点或指数之一。 0. 72.40 072.40 // == 72.40 2.71828 1.e+0 6.67428e-11 1E6 .25 .12345E+5

4.09 虚数

  • Imaginary literals - 官方文档
  • 虚数是复数常数的虚部的十进制表示。 它由浮点数或十进制整数后跟小写字母i组成。 0i 011i // == 11i 0.i 2.71828i 1.e+0i 6.67428e-11i 1E6i .25i .12345E+5i

4.10 符文

  • Rune - literals
  • rune 等同于int32,常用来处理unicode或utf-8字符;byte 等同于int8,常用来处理ascii字符

4.11 字符串字面量

  • String literals - 官方文档
  • 两种形式:后引号和双引号。双引号除了换行和未转移的双引号,其他都是被显示。而后引号除了自己之外所有的字符都会被显示。

05 常量

  • Constants - 官方文档
  • 数值常量:Rune, integer, floating-point, and complex constants
  • 常量表达式:具有常量结果的转换,或某些内置函数的结果值,例如应用于任何值的unsafe.Sizeof
  • 预声明:bolean、iota
  • 短变量声明没有给出明确的类型,所以是无类型常量

  常量使用限制事项:

  • 表示至少256位的整数常量。
  • 表示浮点常数,包括复数常量的部分,尾数至少为256位,有符号二进制指数至少为16位。
  • 如果无法精确表示整数常量,则给出错误。
  • 如果由于溢出而无法表示浮点或复数常量,则给出错误。
  • 如果由于精度限制而无法表示浮点或复数常量,则舍入到最接近的可表示常量。

06 变量

07 类型

  • Types - 官方文档
  • 类型声明:将一个类型绑定到一个Name上。它包括两种,别名声明和类型定义。
    • 别名声明:
1
2
3
4
type (
  nodeList = []*Node  // nodeList and []*Node are identical types
  Polar    = polar    // Polar and polar denote identical types
)
  • 类型定义:类型定义创建一个新的,不同的类型,其具有与给定类型相同的基础类型和操作,并将标识符绑定到它;已定义的类型可能具有与之关联的方法,它不继承绑定到给定类型的任何方法,但接口类型的方法集或复合类型的元素保持不变:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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)
}

// 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

7.01 方法集

  • Method sets - 官方文档
  • 类型关联着方法集。任何类型都有一个空方法集,方法集中每个方法具有唯一的非空方法名称。类的方法集确定类型实现的接口和可以使用该接收器的调用方法。

7.02 布尔类型

7.03 数值类型

  • Numeric types - 官方文档
  • 数值类型是预先声明的集合,它的值包括interger、floating-point、复数、byte、rune。为了避免可移植性问题,所有数值类型都是类型定义,除了byte,byte是uint8的别名;还有rune,它是uint32的别名。在表达式或赋值中混合使用不同的数字类型时,需要显式转换。 例如,int32和int的类型不同,即使它们在特定体系结构上可能具有相同的大小。

7.04 字符串类型

  • String types - 官方文档
  • 字符串值是(可能为空)字节序列。字符串是不可改变的,一旦创建它就不可能改变其内容。预声明类型为stirng,它属于类型定义。字符串通过index来获取对应的字节s[i],但通过&s[i]来获取地址是非法的。

7.05 数组类型

  • Array types - 官方文档
  • 长度是数组的的一部分,所有数组都是明确了长度的。内置方法len来获取数组长度,可以通过下标来获取元素地址。数组类型一般是一维的,但也可以是多维的。

7.06 切片类型

  • Slice types - 官方文档
  • 切片是底层数组的连续段的描述符,并提供对该数组中编号的元素序列的访问。切片s的长度可以通过内置函数len发现; 与数组不同,它可能在执行期间发生变化 元素可以通过整数索引0到len(s)-1来寻址。 给定元素的切片索引可能小于底层数组中相同元素的索引。
  • 切片的容量用内置方法cap来获取。
  • 通过内置方法make来创建和初始化切片,该方法指定切片类型和长度,以及可选的初始化容量。而分配返回的隐藏数组。
1
2
3
4
5
make([]T, length, capacity)

make([]int, 50, 100)
// 上面一个语句和下面的语句是等价的
new([100]int)[0:50]
  • 和数组一样,一般切片是一维切片。对于切片阵列,内部长度可以动态变化。 而且,内部切片必须单独初始化。

7.07 结构体类型

  • Struct types - 官方文档
  • 结构体有一组命名的元素,它被称为fields,每个都有一个名称和一个类型。字段名称可以显示指定(IdentifierList)或者隐式指定(EmbeddedField)。在结构中,非空字段名称必须是唯一的。
  • 一个field声明没有明确名称叫做嵌入式filed。一个嵌入式field必须具体的类型,比如T;或者指向一个非interface类型*T的指针,并且T本身类型不能是指针类型。非限定类型名称充当字段名称。
1
2
3
4
5
6
7
8
// A struct with four embedded fields of types T1, *T2, P.T3 and *P.T4
struct {
	T1        // field name is T1
	*T2       // field name is T2
	P.T3      // field name is T3
	*P.T4     // field name is T4
	x, y int  // field names are x and y
}
  • 下面的类型是非法的,因为field名称必须是唯一的
1
2
3
4
5
struct {
	T     // conflicts with embedded field *T and *P.T
	*T    // conflicts with embedded field T and *P.T
	*P.T  // conflicts with embedded field T and *T
}
  • 内嵌field:S类型内嵌定义类型T,扩展方法集合情况如下
    • 如果S包含内嵌field T,S或*S的方法集合接收器T的扩展方法,*S的方法集合还包括*T扩展的方法。
    • 如果S包含内嵌field *T,S或*S的方法集合包括接收器T或*T的扩展方法。

  字段声明后面可以跟一个可选的字符串文字标记,该标记成为相应字段声明中所有字段的属性。 空标记字符串等同于缺少标记。 标签通过反射界面可见,并参与结构的类型标识,否则将被忽略。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
struct {
	x, y float64 ""  // an empty tag string is like an absent tag
	name string  "any string is permitted as a tag"
	_    [4]byte "ceci n'est pas un champ de structure"
}

// A struct corresponding to a TimeStamp protocol buffer.
// The tag strings define the protocol buffer field numbers;
// they follow the convention outlined by the reflect package.
struct {
	microsec  uint64 `protobuf:"1"`
	serverIP6 uint64 `protobuf:"2"`
}

7.08 指针类型

  • Pointer types - 官方文档
  • 指针类型表示指向给定类型的变量的所有指针的集合,称为指针的基本类型。 未初始化指针的值为nil。
1
2
*Point
*[4]int

7.09 方法类型

  • Function types - 官方文档
  • 方法类型表示一组具有相同参数和返回结果的方法集合。
  • 名称(IdentifierList)必须全部存在或全部不存在。
    • 如果存在,则每个名称代表指定类型的一个项目(参数或结果),并且签名中的所有非空白名称必须是唯一的。
    • 如果不存在,则每种类型代表该类型的一个项目。
  • 参数和结果列表始终带括号,但如果只有一个未命名的结果,则可以将其写为未加括号的类型。
  • 函数签名中的最后传入参数可以具有前缀为的类型….具有这样的参数的函数称为可变参数,并且可以使用该参数的零个或多个参数来调用。
1
2
3
4
5
6
7
8
func()
func(x int) int
func(a, _ int, z float32) bool
func(a, b int, z float32) (bool)
func(prefix string, values ...int)
func(a, b int, z float64, opt ...interface{}) (success bool)
func(int, int, float64) (float64, *[]int)
func(n int) func(p *T)

7.10 接口类型

  • Interface types - 官方文档
  • 接口类型指定称为其接口的方法集。
  • 接口类型的变量可以使用方法集存储任何类型的值,该方法集是接口的任何超集。这种类型就实现了接口。
  • 接口类型的未初始化变量的值为nil。
  • 与所有方法集一样,在接口类型中,每个方法都必须具有唯一的非空名称。
1
2
3
4
InterfaceType      = "interface" "{" { MethodSpec ";" } "}" .
MethodSpec         = MethodName Signature | InterfaceTypeName .
MethodName         = identifier .
InterfaceTypeName  = TypeName .
1
2
3
4
5
6
// A simple File interface
interface {
	Read(b Buffer) bool
	Write(b Buffer) bool
	Close()
}
  • 所有的类型都实现了一个接口interface{}
  • 接口T可以使用(可能合格的)接口类型名称E来代替方法规范。这在T中称为嵌入接口E; 它将E的所有(导出和非导出)方法添加到接口T。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
type ReadWriter interface {
	Read(b Buffer) bool
	Write(b Buffer) bool
}

type File interface {
	ReadWriter  // same as adding the methods of ReadWriter
	Locker      // same as adding the methods of Locker
	Close()
}

type LockedFile interface {
	Locker
	File        // illegal: Lock, Unlock not unique
	Lock()      // illegal: Lock not unique
}

  接口类型T可以不嵌入自身或递归地嵌入T的任何接口类型。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// illegal: Bad cannot embed itself
type Bad interface {
	Bad
}

// illegal: Bad1 cannot embed itself using Bad2
type Bad1 interface {
	Bad2
}
type Bad2 interface {
	Bad1
}

7.11 Map类型

  • Map types - 官方文档
  • Map是一类元素的无序组合。索引是唯一的key值集合。没有初始化的Map值为0.
  • 必须为键类型的操作数完全定义比较运算符==和!=; 因此,键类型不能是函数,映射或切片。 如果密钥类型是接口类型,则必须为动态密钥值定义这些比较运算符; 失败会导致运行时恐慌。
1
2
3
4
5
6
map[string]int
map[*T]struct{ x, y float64 }
map[string]interface{}

make(map[string]int)
make(map[string]int, 100)

  可以在执行期间使用赋值添加元素,并使用索引表达式检索元素; 可以使用delete内置函数删除它们。初始容量不限制容量大小,map可以增长以存储其中的项目数。但nil除外,nil mpa与空map一样,不能添加任何元素。

7.12 通道类型

  • Channel types - 官方文档
  • 通道(channel)提供了一种机制给并发执行的函数,通过发送和接收明确类型的值。没有初始化的channel为nil
  • 操作符<-是可选的,它标明这个通道的传播方向,是发送还是接收。如果没有这个标识符,那么这个channel是双向的。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
chan T          // can be used to send and receive values of type T
chan<- float64  // can only be used to send float64s
<-chan int      // can only be used to receive ints

// The <- operator associates with the leftmost chan possible:
chan<- chan int    // same as chan<- (chan int)
chan<- <-chan int  // same as chan<- (<-chan int)
<-chan <-chan int  // same as <-chan (<-chan int)
chan (<-chan int)

// 可以用make创建一个新的初始化通道,通道类型和可选容量作为参数。
make(chan int, 100)

  容量是设置channel的缓冲区。如果容量为0或者缺乏,这个channel是非缓冲的,只有发送和接收都准备好了才会成功。相反,缓冲通道在它没有满时,会立刻发送不会阻塞。一个nil通道永远不会准备好。

  channel可以使用内置方法close关闭通道。接收运算符的多值赋值形式,可以报告在通道关闭之前是否发送了接收值。

  单个通道可用于发送语句,接收操作,以及通过任意数量的goroutine调用内置函数cap和len而无需进一步同步。channel是先进先出的队列。

08 类型和值特性

  • Properties of types and values - 官方文档

    8.1 类型标识符

  • Type identity - 官方文档

  • 类型特点:如果两个类型的基础类型相同,那么这两种类型是相同的。

    • 如果两个数组类型具有相同的元素类型和相同的数组长度,则它们是相同的。
    • 如果两个切片类型具有相同的元素类型,则它们是相同
    • 如果两个结构类型具有相同的字段序列,并且相应的字段具有相同的名称,相同的类型和相同的标记,则它们是相同的。来自不同 包的未导出字段名称总是不同的。
    • 如果两个指针类型具有相同的基类型,则它们是相同的
    • 如果两个函数类型具有相同数量的参数和结果值,相应的参数和结果类型相同,并且两个函数都是可变参数或两者都不是,则它们 - 是相同的。参数和结果名称不需要匹配。
    • 如果两个接口类型具有相同名称和相同功能类型的相同方法集,则它们是相同的。来自不同包的未导出方法名称总是不同的。方法 - 的顺序无关紧要。
    • 如果两个地图类型具有相同的键和元素类型,则它们是相同的。
    • 如果两种通道类型具有相同的元素类型和相同的方向,则它们是相同的。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
type (
	A0 = []string
	A1 = A0
	A2 = struct{ a, b int }
	A3 = int
	A4 = func(A3, float64) *A0
	A5 = func(x int, _ float64) *[]string
)

type (
	B0 A0
	B1 []string
	B2 struct{ a, b int }
	B3 struct{ a, c int }
	B4 func(int, float64) *B0
	B5 func(x int, y float64) *A1
)

type	C0 = B0

// these types are identical:
A0, A1, and []string
A2 and struct{ a, b int }
A3 and int
A4, func(int, float64) *[]string, and A5

B0 and C0
[]int and []int
struct{ a, b *T5 } and struct{ a, b *T5 }
func(x int, y float64) *[]string, func(int, float64) (result *[]string), and A5

  其中B0和B1是不同的,因为它们两个是被不同的类型定义(TypeDef = identifier Type .)func(int,float64)B0和func(x int,y float64)[]string不同,因为B0与[]string不同。

8.2 类型转换

  • Assignability - 官方文档
  • 如果满足下列条件之一,则x可分配类型为T的变量:
    • x的类型和T的类型相同
    • x的类型V和T的底层类型相同,并且V或T中至少一个不是定义类型
    • T是接口类型,x实现了T
    • x是双向channel值,T是channel类型,x的类型V和T具有相同的元素类型,并且V或T至少有一个不是定义类型
    • x是预先声明的标识符nil,T是指针、函数、切片、map、channel或者接口类型
    • x是一个无类型常量,可由类型T的值表示

8.3 表示

  • Representability - 官方文档
  • 如果满足下列条件之一,则常量x可由类型T的值表示:
    • x是由T确定的值集合
    • T是浮点类型,x可以舍入到T的精度而不会溢出。舍入使用IEEE 754舍入到偶数规则,但IEEE负零进一步简化为无符号零。常量永远不会导致IEEE负零、NaN或无穷大。
    • T是复数类型,x的组件real(x)和imag(x)可由T组件类型(float32 or float64)的值表示

09 块

1
2
Block = "{" StatementList "}" .
StatementList = { Statement ";" } .
  • 除了源代码中的显示块之外,还有隐式块:
    • universe块包含所有Go源文本
    • 每个包都与包块包含这个包的所有源代码
    • 每个文件都有一个文件块,其中包含该文件中的所有Go源文本。
    • 每个“if”,“for”和“switch”语句都被认为是在它自己的隐式块中。
    • “switch”或“select”语句中的每个子句都充当隐式块。

10 声明和范围

  • Declarations and scope - 官方文档
  • 声明绑定非空标识符到常量、类型、变量、方法、Lable或者包。每个标识符必须被声明。同一个块中,标识符不能被声明两次,并且不能在文件和包块中声明标识符
  • 空白标识符可以像声明中的任何其他标识符一样使用,但它不引入绑定,因此不会声明。在包块中,标识符init可能仅用于init函数声明,并且与空标识符一样,它不会引入新的绑定。
1
2
Declaration   = ConstDecl | TypeDecl | VarDecl .
TopLevelDecl  = Declaration | FunctionDecl | MethodDecl .
  • Go使用块的词法范围:
    • 预先声明的标识符的范围是Universe块
    • 顶层(在任何函数之外)声明的常量,类型,变量或函数(但不是方法)的标识符的范围是包块。
    • 导入包的包名称范围是包含导入声明的文件的文件块。
    • 方法接收者,函数参数或结果变量的标识符的范围是函数体。
    • 在函数内声明的常量或变量标识符的范围从ConstSpec或VarSpec(短变量声明的ShortVarDecl)结束开始,并在最内层包含块的末尾结束。
    • 在函数内声明的类型标识符的范围从TypeSpec中的标识符开始,并在最内层包含块的末尾结束。

  块中声明的标识符可以在内部块中重新声明。 虽然内部声明的标识符在范围内,但它表示内部声明声明的实体。

10.01 标签范围

  • Label scopes - 官方文档
  • 标签由带标签的语句声明,用于“break”,“continue”和“goto”语句。 定义从未使用过的标签是违法的。 与其他标识符相比,标签不是块作用域,也不与非标签的标识符冲突。 标签的范围是声明它的函数体,并排除任何嵌套函数的主体。

10.02 空白标识符

  • Blank identifier - 官方文档
  • 空白标识符由下划线字符_表示。 它充当匿名占位符而不是常规(非空)标识符,并且在声明,操作数和赋值中具有特殊含义。

10.03 预先声明标识符

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Types:
	bool byte complex64 complex128 error float32 float64
	int int8 int16 int32 int64 rune string
	uint uint8 uint16 uint32 uint64 uintptr

Constants:
	true false iota

Zero value:
	nil

Functions:
	append cap close complex copy delete imag len
	make new panic print println real recover

10.04 导出标识符

10.05 标识符的唯一性

  • Uniqueness of identifiers - 官方文档
  • 给定一组标识符,如果标识符与集合中的每个标识符不同,则称为唯一标识符。 如果拼写不同,或者它们出现在不同的包中且未导出,则两个标识符是不同的。 否则,他们是一样的。

10.06 常量

  • Constant declarations - 官方文档
  • 常量声明将标识符列表(常量的名称)绑定到常量表达式列表的值。 标识符的数量必须等于表达式的数量,左侧的第n个标识符绑定到右侧的第n个表达式的值。
1
2
3
4
5
ConstDecl      = "const" ( ConstSpec | "(" { ConstSpec ";" } ")" ) .
ConstSpec      = IdentifierList [ [ Type ] "=" ExpressionList ] .

IdentifierList = identifier { "," identifier } .
ExpressionList = Expression { "," Expression } .

  如果存在类型,则所有常量都采用指定的类型,并且表达式必须可分配给该类型。 如果省略该类型,则常量将采用相应表达式的各个类型。 如果表达式值是无类型常量,则声明的常量保持无类型,常量标识符表示常量值。 例如,如果表达式是浮点字面值,则常量标识符表示浮点常量,即使文字的小数部分为零。

1
2
3
4
5
6
7
8
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声明列表中,除了第一个Const之外,可以省略表达式列表。 这样的空列表等同于第一个前面的非空表达式列表的文本替换及其类型(如果有的话)。 因此,省略表达式列表等同于重复前一个列表。 标识符的数量必须等于上一个列表中的表达式数量。 与iota常量生成器一起,此机制允许轻量级声明顺序值:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const (
	Sunday = iota
	Monday
	Tuesday
	Wednesday
	Thursday
	Friday
	Partyday
	numberOfDays  // this constant is not exported
)

10.07 Iota

  • Iota - 官方文档
  • const常量声明,这个预先声明标识符iota表现连续的无类型integer常量。它的值是它们各自在那个声明中的索引,从零开始。它可以定义系列相关常量。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
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

  根据定义,同一ConstSpec中iota的多次使用都具有相同的值:

1
2
3
4
5
6
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)
)

10.08 类型声明

  • Type declarations - 官方文档
  • 类型声明将标识符(类型名称)绑定到类型。 类型声明有两种形式:别名声明和类型定义。
    • 别名声明(Alias declarations):别名声明将标识符绑定到给定类型。
1
2
3
4
5
6
AliasDecl = identifier "=" Type .
// 
type (
	nodeList = []*Node  // nodeList and []*Node are identical types
	Polar    = polar    // Polar and polar denote identical types
)
  • 类型声明(Type definitions):类型定义创建一个新的,不同的类型,其具有与给定类型相同的基础类型和操作,并将标识符绑定到它。
1
2
3
TypeDef = identifier Type .

// 
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// 
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)
}


// 已定义的类型可能具有与之关联的方法。 它不继承绑定到给定类型的任何方法,但接口类型的方法集或复合类型的元素保持不变:

// 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)
}

10.09 变量声明

1
2
VarDecl     = "var" ( VarSpec | "(" { VarSpec ";" } ")" ) .
VarSpec     = IdentifierList ( Type [ "=" ExpressionList ] | "=" ExpressionList ) .
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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不能用于初始化没有显式类型的变量。

1
2
3
4
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

10.10 短变量声明

1
2
3
4
5
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

  与常规变量声明不同,短变量声明可以重新声明变量,前提是它们最初在同一个块中声明(或者如果块是函数体的参数列表)具有相同的类型,并且至少有一个非空变量 是新的。 因此,重新声明只能出现在多变量简短声明中。 重新声明不会引入新变量; 它只是为原始分配一个新值

1
2
3
field1, offset := nextField(str, 0)
field2, offset := nextField(str, offset)  // redeclares offset
a, a := 1, 2   

  简短变量声明可能仅出现在函数内部。 在某些上下文中,例如“if”,“for”或“switch”语句的初始化程序,它们可用于声明本地临时变量。

10.11 函数声明

1
2
3
FunctionDecl = "func" FunctionName Signature [ FunctionBody ] .
FunctionName = identifier .
FunctionBody = Block .

  如果函数的签名声明了结果参数,则函数体的语句列表必须以终止语句结束。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
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

10.12 方法声明

  • Method declarations - 官方文档
  • 方法是具有接收器的功能。 方法声明将标识符(方法名称)绑定到方法,并将方法与接收方的基本类型相关联。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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)

11 表达式

11.01 操作对象

  • Operands - 官方文档
  • 操作数表示表达式中的基本值。 操作数可以是文字,(可能是合格的)非空标识符,表示常量,变量或函数,或带括号的表达式。空白标识符可能仅在分配的左侧显示为操作数。
1
2
3
4
Operand     = Literal | OperandName | "(" Expression ")" .
Literal     = BasicLit | CompositeLit | FunctionLit .
BasicLit    = int_lit | float_lit | imaginary_lit | rune_lit | string_lit .
OperandName = identifier | QualifiedIdent.

11.02 合格的标识符

1
QualifiedIdent = PackageName "." identifier .

  限定标识符访问必须导入的不同包中的标识符。 必须在该包的包块中导出和声明标识符。

1
math.Sin	// denotes the Sin function in package math

11.03 复合文字

  • Composite literals - 官方文档
  • 复合文字构造结构,数组,切片和贴图的值,并在每次计算时创建新值。 它们包含文字的类型,后跟括号绑定的元素列表。 每个元素可以可选地在相应的键之前。
1
2
3
4
5
6
7
8
9
CompositeLit  = LiteralType LiteralValue .
LiteralType   = StructType | ArrayType | "[" "..." "]" ElementType |
                SliceType | MapType | TypeName .
LiteralValue  = "{" [ ElementList [ "," ] ] "}" .
ElementList   = KeyedElement { "," KeyedElement } .
KeyedElement  = [ Key ":" ] Element .
Key           = FieldName | Expression | LiteralValue .
FieldName     = identifier .
Element       = Expression | LiteralValue .

  LiteralType的基础类型必须是结构,数组,切片或地图类型(语法强制执行此约束,除非类型以TypeName形式给出)。 元素和键的类型必须可分配给文字类型的相应字段,元素和键类型; 没有额外的转换。 该键被解释为结构文字的字段名称,数组和切片文字的索引以及地图文字的键。 对于地图文字,所有元素都必须具有键。 指定具有相同字段名称或常量键值的多个元素是错误的。 对于非常量映射键,请参阅评估顺序部分。

  对于struct literals,以下规则适用:

  • 键必须是结构类型中声明的字段名称。
  • 不包含任何键的元素列表必须按声明字段的顺序列出每个结构字段的元素。
  • 如果任何元素都有一个键,则每个元素都必须有一个键。
  • 包含键的元素列表不需要为每个struct字段都有一个元素。 省略的字段获得该字段的零值。
  • 文字可以省略元素列表; 这样的文字评估其类型的零值。
  • 为属于不同包的结构的非导出字段指定元素是错误的。
1
2
3
4
5
6
type Point3D struct { x, y, z float64 }
type Line struct { p, q Point3D }

// one may write
origin := Point3D{}                            // zero value for Point3D
line := Line{origin, Point3D{y: -4, z: 12.3}}  // zero value for line.q.x

  对于数组和切片字面量应用以下规则:

  • 每个元素都有一个关联的整数索引,标记其在数组中的位置。
  • 带键的元素使用键作为索引。 键必须是一个非负常量,可由int类型的值表示; 如果是键入的,则必须是整数类型。
  • 没有键的元素使用前一个元素的索引加一个。 如果第一个元素没有键,则其索引为零。

  获取复合文字的地址会生成一个指向使用文字值初始化的唯一变量的指针。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
var pointer *Point3D = &Point3D{y: 1000}

// 
buffer := [10]string{}             // len(buffer) == 10
intSet := [6]int{1, 2, 3, 5}       // len(intSet) == 6
days := [...]string{"Sat", "Sun"}  // len(days) == 2

// 
[...]Point{{1.5, -3.5}, {0, 0}}     // same as [...]Point{Point{1.5, -3.5}, Point{0, 0}}
[][]int{{1, 2, 3}, {4, 5}}          // same as [][]int{[]int{1, 2, 3}, []int{4, 5}}
[][]Point{{{0, 1}, {1, 2}}}         // same as [][]Point{[]Point{Point{0, 1}, Point{1, 2}}}
map[string]Point{"orig": {0, 0}}    // same as map[string]Point{"orig": Point{0, 0}}
map[Point]string{{0, 0}: "orig"}    // same as map[Point]string{Point{0, 0}: "orig"}

type PPoint *Point
[2]*Point{{1.5, -3.5}, {}}          // same as [2]*Point{&Point{1.5, -3.5}, &Point{}}
[2]PPoint{{1.5, -3.5}, {}}          // same as [2]PPoint{PPoint(&Point{1.5, -3.5}), PPoint(&Point{})}

// 
// list of prime numbers
primes := []int{2, 3, 5, 7, 9, 2147483647}

// vowels[ch] is true if ch is a vowel
vowels := [128]bool{'a': true, 'e': true, 'i': true, 'o': true, 'u': true, 'y': true}

// the array [10]float32{-1, 0, 0, 0, -0.1, -0.1, 0, 0, 0, -1}
filter := [10]float32{-1, 4: -0.1, -0.1, 9: -1}

// frequencies in Hz for equal-tempered scale (A4 = 440Hz)
noteFrequency := map[string]float32{
	"C0": 16.35, "D0": 18.35, "E0": 20.60, "F0": 21.83,
	"G0": 24.50, "A0": 27.50, "B0": 30.87,
}

11.04 函数字面量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 表示一个匿名函数
FunctionLit = "func" Signature FunctionBody .

func(a, b int, z float64) bool { return a*b < int(z) }
 
 
// 函数可以被分配给变量
f := func(x, y int) int { return x + y }
// 或者直接调用
func(ch chan int) { ch <- ACK }(replyChan)

11.05 重要的表达式

11.06 选择器

1
2
3
x.f
// x不是包,上面表达式意味着f是x的字段或者方法,f被称为Selector,它不是空白标识符
// 在T中声明的字段或方法f的深度为零在T中的嵌入字段A中声明的字段或方法f的深度是A中的f的深度加1

  selector的应用规则:

  • 对于非接口、非指针的类型T或*T的值x,x.f意味着字段或方法在T类型中有浅深度处有这样一个f。如果没有这样一个f在浅深度,这个表达式是非法的
  • 对于接口类型I的值x,x.f意味着这个动态的值x有真实的方法f。如果没有f这个方法,这个选择器表达式是非法的
  • 作为例外,如果x是一个定义的指针类型,(*x).f是一个无效的选择器表达式表示字段(而不是方法),则x.f是(*x).f缩写。
  • 其他所有的场景,x.f是非法的
  • 如果x是为nil的指针类型,x.f表示结构体的字段,则分配或评估x.f会导致运行时错误。
  • 如果x是为nil的接口类型,调用或评估x.f会导致运行时错误。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
type T0 struct {
	x int
}

func (*T0) M0()

type T1 struct {
	y int
}

func (T1) M1()

type T2 struct {
	z int
	T1
	*T0
}

func (*T2) M2()

type Q *T2

var t T2     // with t.T0 != nil
var p *T2    // with p != nil and (*p).T0 != nil
var q Q = p

// 或许可以另外的写法
t.z          // t.z
t.y          // t.T1.y
t.x          // (*t.T0).x

p.z          // (*p).z
p.y          // (*p).T1.y
p.x          // (*(*p).T0).x

q.x          // (*(*q).T0).x        (*q).x is a valid field selector

p.M0()       // ((*p).T0).M0()      M0 expects *T0 receiver
p.M1()       // ((*p).T1).M1()      M1 expects T1 receiver
p.M2()       // p.M2()              M2 expects *T2 receiver
t.M2()       // (&t).M2()           M2 expects *T2 receiver, see section on Calls

11.07 函数表达式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
type T struct {
	a int
}
func (tv  T) Mv(a int) int         { return 0 }  // value receiver
func (tp *T) Mp(f float32) float32 { return 1 }  // pointer receiver

var t T

func(tv T, a int) int


// 调用方式
t.Mv(7)
T.Mv(t, 7)
(T).Mv(t, 7)
f1 := T.Mv; f1(t, 7)
f2 := (T).Mv; f2(t, 7)

11.08 函数值

  • Method values - 官方文档
  • 函数作为表达式的值,如果接收可以是指针类型,也可以是值类型。另外类型可以是接口类型也可以是非接口类型。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
type T struct {
	a int
}
func (tv  T) Mv(a int) int         { return 0 }  // value receiver
func (tp *T) Mp(f float32) float32 { return 1 }  // pointer receiver

var t T
var pt *T
func makeT() T

f := t.Mv; f(7)   // like t.Mv(7)
f := pt.Mp; f(7)  // like pt.Mp(7)
f := pt.Mv; f(7)  // like (*pt).Mv(7)
f := t.Mp; f(7)   // like (&t).Mp(7)
f := makeT().Mp   // invalid: result of makeT() is not addressable

11.09 索引表达式

  主要表达形式:a[x]

  索引表达式规则如下:

  • 如果不是map:
    • x必须是integer类型或者未指定类型常量
    • 常量索引必须是非负,并且可以通过int类型的值表示
    • 一个非类型化的常量索引的类型为int
    • index的索引范围为0 <= x < len(a),否则会越界
  • 如果是A类型的数组:
    • 常量索引必须在范围内
    • 如果在运行时x在范围外,则会发生运行时错误
    • a[x]是数组元素在索引x,而a[x]是类型A的元素
  • 如果是指针类型数组:
    • a[x]是(*a)[x]的速记表达方式
  • 如果是S切片类型:
    • 如果运行时x超出界限,发生运行时错误
    • a[x]是切片元素在索引x处,并且这个元素是类型S
  • 如果是string类型:
    • 如果string a是一个常量类型,常量索引必须在范围内
    • 如果运行时x超出界限,发生运行时错误
    • a[x]是在索引x处的一个非常量byte值,a[x]是一个byte类
    • a[x]可能没有被分配到
  • 如果是一个map类型M:
    • x的类型必须分配给key类型M
    • 如果map有一个key键值x到实体,a[x]表示在x键值下的M类型元素a[x]
    • 如果map为nil或者并没有这样一个key,a[x]表示类型M的零值

  其他情况下a[x]是非法的

1
2
3
4
5
6
v, ok = a[x]
v, ok := a[x]
var v, ok = a[x]

// 如果map是nil,那么会产生运行时错误
如果存在这个key对应的实体那么ok返回true否则返回false

11.10 切片表达式

  • Slice expressions - 官方文档
  • slice表达式创建于一个子字符串或者从字符串、数组、指针数组或者切片切割而来。两种变种:一个简单如上述类型一个低位和高位的边界;或者用上述类型通过容量来设定边界。

11.10.1 简单切片表达式

1
2
3
4
5
6
7
8
9
a[low : high]

a := [5]int{1, 2, 3, 4, 5}
s := a[1:4]

// 简单表达式
a[2:]  // same as a[2 : len(a)]
a[:3]  // same as a[0 : 3]
a[:]   // same as a[0 : len(a)]

  对于指针数组,a[low : high]是(*a)[low : high]的速记方式。

  对于切片来说,它的边界是cap(a),而不是length。一个常量index,必须是非负并且是表现为类型int;对于数组或者常量字符串,常量indices必须是在范围内。如果两个都是常量,它必须满足low <= high。如果indices超出范围,在运行时会发生运行时错误。

11.10.2 完全切片表达式

1
2
3
4
5
a[low : high : max]

a := [5]int{1, 2, 3, 4, 5}
t := a[1:3:5]
// 上面表示长度为2容量为4

  索引满足$0 <= low <= high <= max <= cap(a)$,否则会发生out of range。一个常量必须是非负并可以使用int类型表示;对于数组,常量索引必须在范围内。

11.11 类型断言

  主要表达式:

1
x.(T)

  如果T不是接口类型:x.(T)断言动态类型x和T类型相同。T实现了x类型对应的接口;否则,这个断言是无效的,x无法存储T类型的值。

  如果T是接口类型:x.(T)断言动态类型x实现了接口T

  如果类型断言为true,这表达式表达式的值存储在类型为T的x中。如果断言false,则发生运行时错误。换句话说,即使动态类型x只在允许时是已知的,x.(T)在正确的程序中已知为T。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
var x interface{} = 7          // x has dynamic type int and value 7
i := x.(int)                   // i has type int and value 7

type I interface { m() }

func f(y I) {
	s := y.(string)        // illegal: string does not implement I (missing method m)
	r := y.(io.Reader)     // r has type io.Reader and the dynamic type of y must implement both I and io.Reader
	
}

// 类型断言使用分配或初始化特殊形式
v, ok = x.(T)
v, ok := x.(T)
var v, ok = x.(T)
var v, ok T1 = x.(T)

// 一个额外未定义的指针类型ok值为true如果断言成功否则为false并且v的值为T类型的零值这个类型不会发生运行时错误

11.12 调用

  • Calls - 官方文档

  • 方法调用(function call):对于包中导出的方法调用。

  • 函数调用(method call):类型自身的方法调用。

1
2
3
math.Atan2(x, y)  // function call
var pt *Point
pt.Scale(3.5)     // method call with receiver pt

11.13 可变参数…

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func Greeting(prefix string, who ...string)
// 下面的who为nil
Greeting("nobody")
// 下面的who为[]string{"Joe", "Anna", "Eileen"}
Greeting("hello:", "Joe", "Anna", "Eileen")

// 如果最终参数可分配给切片类型[] T,则可以将其作为...参数的值保持不变,
// 如果参数后面跟着....在这种情况下,不会创建新切片。
s := []string{"James", "Jasmine"}
Greeting("goodbye:", s...)

11.14 运算符

1
2
3
4
5
6
7
8
9
Expression = UnaryExpr | Expression binary_op Expression .
UnaryExpr  = PrimaryExpr | unary_op UnaryExpr .

binary_op  = "||" | "&&" | rel_op | add_op | mul_op .
rel_op     = "==" | "!=" | "<" | "<=" | ">" | ">=" .
add_op     = "+" | "-" | "|" | "^" .
mul_op     = "*" | "/" | "%" | "<<" | ">>" | "&" | "&^" .

unary_op   = "+" | "-" | "!" | "^" | "*" | "&" | "<-" .

  对于二元操作符,操作数类型必须相同,除非操作涉及移位无类型常量

  除了操作符之外,如果一个操作数是无类型常量而另一个操作数不是,则该常量将隐式转换为另一个操作数的类型。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
var s uint = 33
var i = 1<<s                  // 1 has type int
var j int32 = 1<<s            // 1 has type int32; j == 0
var k = uint64(1<<s)          // 1 has type uint64; k == 1<<33
var m int = 1.0<<s            // 1.0 has type int; m == 0 if ints are 32bits in size
var n = 1.0<<s == j           // 1.0 has type int32; n == true
var o = 1<<s == 2<<s          // 1 and 2 have type int; o == true if ints are 32bits in size
var p = 1<<s == 1<<33         // illegal if ints are 32bits in size: 1 has type int, but 1<<33 overflows int
var u = 1.0<<s                // illegal: 1.0 has type float64, cannot shift
var u1 = 1.0<<s != 0          // illegal: 1.0 has type float64, cannot shift
var u2 = 1<<s != 1.0          // illegal: 1 has type float64, cannot shift
var v float32 = 1<<s          // illegal: 1 has type float32, cannot shift
var w int64 = 1.0<<33         // 1.0<<33 is a constant shift expression
var x = a[1.0<<s]             // 1.0 has type int; x == a[0] if ints are 32bits in size
var a = make([]byte, 1.0<<s)  // 1.0 has type int; len(a) == 0 if ints are 32bits in size

  运算符优先级,一元(Unary)运算符优先级最高,优先级如下:

Precedence Operator
5 * / % << >> & &^
4 + - | ^
3 == != < <= > >=
2 &&
1 ||

11.14.1 算术运算符

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
+    sum                    integers, floats, complex values, strings
-    difference             integers, floats, complex values
*    product                integers, floats, complex values
/    quotient               integers, floats, complex values
%    remainder              integers

&    bitwise AND            integers
|    bitwise OR             integers
^    bitwise XOR            integers
&^   bit clear (AND NOT)    integers

<<   left shift             integer << unsigned integer
>>   right shift            integer >> unsigned integer

  此规则的一个例外是,如果被除数x是int类型x的最负值,则由于二进制补码整数溢出,商q = x / -1等于x(和r = 0):

x q
int8 -128
int16 -32768
int32 -2147483648
int64 -9223372036854775808

  如果除数是常数,则它不能为零。 如果除数在运行时为零,则会发生运行时混乱。   如果被除数是非负的且除数是2的常数幂,则除法可以用右移代替,并且计算余数可以用按位AND运算代替

x x / 4 x % 4 x >> 2 x & 3
11 2 3 2 3
-11 -2 -3 -3 1
  • 移位操作符(shift operators)将左操作符移位到右操作符指定的移位计数。
  • 如果左操作符是有符号整数,则它们实现算数移位;如果是无符号整数,则它们实现的是逻辑移位。
  • 移位次数没有上限
  • 移位计数n就好比左操作数被移位了n。
  • x << 1 与 x * 2相同,x >> 1 与 x / 2相同,但截断为负无穷大

  一元运算符定义如下:

1
2
3
4
+x                          is 0 + x
-x    negation              is 0 - x
^x    bitwise complement    is m ^ x  with m = "all bits set to 1" for unsigned x
                                      and  m = -1 for signed x
  • 整数溢出   整型溢出:无符号整型溢出时会丢弃高位数;对于有符号整型溢出时不会导致运行时错误。

  • 浮点运算符   浮点数和复数除以0是否发生运行时错误依据的是具体实现

  例如,一些体系结构提供了“融合乘法和加法”(FMA)指令,该指令计算x * y + z而不舍入中间结果x * y。 这些示例显示Go实现何时可以使用该指令:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// FMA allowed for computing r, because x*y is not explicitly rounded(舍入):
r  = x*y + z
r  = z;   r += x*y
t  = x*y; r = t + z
*p = x*y; r = *p + z
r  = x*y + float64(z)

// FMA disallowed for computing r, because it would omit rounding of x*y:
r  = float64(x*y) + z
r  = z; r += float64(x*y)
t  = float64(x*y); r = t + z
  • 字符串串联
1
2
s := "hi" + string(c)
s += " and good bye"

11.14.2 比较运算符

1
2
3
4
5
6
==    equal
!=    not equal
<     less
<=    less or equal
>     greater
>=    greater or equal

  等于运算符==和!=适用于可比较的操作数。 排序运算符<,<=,>和> =适用于可排序的操作数。 这些术语和比较结果定义如下: - Boolean值 可比较 - Integer值 可比较,也可排序 - Floating-point值 可比较,可排序,依照IEEE-754标准 - Complex(复数)值 可比较。两个复数相等,分别比较实体real(u)和虚构imag(u)部分。 - String值 可比较,可排序(按字节顺序排列) - Pointer值 可比较。两个指针如果指向同一个变量或者它们都为nil则相等。指向不同零大小(zero-size)变量的指针可能相同也可能不相等。 - Channel值 可比较,如果两个通道值是由相同的make调用创建的,或者两者都具有值nil,则它们是相等的。 - Interface值 可比较,两个接口如果有相同的动态类型和动态值,或者两个都为nil,则它们是相等的。 - x是非接口类型X,t是接口类型T,当X实现了T时,是可比较的。当t的动态类型是X,当t的动态值与x相等。 - Struct值 可比较,当它所有的field可比较。如果它们对应的非空白字段相等,则两个struct相等 - Array值 可比较,如果数组的元素是可比较的。如果它们对应的元素相等,则数组相等。

  注意:如果两个接口(interface)的相同动态类型是不可比较的,则会发生运行时错误。如果数组中有interface、struct中有interface也会发生这样的错误。

  • Slice、Map和方法值是不可比较的。特殊情况除外,当它们的值为nil时,可以比较
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const c = 3 < 4            // c is the untyped boolean constant true

type MyBool bool
var x, y int
var (
	// The result of a comparison is an untyped boolean.
	// The usual assignment rules apply.
	b3        = x == y // b3 has type bool
	b4 bool   = x == y // b4 has type bool
	b5 MyBool = x == y // b5 has type MyBool
)

11.14.3 逻辑运算符

1
2
3
&&    conditional AND    p && q  is  "if p then q else false"
||    conditional OR     p || q  is  "if p then true else q"
!     NOT                !p      is  "not p"

11.14.4 地址运算符

  • Address operators - 官方文档
  • T类型的运算数x,地址运算符&x生成一个指针类型*T。这个操作数必须是可寻址,这意味着一个变量、一个指针、切片指针操作符;或者struct可寻址的操作数,x也可以是复合字面量。如果x会产生运行时错误,那么&x也会产生。
  • 指针类型*T的运算数x,*x意味着类型T的值。如果x是nil,企图取*x则会发生运行时错误
1
2
3
4
5
6
7
8
9
&x
&a[f(2)]
&Point{2, 3}
*p
*pf(x)

var x *int = nil
*x   // causes a run-time panic
&*x  // causes a run-time panic

11.14.5 接收操作符

  • Receive operator - 官方文档
  • channel类型的操作数ch,接收操作符<-ch是从ch通道接收的值。这个通道必须允许接收操作,并且接收运算符是这个通道的元素类型。这个表达式会一直阻塞直到值存在。从一个nil通道接收会永远阻塞。在一个关闭的通道接收操作会立即执行,接收任何先发送的值之后产生对应元素的零值
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
v1 := <-ch
v2 = <-ch
f(<-ch)
<-strobe  // wait until clock pulse and discard received value

// 用于特殊表单的赋值或初始化的接收表达式
x, ok = <-ch
x, ok := <-ch
var x, ok = <-ch
var x, ok T = <-ch

  上面产生一个无类型的boolean值来报告交流是否成功。如果接收的值是通过成功的发送操作传递给通道,则ok的值为true;如果由于通道关闭且为空而产生的零值,则为false。   

11.15 换算

  • Conversions - 官方文档
  • 转换可以是显示转换,也可以是隐式转换
  • 显示转换:T(x)
  • 如果类型前跟着操作符*或者<-,或者类型跟着关键字func并没有返回列表,它必须被括起来以免产生歧义。
1
2
3
4
5
6
7
8
*Point(p)        // same as *(Point(p))
(*Point)(p)      // p is converted to *Point
<-chan int(c)    // same as <-(chan int(c))
(<-chan int)(c)  // c is converted to <-chan int
func()(x)        // function signature func() x
(func())(x)      // x is converted to func()
(func() int)(x)  // x is converted to func() int
func() int(x)    // x is converted to func() int (unambiguous)

  x是一个常量,如果它可以由T表示,则x可以被转换为类型T。有个特殊情况,x是一个非常量整数,可以显示转换为字符串类型。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
uint(iota)               // iota value of type uint
float32(2.718281828)     // 2.718281828 of type float32
complex128(1)            // 1.0 + 0.0i of type complex128
float32(0.49999999)      // 0.5 of type float32
float64(-1e-1000)        // 0.0 of type float64
string('x')              // "x" of type string
string(0x266c)           // "♬" of type string
MyString("foo" + "bar")  // "foobar" of type MyString
string([]byte{'a'})      // not a constant: []byte{'a'} is not a constant
(*int)(nil)              // not a constant: nil is not a constant, *int is not a boolean, numeric, or string type
int(1.2)                 // illegal: 1.2 cannot be represented as an int
string(65.0)             // illegal: 65.0 is not an integer constant

  一个非常量x可以被转换为类型T在下面任何一种情境下:

  • x被分配给T
  • 忽略struct的tags,x的类型和T有相同的底层类型
  • 忽略struct的tags,x的类型和T的指针类型不是定义类型,并且它们的指针基本类型有相同的底层类型
  • x的类型和T都是整数或浮点数类型
  • x的类型和T都是复数类型
  • x是一个整数、字节切片、runes,T是字符串类型
  • x是字符串,T是字节切片、runes

  当比较struct相同,转换时,它的tags被忽略。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
type Person struct {
	Name    string
	Address *struct {
		Street string
		City   string
	}
}

var data *struct {
	Name    string `json:"name"`
	Address *struct {
		Street string `json:"street"`
		City   string `json:"city"`
	} `json:"address"`
}

var person = (*Person)(data)  // ignoring tags, the underlying types are identical

  没有语言机制来在指针和整数之间进行转换。 软件包unsafe在受限情况下实现此功能。

  数字类型之间的转换

  • 非常量转换规则:
    • 当转换发生在整数类型之间,一个有符号整数,它的符号扩展到隐式无限精度;否则它是零扩展。 然后将其截断以适合结果类型的大小。 例如,如果v := uint16(0x10F0),则uint32(int8(v)) == 0xFFFFFFF0。转换始终产生有效值;没有溢出。
    • 当转换一个浮点数为一个整数,分数部分被丢弃(截断为0)
    • 当把一个整数或者浮点数转换为一个浮点数类型,或者复数转换为另一个复数类型,结果值四舍五入到目标类型指定的精度。比如,float32类型的变量x的值可以使用超出IEEE-754 32位数的附加精度来存储,但float32(x)表示将x的值舍入为32位精度的结果。 类似地,x + 0.1可能使用超过32位的精度,但float32(x + 0.1)则不能。
  • 在涉及浮点值或复数值的所有非常量转换中,如果结果类型无法表示转换成功的值,但结果值与实现有关。

  字符串转换

  • 整数转换为字符串,将会产生UTF-8表现形式的字符串。超出Unicode范围的会被表示为”\uFFFD”
1
2
3
4
5
string('a')       // "a"
string(-1)        // "\ufffd" == "\xef\xbf\xbd"
string(0xf8)      // "\u00f8" == "ø" == "\xc3\xb8"
type MyString string
MyString(0x65e5)  // "\u65e5" == "日" == "\xe6\x97\xa5"
  • 字节切片转换为字符串产生一个这个切片连续字节的字符串。
1
2
3
4
5
6
string([]byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'})   // "hellø"
string([]byte{})                                     // ""
string([]byte(nil))                                  // ""

type MyBytes []byte
string(MyBytes{'h', 'e', 'l', 'l', '\xc3', '\xb8'})  // "hellø"
  • 将一个符文切片转换为字符串会产生连续符文值串联起来的字符串
1
2
3
4
5
6
string([]rune{0x767d, 0x9d6c, 0x7fd4})   // "\u767d\u9d6c\u7fd4" == "白鵬翔"
string([]rune{})                         // ""
string([]rune(nil))                      // ""

type MyRunes []rune
string(MyRunes{0x767d, 0x9d6c, 0x7fd4})  // "\u767d\u9d6c\u7fd4" == "白鵬翔"
  • 将一个字符串转换为字节切片产生字符串连续字节的切片。
1
2
3
4
[]byte("hellø")   // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}
[]byte("")        // []byte{}

MyBytes("hellø")  // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}
  • 见一个字符串转换为符文切片产生对应字符串连续的符文切片
1
2
3
4
[]rune(MyString("白鵬翔"))  // []rune{0x767d, 0x9d6c, 0x7fd4}
[]rune("")                 // []rune{}

MyRunes("白鵬翔")           // []rune{0x767d, 0x9d6c, 0x7fd4}

11.16 常量表达式

  常量比较总是产生无类型的布尔值。如果移位表达式(shift expression)左操作数是无类型常量,结果是一个整数常量;否则它和左操作数类型相同,它必须是整数类型。

  对于无类型常量的任何操作结果都是同类型的无类型常量;也就是说,布尔,整数,浮点,复数或字符串常量。如果二进制操作(移位除外)的无类型操作数具有不同的类型,结果是此列表中稍后出现的操作数类型:整数,符文,浮点数,复数。比如,无类型整数常量除以无类型复数常量会产生无类型复数常量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const a = 2 + 3.0          // a == 5.0   (untyped floating-point constant)
const b = 15 / 4           // b == 3     (untyped integer constant)
const c = 15 / 4.0         // c == 3.75  (untyped floating-point constant)
const Θ float64 = 3/2      // Θ == 1.0   (type float64, 3/2 is integer division)
const Π float64 = 3/2.     // Π == 1.5   (type float64, 3/2. is float division)
const d = 1 << 3.0         // d == 8     (untyped integer constant)
const e = 1.0 << 3         // e == 8     (untyped integer constant)
const f = int32(1) << 33   // illegal    (constant 8589934592 overflows int32)
const g = float64(2) >> 1  // illegal    (float64(2) is a typed floating-point constant)
const h = "foo" > "bar"    // h == true  (untyped boolean constant)
const j = true             // j == true  (untyped boolean constant)
const k = 'w' + 1          // k == 'x'   (untyped rune constant)
const l = "hi"             // l == "hi"  (untyped string constant)
const m = string(k)        // m == "x"   (type string)
const Σ = 1 - 0.707i       //            (untyped complex constant)
const Δ = Σ + 2.0e-4       //            (untyped complex constant)
const Φ = iota*1i - 1/1i   //            (untyped complex constant)

// 使用内置函数complex于无类型整数、符文或者浮点数常量产生一个无类型复数常量
const ic = complex(0, c)   // ic == 3.75i  (untyped complex constant)
const  = complex(0, Θ)   // iΘ == 1i     (type complex128)

//  常量表达式总是精确计算;中间值和常量本身精确度明显大于预先声明的类型。下面的声明是合法的:
const Huge = 1 << 100         // Huge == 1267650600228229401496703205376  (untyped integer constant)
const Four int8 = Huge >> 98  // Four == 4                                (type int8)

//  类型常量值必须精确表示常量类型。下面的常量表达式是非法的:
uint(-1)     // -1 cannot be represented as a uint
int(3.14)    // 3.14 cannot be represented as an int
int64(Huge)  // 1267650600228229401496703205376 cannot be represented as an int64
Four * 300   // operand 300 cannot be represented as an int8 (type of Four)
Four * 100   // product 400 cannot be represented as an int8 (type of Four)

  一元按位补码运算符^使用掩码匹配非常量规则:对于无符号常量,掩码全为1,对于有符号和无类型常量,掩码均为-1。

1
2
3
4
5
^1         // untyped integer constant, equal to -2
uint8(^1)  // illegal: same as uint8(-2), -2 cannot be represented as a uint8
^uint8(1)  // typed uint8 constant, same as 0xFF ^ uint8(1) = uint8(0xFE)
int8(^1)   // same as int8(-2)
^int8(1)   // same as -1 ^ int8(1) = -2

11.17 计算顺序

  • Order of evaluation - 官方文档
  • 包级别,初始化依赖于变量初始化表达式各个初始化表达式的顺序。当计算表达式的操作数,分配或返回语句,所有的方法调用,函数调用,和通信操作都是从左到右的顺序进行评估。

  比如,在赋值语句中:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
y[f()], ok = g(h(), i()+x[j()], <-c), k()
// 方法调用和通信执行顺序f(),h(),i(),j(),<-,g(),and k()。
// 但是,没有指定与x的评估和索引以及y的评估相比较的那些事件的顺序。

a := 1
f := func() int { a++; return a }
x := []int{a, f()}            // x may be [1, 2] or [2, 2]: evaluation order between a and f() is not specified
m := map[int]int{a: 1, a: 2}  // m may be {2: 1} or {2: 2}: evaluation order between the two map assignments is not specified
n := map[int]int{a: f()}      // n may be {2: 3} or {3: 3}: evaluation order between the key and the value is not specified


// 在包级别,初始化依赖性会覆盖单个初始化表达式的从左到右的规则,但不会覆盖每个表达式中的操作数:
var a, b, c = f() + v(), g(), sqr(u()) + v()

func f() int        { return c }
func g() int        { return a }
func sqr(x int) int { return x*x }

// functions u and v are independent of all other variables and functions
// 执行顺序u(), sqr(), v(), f(), v(), and g().

  如果执行中有依赖,首先从左到右执行独立的方法或变量。

  根据运算符的关联性评估单个表达式中的浮点运算。 显式括号通过覆盖默认关联性来影响评估。 在表达式x +(y + z)中,在添加x之前执行加法y + z。

12 语句

1
2
3
4
5
6
7
Statement =
	Declaration | LabeledStmt | SimpleStmt |
	GoStmt | ReturnStmt | BreakStmt | ContinueStmt | GotoStmt |
	FallthroughStmt | Block | IfStmt | SwitchStmt | SelectStmt | ForStmt |
	DeferStmt .

SimpleStmt = EmptyStmt | ExpressionStmt | SendStmt | IncDecStmt | Assignment | ShortVarDecl .

12.01 结束语句

  下面的语句是结束语句:

  • “return” 或 “goto” 语句
  • 内置函数 panic
  • 语句列表以终止语句结束的块。
  • if 语句:
    • else分支出现
    • 两个分支都结束
  • for 语句:
    • 没有break语句关联到for 语句
    • 循环条件不成立
  • switch 语句:
    • 没有break语句关联到switch 语句
    • 有一个default语句
    • 各种情况包括default,结束语句,或者一个label “fallthough” 语句
  • select 语句:
    • 没有break语句关联到select语句
    • 语句列表中的各种情况包括default语句,出现结束语句
  • 标记终止声明的带标签的声明。
  • 如果列表不为空且其最终非空语句正在终止,则语句列表以终止语句结束。

12.02 空语句

1
EmptyStmt = .

12.03 带标签的语句

1
2
3
4
LabeledStmt = Label ":" Statement .
Label       = identifier .

Error: log.Panic("error encountered")

12.04 表达式语句

  • Expression statements- 官方文档
  • 除特定的内置函数外,函数和方法调用和接收操作可以出现在语句上下文中。 这些陈述可以用括号括起来。
1
2
3
4
5
h(x+y)
f.Close()
<-ch
(<-ch)
len("foo")  // illegal if len is the built-in function

12.05 发送语句

  • Send statements- 官方文档
  • send语句在通道上发送值。 通道表达式必须是通道类型,通道方向必须允许发送操作,并且要发送的值的类型必须可分配给通道的元素类型。
  • 非缓存通道当接收端确认收到再执行,缓存通道。 如果缓冲区中有空间,则可以继续缓冲通道上的发送。 关闭通道上的发送通过导致运行时恐慌而继续。 在零通道上发送永久阻止。
1
2
3
4
SendStmt = Channel "<-" Expression .
Channel  = Expression .

ch <- 3  // send value 3 to channel ch

12.06 自增自减语句

  • IncDec statements- 官方文档
  • ++和–语句自增或自减无类型常量操作数1。作为分配,操作数必须是可寻址或者一个map的索引表达式
1
IncDecStmt = Expression ( "++" | "--" ) .
IncDec statement Assignment
x++ x += 1
x– x -= 1

12.07 赋值语句

  • Assignments- 官方文档
  • 每个左侧的操作数必须是可寻址的,一个map索引表达式,或者空白标志服。操作数可能被括起来
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Assignment = ExpressionList assign_op ExpressionList .

assign_op = [ add_op | mul_op ] "=" .


x = 1
*p = f()
a[i] = 23
(k) = <-ch  // same as: k = <-ch
// 一个赋值操作符x op=y,op是二元算术运算符等效于 x=x op (y),但是只计算x一次。
// 在赋值操作符中,左右两边的表达式必须明确单个值的表达式,并且左边的表达式不能是空白标识符。

a[i] <<= 2
i &^= 1<<n

// 元组赋值将多值运算的各个元素分配给变量列表。有两种形式。
// 首先,右侧操作数是一个单个的多值表达式比如函数调用,一个channel或者map运算符,或者一个类型断言。左侧的操作数数量必须匹配值。比如,如果f函数返回两个值
x, y = f()
// 第二种形式,左侧操作数必须和右侧的表达式数量匹配,每个表达式有单个值,按顺序赋值给左侧的操作数。
one, two, three = '一', '二', '三'

// 空白标识符提供一种方式忽略右侧值在赋值语句中
_ = x       // evaluate x but ignore it
x, _ = f()  // evaluate f() but ignore second result value

  赋值分两个阶段进行。首先,左侧索引表达式和间接指针(包括明确的选择器的间接指针)的操作数和右侧的表达式都是普通的顺序。另外,赋值从左到右的顺序进行。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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}

  在赋值中,每个值必须可分配给它所分配的操作数的类型,具有以下特殊情况:

  • 任何类型的值都可以分配给空白标识符
  • 如果无类型常量被分配给接口类型变量或者空白标识符,常量首先要显示转换为它的默认的类型。
  • 如果无类型布尔值被分配给接口类变量或者空白标识符,首先显示转换为bool类型

12.08 If 语句

  • If statements- 官方文档
  • if语句执行两个分支的具体条件,依据布尔表达式。如果表达式的值为true,if的分支执行;否则,else分支执行。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
IfStmt = "if" [ SimpleStmt ";" ] Expression Block [ "else" ( IfStmt | Block ) ] .

if x > max {
	x = max
}

// 表达式可以在一个简单语句之前,该语句在计算表达式之前执行。
if x := f(); x < y {
	return x
} else if x > z {
	return z
} else {
	return y
}

12.09 Switch 语句

  • Switch statements- 官方文档
  • switch语句提供了多路执行。将表达式或类型说明符与“switch”内的“cases”进行比较,以确定要执行的分支。
1
SwitchStmt = ExprSwitchStmt | TypeSwitchStmt .

  有两种形式:表达式switches和类型switches。 - 在一个表达式switch,这个case和switch表达式比较; - 在类型switch中,case与switch类型进行比较。switch语句中的switch表达式只被评估一次。

  表达式switch:

  • 在表达式switch,评估switch表达式和case表达式,case表达式不需要是常量,评估从左到右,从上到下进行;第一个和switch表达式相等会触发关联的case执行;其他case则跳过。如果没有case匹配并有一个default语句,default对应语句被执行。最多只有一个default情况,它可以出现在switch语句中的任意位置。缺少的开关表达式等效于布尔值true。
1
2
3
ExprSwitchStmt = "switch" [ SimpleStmt ";" ] [ Expression ] "{" { ExprCaseClause } "}" .
ExprCaseClause = ExprSwitchCase ":" StatementList .
ExprSwitchCase = "case" ExpressionList | "default" .
  • 如果switch表达式结果是一个无类型常量,它首先显示转换为它的默认类型;如果它是一个无类型布尔值,它首先被显示转换为bool。预先声明无类型值nil不能被用于switch表达式。
  • 如果case表达式是无类型,首先需要显示转换为switch表达式的类型。对于每个case表达式 x 和switch表达式的值 t ,x == t 必须是个有效的比较表达式。
  • 换句话说,switch表达式被视为用于声明和初始化没有显式类型的临时变量t; 它是t的值,对每个case表达式x进行相等性测试。
  • 在case或default子句中,最后一个非空语句可以是(可能标记的)“fallthrough”语句,以指示控件应该从该子句的末尾流向下一个子句的第一个语句。 否则控制流到“switch”语句的末尾。 “fallthrough”语句可能显示为除表达式开关的最后一个子句之外的所有语句。
  • switch表达式可以在一个简单语句之前,该语句在计算表达式之前执行。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
switch tag {
default: s3()
case 0, 1, 2, 3: s1()
case 4, 5, 6, 7: s2()
}

switch x := f(); {  // missing switch expression means "true"
case x < 0: return -x
default: return x
}

switch {
case x < y: f1()
case x < z: f2()
case x == 4: f3()
}

// 实现限制编译器可能不允许多个案例表达式评估相同的常量 例如当前编译器在case表达式中不允许重复的整数浮点或字符串常量

  类型switch:

  • 类型switch比较的是类型而不是值。它在其他方面类似于表达式switch。 它由一个特殊的switch表达式标记,该表达式具有使用保留字类型而不是实际类型的类型断言的形式:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
switch x.(type) {
// cases
}
// case匹配实际类型T和动态x的类型。通过类型断言,x必须是接口类型,并且每个case中的非接口类型T必须实现了x类型。并且case的每个类型必须全部不同。

TypeSwitchStmt  = "switch" [ SimpleStmt ";" ] TypeSwitchGuard "{" { TypeCaseClause } "}" .
TypeSwitchGuard = [ identifier ":=" ] PrimaryExpr "." "(" "type" ")" .
TypeCaseClause  = TypeSwitchCase ":" StatementList .
TypeSwitchCase  = "case" TypeList | "default" .
TypeList        = Type { "," Type } .
  • TypeSwitchGuard可能包含一个短变量声明。 使用该表单时,变量在每个子句的隐式块中的TypeSwitchCase的末尾声明。 在具有仅列出一种类型的案例的子句中,变量具有该类型; 否则,该变量具有TypeSwitchGuard中表达式的类型。
  • 案例可以使用预先声明的标识符nil而不是类型; 当TypeSwitchGuard中的表达式是nil接口值时,将选择该情况。 可能最多只有一个nil case。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
switch i := x.(type) {
case nil:
	printString("x is nil")                // type of i is type of x (interface{})
case int:
	printInt(i)                            // type of i is int
case float64:
	printFloat64(i)                        // type of i is float64
case func(int) float64:
	printFunction(i)                       // type of i is func(int) float64
case bool, string:
	printString("type is bool or string")  // type of i is type of x (interface{})
default:
	printString("don't know the type")     // type of i is type of x (interface{})
}

// could be rewritten:

v := x  // x is evaluated exactly once
if v == nil {
	i := v                                 // type of i is type of x (interface{})
	printString("x is nil")
} else if i, isInt := v.(int); isInt {
	printInt(i)                            // type of i is int
} else if i, isFloat64 := v.(float64); isFloat64 {
	printFloat64(i)                        // type of i is float64
} else if i, isFunc := v.(func(int) float64); isFunc {
	printFunction(i)                       // type of i is func(int) float64
} else {
	_, isBool := v.(bool)
	_, isString := v.(string)
	if isBool || isString {
		i := v                         // type of i is type of x (interface{})
		printString("type is bool or string")
	} else {
		i := v                         // type of i is type of x (interface{})
		printString("don't know the type")
	}
}
  • 类型开关防护装置可以在简单语句之前,该语句在评估防护装置之前执行。
  • 类型切换中不允许使用“fallthrough”语句。

12.10 For 语句

  • For statements- 官方文档
  • for语句针对重复执行同一个语句快。它有3中形式:迭代可能用简单的条件控制,一个 for 分句或者 range 分句
1
2
ForStmt = "for" [ Condition | ForClause | RangeClause ] Block .
Condition = Expression .

  单个条件的for语句:这是最简单的形式,一个 for 语句明确布尔条件重复执行语句快。这个条件在每次执行前被计算。如果条件不存在,则它等于布尔值true。

1
2
3
for a < b {
	a *= 2
}

  for分句的for语句:带有ForClause的“for”语句也受其条件控制,但另外它可以指定init和post语句,例如赋值,递增或递减语句。 init语句可能是一个简短的变量声明,但post语句不能。 init语句声明的变量在每次迭代中重复使用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
ForClause = [ InitStmt ] ";" [ Condition ] ";" [ PostStmt ] .
InitStmt = SimpleStmt .
PostStmt = SimpleStmt .


for i := 0; i < 10; i++ {
	f(i)
}

// 如果是非空,初始化语句被在第一次迭代时执行一次,并在评估条件之前;post语句在每次块执行完成后执行一次。任何ForClause元素可能是空但是分号是必须的除非只有一个条件。如果条件不存在,等效于条件是一个true的布尔值

for cond { S() }    is the same as    for ; cond ; { S() }
for      { S() }    is the same as    for true     { S() }

  用range分句的for语句

  • for语句通过使用range 分句迭代array、slice、string、map或者从channel接收的值的全部元素。每个实体分配迭代值给迭代变量,并执行这个语句块。
1
RangeClause = [ ExpressionList "=" | IdentifierList ":=" ] "range" Expression .
  • 在range分句的右侧表达式叫做range expression,它可能是个array、数组指针、切片、string、map或channel接收操作。作为一个语句,如果左侧操作数必须是可寻址的或者map索引表达式(a[x]);它们代表迭代变量,如果range expression是channel,最多提供一个迭代变量,其他表达式可能会有两个。如果最后一个迭代变量是空白标识符,range分句等效于没有那个标识符。
  • range expression x在迭代的开始前被计算一次,但有个例外情况:如果最多一个迭代变量并且len(x)是常量,range expression则不计算
  • 每次迭代左侧的方法调用被调用。每次迭代,如果存在相应的迭代变量,则按如下方式生成迭代值:
Range expression 1st value 2nd value
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
  • 对于一个array,指向array或者slice值 a,索引迭代值从0开始并按顺序递增。如果最多一个迭代变量显示,range循环迭代值从0到len(a)-1,并且不会索引到array或者slice本身。对于一个nil切片,迭代次数为0。
  • 对于一个string值,range分句迭代通过字符串的unicode代码点,并从索引为0的字节开始。在连续的迭代中,索引值是字符串中连续UTF-8编码的代码点的第一个字节的索引,而符文类型的第二个值将是相应的代码点的值。如果迭代遭遇不存在的utf-8序列,第二个值会是0xFFFD。Unicode替换字符,下一次迭代将在字符串中前进一个字节。
  • map的迭代顺序是不确定的,并不能保证从一次迭代到下一次迭代是相同的。如果map实体在迭代过程被移除而不能达到,相关的迭代值不会被执行。如果map在迭代过程被创建,实体可能被执行,也可能被跳过。对于创建的每个条目以及从一次迭代到下一次迭代,选择可能不同。nil的map迭代次数为0
  • 对于channel,迭代值产生是channel发送连续的值,直到channel被关闭。如果channel是nil,range expression会被永久阻塞。

  迭代值被分配给各自迭代变量作为赋值语句

  迭代变量可能被声明通过range分句使用短变量声明(:=)。这种情况它们的类型被设置给各自独立迭代值对应的类型,并且它们的范围是for语句的块;它们被重复使用在每次迭代。如果迭代变量声明在for语句外面,它们的值会是最后一次执行的结果。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
var testdata *struct {
	a *[7]int
}
for i, _ := range testdata.a {
	// testdata.a is never evaluated; len(testdata.a) is constant
	// i ranges from 0 to 6
	f(i)
}

var a [10]string
for i, s := range a {
	// type of i is int
	// type of s is string
	// s == a[i]
	g(i, s)
}

var key string
var val interface {}  // element type of m is assignable to val
m := map[string]int{"mon":0, "tue":1, "wed":2, "thu":3, "fri":4, "sat":5, "sun":6}
for key, val = range m {
	h(key, val)
}
// key == last map key encountered in iteration
// val == map[key]

var ch chan Work = producer()
for w := range ch {
	doWork(w)
}

// empty a channel
for range ch {}

12.11 Go语句

  • Go statements- 官方文档
  • “go”语句在同一地址空间内作为独立的并发控制线程或goroutine开始执行函数调用。
1
GoStmt = "go" Expression .

  表达式必须是一个function或method调用;它不能使用括号括起来。调用内置函数限制请参考前面表达式语句。

  函数值和参数在调用goroutine中照常计算,但与常规调用不同,程序执行不等待调用的函数完成。反而,方法在一个新的goroutine独立的运行。当方法结束,这个goroutine也结束。如果方法有任何返回值,当方法完成时,它们也会被丢弃。

1
2
go Server()
go func(ch chan<- bool) { for { sleep(10); ch <- true }} (c)

12.12 Select语句

  • Select statements- 官方文档
  • select语句选择一组send或receive操作符会被执行。它有点类似于switch,但是case全部是通信操作符
1
2
3
4
5
SelectStmt = "select" "{" { CommClause } "}" .
CommClause = CommCase ":" StatementList .
CommCase   = "case" ( SendStmt | RecvStmt ) | "default" .
RecvStmt   = [ ExpressionList "=" | IdentifierList ":=" ] RecvExpr .
RecvExpr   = Expression .

  一个带RecvStmt的case可能分配一个接收表达式结果给一个或两个变量,变量可以用短变量声明。接收表达式必须是一个接收操作符(可能用括号括起来)。可以最多有一个default分句,它可能出现在列表中的任意位置。

  执行一个select语句分几步进行:

  • 对于语句中的case,接收操作的channel操作数以及发送语句的channel和右侧表达式在输入“select”语句后按源顺序精确计算一次。结果是一组要接收或发送的通道,以及要发送的相应值。无论选择哪种(如果有的话)通信操作进行,评估中的任何副作用都将发生。具有短变量声明或赋值的RecvStmt左侧的表达式不计算。
  • 如果一个或多个通信可以继续,则可以通过统一的伪随机选择来选择可以继续的单个通信。其他情况,如果又一个default case,则执行这个case。如果没有default case,选择语句阻塞直到至少一个通信可以执行。
  • 除非所选择的情况是默认情况,否则执行相应的通信操作。如果所选案例是具有短变量声明或赋值的RecvStmt,则计算左侧表达式并分配接收值(或多个值)。
  • 执行所选案例的语句列表。
  • **如果是for循环内存在一个select并且存在default语句分支,则容易造成死循环抢占cpu,导致CPU占用很高。**

  由于nil通道上的通信永远不会进行,因此只选择nil通道并且永远不会出现默认情况:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
var a []int
var c, c1, c2, c3, c4 chan int
var i1, i2 int
select {
case i1 = <-c1:
	print("received ", i1, " from c1\n")
case c2 <- i2:
	print("sent ", i2, " to c2\n")
case i3, ok := (<-c3):  // same as: i3, ok := <-c3
	if ok {
		print("received ", i3, " from c3\n")
	} else {
		print("c3 is closed\n")
	}
case a[f()] = <-c4:
	// same as:
	// case t := <-c4
	//	a[f()] = t
default:
	print("no communication\n")
}

for {  // send random sequence of bits to c
	select {
	case c <- 0:  // note: no statement, no fallthrough, no folding of cases
	case c <- 1:
	}
}

select {}  // block forever

12.13 Return 语句

  • Return statements- 官方文档
  • return语句是一个方法F执行完,附加提供一个或多个结果值。任何方法的defer方法在F返回结果前执行
1
2
3
4
5
6
ReturnStmt = "return" [ ExpressionList ] .

// 如果方法没有结果类型,返回语句必须不返回任何值
func noResult() {
	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”语句中禁止空表达式列表

1
2
3
4
5
6
func f(n int) (res int, err error) {
	if _, err := f(n-1); err != nil {
		return  // invalid return statement: err is shadowed
	}
	return
}

12.14 Break语句

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
BreakStmt = "break" [ Label ] .
// 如果有标签,则必须是包含“for”,“switch”或“select”语句的标签,并且该标签的执行终止。
OuterLoop:
	for i = 0; i < n; i++ {
		for j = 0; j < m; j++ {
			switch a[i][j] {
			case nil:
				state = Error
				break OuterLoop
			case item:
				state = Found
				break OuterLoop
			}
		}
	}

12.15 Continue 语句

  • Continue statements- 官方文档
  • “continue”语句在其post语句中开始内部“for”循环的下一次迭代。 “for”循环必须在同一个函数内。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
ContinueStmt = "continue" [ Label ] .
// 如果有一个标签,那么它必须是一个封闭的“for”语句,并且是执行进程的标签。
RowLoop:
	for y, row := range rows {
		for x, data := range row {
			if data == endOfRow {
				continue RowLoop
			}
			row[x] = data + bias(x, y)
		}
	}

12.16 Goto 语句

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
GotoStmt = "goto" Label .

goto Error

// 执行“goto”语句不得导致任何变量进入范围,这些变量尚未在goto的范围内。 例如,这个例子:
goto L  // BAD
	v := 3
L:

// 上面栗子是错误的,因为跳进Label L跳过了创建v

//块外的“goto”语句不能跳转到该块内的标签。 例如,这个例子:
if n%2 == 1 {
	goto L1
}
for n > 0 {
	f()
	n--
L1:
	f()
	n--
}

12.17 Fallthrough 语句

  • Fallthrough statements- 官方文档
  • “fallthrough”语句将控制转移到表达式“switch”语句中的下一个case子句的第一个语句。 它可能仅用作此类子句中的最终非空语句。

12.18 Defer statements

  • Defer statements- 官方文档
  • defer语句调用一个函数,它执行被推迟到外围函数返回之前,因为外围函数执行了return,到达这个函数到末尾,或者panic发生。
1
DeferStmt = "defer" Expression .

  这个表达式必须是一个方法或者函数调用;它不能用括号括起来。调用内置函数被限制,具体参考表达式语句。

  每次defer语句执行,调用的函数值和参数将照常计算并重新保存,但不会调用实际函数。而是在外围函数返回之前立即调用延迟函数,多个延迟函数以相反的顺序调用。也就是说,如果外围函数通过显式return语句返回,则在该return语句之前执行延迟函数。如果延迟函数为nil,则在外围函数执行时panic,而不是在执行“defer”语句时执行。

  例如,如果延迟函数是函数文字并且周围函数已命名在文字范围内的结果参数,则延迟函数可以在返回结果参数之前访问和修改结果参数。 如果延迟函数具有任何返回值,则在函数完成时将丢弃它们。 (另请参阅处理恐慌的部分。)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
lock(l)
defer unlock(l)  // unlocking happens before surrounding function returns

// prints 3 2 1 0 before surrounding function returns
for i := 0; i <= 3; i++ {
	defer fmt.Print(i)
}

// f returns 42
func f() (result int) {
	defer func() {
		// result is accessed after it was set to 6 by the return statement
		result *= 7
	}()
	return 6
}

13 内置方法

  • Built-in functions - 官方文档
  • 内置方法是预先声明的。它们调用方式和其他方法一样,但是一些内置方法接收一个类型而不是一个表达式作为第一个参数。内置方法不会有标准的Go类型,所以它们只会出现在调用表达式;它们不能作为方法值使用。

13.01 Close

  • Close - 官方文档
  • 对于channel c,内置方法close©记录这个通道停止发送任何数据。如果c只是一个接收channel,则报错。
  • 发送或关闭一个已经关闭的channel会导致运行时错误。
  • 关闭一个nil channel也会发送运行时错误。
  • 调用close以后,并且之前发送的值被接收完成,接收操作符会返回对应channel定义的类型的零值,并切不会阻塞。
  • 多值接收操作返回接收值以及信道是否关闭的指示

13.02 len()和cap()

Call Argument type Result
len(s) string type string length in bytes
[n]T, *[n]T array length (== n)
[]T slice length
map[K]T map length (number of defined keys)
chan T number of elements queued in channel buffer
cap(s) [n]T, *[n]T array length (== n)
[]T slice capacity
chan T channel buffer capacity

  slice的容量是底层数组分配的元素数量。任何时候都符合这个关系:$0 <= len(s) <= cap(s)$

  nil的slice、map、channel的长度为0。nil的slice、channel的容量为0

  s是字符串常量,表达式len(s)也是个常量。如果s是数组或者指向数组的指针,并且s不包含channel接收或者函数调用,则表达式len(s)和cap(s)也是常量;这种情况不计算s。否则len和cap都不是常量,并且需要计算s。

1
2
3
4
5
6
7
8
const (
	c1 = imag(2i)                    // imag(2i) = 2.0 is a constant
	c2 = len([10]float64{2})         // [10]float64{2} contains no function calls
	c3 = len([10]float64{c1})        // [10]float64{c1} contains no function calls
	c4 = len([10]float64{imag(2i)})  // imag(2i) is a constant and no function call is issued
	c5 = len([10]float64{imag(z)})   // invalid: imag(z) is a (non-constant) function call
)
var z complex128

13.03 new()

  • Allocation - 官方文档
  • 内置方法new 创建一个类型T,在运行时分配一个存储空间给对应变量,并且返回一个*T的值给变量。变量被初始化,请参考初始化值。
1
2
3
4
5
6
new(T)

// 举例说明
type S struct { a int; b float64 }
new(S)
// 分配内存给类型S的变量初始化它(a=0,	b=0.0)并且返回一个类型*S值包括地址

13.04 make() slices, maps and channels

  • Making slices, maps and channels - 官方文档
  • 内置方法make创建一个类型T,这个类型必须是slice、map或者channel,后面跟上特定类型的表达式。它返回一个类型T的值(注意不是*T)。内存被初始化,参考初始化值
Call Type T Result
make(T, n) slice slice of type T with length n and capacity n
make(T, n, m) slice slice of type T with length n and capacity m
make(T) map map of type T
make(T, n) map map of type T with initial space for approximately n elements
make(T) channel unbuffered channel of type T
make(T, n) channel buffered channel of type T, buffer size n

  每个参数的大小n、m必须是int类型或者无类型常量。常量参数大小必须是非负数并且是int类型;如果它是一个无类型的常量,则给出int类型。 如果提供n和m并且它们是常数,那么必须n<=m。 如果在运行时n为负或大于m,则会发生运行时错误。

1
2
3
4
5
6
s := make([]int, 10, 100)       // slice with len(s) == 10, cap(s) == 100
s := make([]int, 1e3)           // slice with len(s) == cap(s) == 1000
s := make([]int, 1<<63)         // illegal: len(s) is not representable by a value of type int
s := make([]int, 10, 0)         // illegal: len(s) > cap(s)
c := make(chan int, 10)         // channel with a buffer size of 10
m := make(map[string]int, 100)  // map with initial space for approximately 100 elements

  使用地图类型和大小提示n调用make将创建一个包含初始空间的地图,以容纳n个地图元素。 精确的行为取决于实现。

13.05 append()和copy()

  • Appending to and copying slices - 官方文档
  • 内置方法 append 和 copy 帮助slice操作符。对于这两个函数,结果与参数引用的内存是否重叠无关。
  • 可变函数 append 拼接零或多个值x到s,s的类型是S,S必须是slice类型。append方法返回这个类型S的slice。值x被传递给类型为…T的参数,其中T是S的元素类型,并且应用相应的参数传递规则。
  • 作为一种特殊情况,append还接受第一个可赋值给type[] byte的参数,第二个参数是string类型,后跟….这个表单附加了字符串的字节。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
append(s S, x ...T) S  // T is the element type of S


// 如果s的容量不足以保存额外的值,append方法分配一个新的足够大的底层数组来保存已经存在的切片元素和额外添加的元素。另外,append方法会重用底层数组。
s0 := []int{0, 0}
s1 := append(s0, 2)                // append a single element     s1 == []int{0, 0, 2}
s2 := append(s1, 3, 5, 7)          // append multiple elements    s2 == []int{0, 0, 2, 3, 5, 7}
s3 := append(s2, s0...)            // append a slice              s3 == []int{0, 0, 2, 3, 5, 7, 0, 0}
s4 := append(s3[3:6], s3[2:]...)   // append overlapping slice    s4 == []int{3, 5, 7, 2, 3, 5, 7, 0, 0}

var t []interface{}
t = append(t, 42, 3.1415, "foo")   //                             t == []interface{}{42, 3.1415, "foo"}

var b []byte
b = append(b, "bar"...)            // append string contents      b == []byte{'b', 'a', 'r' }
  • 内置方法copy复制切片元素从src源到dst目的,并返回复制的元素数量。两个参数必须有相同的元素类型T,并且必须可以分配给一个切片[]T。复制的元素数量是len(src) 和 len(dst)的最小值。作为一种特殊情况,copy还接受一个目标参数,该参数可分配给[]字节类型,其源参数为字符串类型。 此表单将字符串中的字节复制到字节切片中。
1
2
3
4
5
6
7
8
9
copy(dst, src []T) int
copy(dst []byte, src string) int

var a = [...]int{0, 1, 2, 3, 4, 5, 6, 7}
var s = make([]int, 6)
var b = make([]byte, 5)
n1 := copy(s, a[0:])            // n1 == 6, s == []int{0, 1, 2, 3, 4, 5}
n2 := copy(s, s[2:])            // n2 == 4, s == []int{2, 3, 4, 5, 4, 5}
n3 := copy(b, "Hello, World!")  // n3 == 5, b == []byte("Hello")

13.06 删除map元素

1
2
delete(m, k)  // remove element m[k] from map m
// 如果map m是nil或者m[k]不存在delete是无效操作

13.07 操作复合数

  • Manipulating complex numbers - 官方文档
  • 3个内置方法绑定和解除绑定复合元素。内置函数complex从从浮点实部和虚部建造一个复合值,real 和 imag 从复合值中取出实体和虚部。
1
2
3
complex(realPart, imaginaryPart floatT) complexT
real(complexT) floatT
imag(complexT) floatT
  • 参数类型和返回对应值。complex,这两个参数必须是相同的浮点类型,并且返回类型具有相应的浮点组成的复合类型:complex64对应float32参数,complex128对应float64参数。如果参数其中一个是无类型常量,它首先要显示转换为另一个参数类型。如果两个都是无类型常量,它们必须是非复合类型数或者它们的虚部必须是零,并且返回类型是无类型复合常量。
  • 对于real和imag,参数必须是复合类型,并且返回类型是对应的浮点类型:float32对应complex64,float64对应complex128的参数。如果参数是无类型常量,它必须是个数值,并且返回的值是无类型浮点常量。
  • 内置方法real和imag是complex的逆方法,所以对于一个复合类型Z的值z,$z = Z(complex(real(z), imag(z)))$
  • 如果操作对象都是常量,返回值也是常量。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var a = complex(2, -2)             // complex128
const b = complex(1.0, -1.4)       // untyped complex constant 1 - 1.4i
x := float32(math.Cos(math.Pi/2))  // float32
var c64 = complex(5, -x)           // complex64
var s uint = complex(1, 0)         // untyped complex constant 1 + 0i can be converted to uint
_ = complex(1, 2<<s)               // illegal: 2 assumes floating-point type, cannot shift
var rl = real(c64)                 // float32
var im = imag(a)                   // float64
const c = imag(b)                  // untyped constant -1.4
_ = imag(3 << s)                   // illegal: 3 assumes complex type, cannot shift

13.08 panic()和recover()

1
2
func panic(interface{})
func recover() interface{}

  当执行一个方法F,一个显示调用调用panic或者运行时错误终止F的执行。任何F的延迟方法常规执行。下一步,F的调用者运行的任何延迟函数都会运行,依此类推,直到执行goroutine中的顶级函数延迟。此时,程序终止并报告错误条件,包括panic参数的值。此终止序列称为panic。

1
2
3
panic(42)
panic("unreachable")
panic(Error("cannot parse"))

  recover方法允许管理goroutine中panic。认为一个方法G延迟一个方法D,D直接调用recover方法,并且panic发生在同一个goroutine的一个方法中,而G方法正在执行。当这个执行延迟方法到达D时,D调用recover的返回值成为传递panic调用的值。如果D返回正常,没有开始一个新的panic,panic序列停止。这种情况,G之间调用的函数状态和panic调用被丢弃,并恢复正常执行。然后运行在D之前由G推迟的任何函数,并且G的执行通过返回其调用者而终止。

  • 如果满足以下任何条件,则恢复的返回值为零:
    • panic的参数为nil
    • goroutine没有发生panic
    • recover没有被延迟函数直接调用

  下面的protect方法调用方法g并且保护调用者免受g引起的运行时错误。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func protect(g func()) {
	defer func() {
		log.Println("done")  // Println executes normally even if there is a panic
		if x := recover(); x != nil {
			log.Printf("run time panic: %v", x)
		}
	}()
	log.Println("start")
	g()
}

13.09 引导相关方法

  • Bootstrapping - 官方文档
  • 当前实现提供几个内置函数引导时很有用。记录这些函数是为了完整性,但不保证保留在语言中。 他们不会返回结果。
Function Behavior
print prints all arguments; formatting of arguments is implementation-specific
println like print but prints spaces between arguments and a newline at the end
  • 实现限制:print和println不需要接受任意参数类型,但必须支持打印布尔,数字和字符串类型。

14 包

  • Packages - 官方文档
  • Go程序是通过将包链接在一起构建的。反过来,包由一个或多个源文件构成,这些源文件一起声明属于包的常量,类型,变量和函数,并且可以在同一包的所有文件中访问。这些元素可以导出并在其他包中使用。

14.1 组织源文件

  • Source file organization - 官方文档
  • 每个源文件都包含一个package子句,它定义了它所属的包,后跟一个可能为空的import声明集,它声明了要使用其内容的包,后跟一组可能为空的函数、类型、变量、和常量声明。
1
SourceFile       = PackageClause ";" { ImportDecl ";" } { TopLevelDecl ";" } .

14.2 包子句

1
2
3
4
5
PackageClause  = "package" PackageName .
PackageName    = identifier .

// 包名不能为空白标识符
package math
  • 共享相同PackageName的一组文件形成包的实现。 实现可能要求包的所有源文件都位于同一目录中。

14.3 导入声明

1
2
3
ImportDecl       = "import" ( ImportSpec | "(" { ImportSpec ";" } ")" ) .
ImportSpec       = [ "." | PackageName ] ImportPath .
ImportPath       = string_lit .

  PackageName使用合格的标识符访问从包的源文件导出的标识符。它声明文件块中。如果包名被省略,默认使用包名的标识符。如果明确标出符号(.)代替名称,在该包的包块中声明的所有包的导出标识符将在导入源文件的文件块中声明,并且必须在没有限定符的情况下访问

  导入路径依的解释依赖于实现,但是它通常是已编译包的完整文件名的子字符串,并且可能与已安装的包的存储库相关。

  实现限制:编译器可以仅使用属于Unicode的L,M,N,P和S常规类别(不带空格的图形字符)的字符将ImportPaths限制为非空字符串,也可以排除字符!“#$%& ‘()*,:; <=>?[\ _] ^`{|}和Unicode替换字符U + FFFD。

  假设我们编译了一个包含package子句包math的包,它导出函数Sin,并将编译后的包安装在由“lib / math”标识的文件中。 此表说明了在各种类型的导入声明后导入包的文件中如何访问Sin。

Import declaration Local name of Sin
import “lib/math” math.Sin
import m “lib/math” m.Sin
import . “lib/math” Sin

  导入声明声明导入和导入的包之间的依赖关系。 软件包直接或间接导入自身或直接导入软件包而不引用任何导出的标识符是非法的。 要仅为其副作用(初始化)导入包,请使用空白标识符作为显式包名:

1
import _ "lib/math"

14.4 一个包示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package main

import "fmt"

// Send the sequence 2, 3, 4, … to channel 'ch'.
func generate(ch chan<- int) {
	for i := 2; ; i++ {
		ch <- i  // Send 'i' to channel 'ch'.
	}
}

// Copy the values from channel 'src' to channel 'dst',
// removing those divisible by 'prime'.
func filter(src <-chan int, dst chan<- int, prime int) {
	for i := range src {  // Loop over values received from 'src'.
		if i%prime != 0 {
			dst <- i  // Send 'i' to channel 'dst'.
		}
	}
}

// The prime sieve: Daisy-chain filter processes together.
func sieve() {
	ch := make(chan int)  // Create a new channel.
	go generate(ch)       // Start generate() as a subprocess.
	for {
		prime := <-ch
		fmt.Print(prime, "\n")
		ch1 := make(chan int)
		go filter(ch, ch1, prime)
		ch = ch1
	}
}

func main() {
	sieve()
}

15 程序初始化和执行

15.1 零值

  • The zero value - 官方文档
  • 当为一个变量分配存储,通过声明或者调用new,或者当一个新值被创建,通过复合字面量或者调用make,并没有提供显示初始化,变量或值获得一个默认值。这个变量或值的每个元素被设置为对应类型的零值:布尔的false,数值的0,string的”“,指针、方法、接口、切片、channel、map的nil。这种初始化是递归完成的,因此,例如,如果没有指定值,则结构数组的每个元素都将其字段归零。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
var i int
var i int = 0
// 它们是等效的

type T struct { i int; f float64; next *T }
t := new(T)

t.i == 0
t.f == 0.0
t.next == nil

// 通过下面的声明也是正确的
var t T

15.2 包初始化

  • Package initialization - 官方文档
  • 在一个包中,包级别的变量被初始化按照声明顺序,但必须在它们依赖的变量的后面。
  • 更清晰地描述,一个包级别的变量考虑初始化,如果它还没有初始化,并且没有初始化表达式或者它的初始化表达式没有依赖于还没有初始化的变量。初始化通过不断初始化下一个最早顺序的包级别变量,直到没有变量需要初始化。
  • 如果执行到最后有任何变量没有被初始化,这些变量是一个或多个初始化循环,这个程序是无效的。
  • 多个文件的变量声明顺序取决于编译器的载入顺序:第一个文件的变量声明在第二个文件变量之前,以此类推。
  • 依赖性分析不依赖于变量的实际值,仅依赖于源中对它们的词汇引用,而是可传递地进行分析。比如,变量x的初始化表达式引用变量y的函数,则x依赖于y。 特别地
    • 对变量或方法的引用是表示该变量或方法的标识符
    • 对函数m的引用是一个函数值或函数表达式的形式t.m,这个类型t不是一个接口类型,并且函数m在t的函数集中。t.m的结果值是无关紧要的
    • 一个变量、方法或函数x依赖变量y,如果x的初始化表达式或者body(方法或函数)包含对y的引用或方法、函数依赖于y。
  • 对每个包执行依赖分析;仅考虑引用当前包中声明的变量,函数和方法的引用。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
var (
	a = c + b
	b = f()
	c = f()
	d = 3
)

func f() int {
	d++
	return d
}
// 初始化顺序是	dbca

  变量也可以使用包块中声明的名为init的函数进行初始化,不带参数,也不使用结果参数。

1
func init() {  }

  单个源文件中,也可以为每个包定义多个这样的功能。在包块中,init标识符只能用于声明init函数,但标识符本身未声明。因此,无法从程序中的任何位置引用init函数。

  包没有引入其他包,通过初始化所有包级别变量,跟随在init方法后面,可能多个文件,出现在编译器中。如果包有引入包,引入包先于本身进行初始化。如果多个包引入一个包,这个引入的包只会初始化一次。引入的包,创建时保证它不会依赖循环初始化。

  包初始化-变量初始化和调用init方法-发生在单个goroutine,顺序执行,一次一个包。init方法可能在其他goroutine中执行,初始化代码并发执行。然而,初始化在init方法排序:在返回前一个之前,它不会调用下一个。

  为了确保可重现的初始化行为,建议构建系统以词法文件名顺序将属于同一个包的多个文件呈现给编译器。

15.3 程序执行

  • Program execution - 官方文档
  • 一个完整的程序是通过将一个名为main package的单个无转换包与它所导入的所有包一起链接来创建的。 主包必须具有包名称main,并声明一个不带参数的函数main并且不返回任何值。
1
func main() {  }
  • 程序执行从初始化主包然后调用main函数开始。
  • 当该函数调用返回时,程序退出。 它不等待其他(非主要)goroutines完成。

16 错误

1
2
3
4
5
6
7
type error interface {
	Error() string
}

// 它是表示错误条件的传统接口,其中nil值表示无错误。 例如,可以定义从文件读取数据的函数:

func Read(f *File, b []byte) (n int, err error)

17 运行时恐慌

  • Run-time panics - 官方文档
  • 执行错误(例如尝试索引数组超出范围)会触发运行时混乱,相当于使用实现定义的接口类型runtime.Error的值调用内置函数panic。 该类型满足预先声明的接口类型错误。 未指定表示不同运行时错误条件的确切错误值。
1
2
3
4
5
6
package runtime

type Error interface {
	error
	// and perhaps other methods
}

18 考虑系统环境

18.1 unsafe包

  • Package unsafe - 官方文档
  • 内置包unsafe,编译和容易通过引入”unsafe”,提供低级编程的工具,包括违反类型系统的操作符。
  • 使用unsafe包装必须手动检查以确保类型安全,并且可能无法携带。它提供了以下接口
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package unsafe

type ArbitraryType int  // shorthand for an arbitrary Go type; it is not a real type
type Pointer *ArbitraryType

func Alignof(variable ArbitraryType) uintptr
func Offsetof(selector ArbitraryType) uintptr
func Sizeof(variable ArbitraryType) uintptr

// 一个指针是指针类型但是一个指针值可能不会被解除引用。任何底层类型uintptr的指针或值可以被转换为底层类型Pointer,反之亦然。Pointer 和 uintptr 的相互转换是通过实现定义的。
var f float64
bits = *(*uint64)(unsafe.Pointer(&f))

type ptr unsafe.Pointer
bits = *(*uint64)(ptr(&f))

var p ptr = nil

// Alignof和Sizeof方法是任何类型的x表达式返回对齐或大小,分别表示假设变量v,好像v是通过var v=x 声明的。
// Offsetof方法使用一个选择器s.f,象征着一个struct的字段f通过 s 或者 *s 来表示,并返回字段的相对于结构地址的字节偏移量。
// 如果f是一个嵌入字段,必须可以通过结构的字段在没有指针间接的情况下访问它。 对于具有字段f的结构:
uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.f) == uintptr(unsafe.Pointer(&s.f))
// 计算机体系结构可能需要对齐存储器地址; 也就是说,对于变量的地址是因子的倍数,变量的类型的对齐。 函数Alignof采用表达任何类型变量的表达式,并以字节为单位返回(变量类型)的对齐方式。 对于变量x:
uintptr(unsafe.Pointer(&x)) % unsafe.Alignof(x) == 0
// 对AlignofOffsetof和Sizeof的调用是uintptr类型的编译时常量表达式

18.2 大小和对齐保证

type size in bytes
byte, uint8, int8 1
uint16, int16 2
uint32, int32, float32 4
uint64, int64, float64, complex64 8
complex128 16
  • 保证以下最小对齐属性:
    • 对于一个任何类型变量x:unsafe.Alignof(x) 至少是1
    • 对于结构类型变量x:unsafe.Alignof(x) 每个字段f的所有值的unsafe.Alignof(x.f)中的最大值,但至少是1
    • 对于数组类型变量x:unsafe.Alignof(x)与数组元素类型的变量的对齐方式相同。

  如果结构或数组类型不包含大小大于零的字段(或元素),则其大小为零。 两个不同的零大小变量在内存中可能具有相同的地址。