引言
这是一份阅读Go语言手册的读书笔记。Go语言中文手册可见Go中文手册。
Go是一种强类型化的语言,具有垃圾回收机制,并显式支持并发编程。 程序由包构造,以此来提供高效的依赖管理功能。当前实现使用传统的“编译-链接”模型来生成可执行的二进制文件。
词法元素
注释
注释与C++一致,有如下两种形式:
- 行注释 以// 开始,至行尾结束。一条行注释视为一个换行符。
- 块注释 以 /* 开始,至 */ 结束。 块注释在包含多行时视为一个换行符,否则视为一个空格。
注释不可嵌套。
标记
标记构成Go语言的词汇,有4种类型:标识符,关键字, 运算符与分隔符以及字面。
空白符由空格、横向制表符、回车和换行符组成,除非用它来分隔会结合成单个的标记, 否则它将被忽略。此外,换行符或EOF(文件结束符)会触发分号的插入。
分号
正式语法使用分号 “;” 作为一些生成式的终止符。Go程序会使用以下两条规则来省略大多数分号:
- 当输入被分解成标记时,若该行的行末标记为以下标记之一,分号就会被自动插入到该标记流中的非空行末处:
- 标识符
- 整数, 浮点数, 虚数, 符文或 字符串 字面
- 关键字 break, continue, fallthrough 或 return
- 运算符与分隔符 ++, --, ), ]或 }
- 为允许复合语句占据单行,闭合的 “)” 或 “}” 之前的分号可以省略
标识符
标识符被用来命名程序实体,一个标识符由一个或多个字母和数字组成。 标识符的第一个字符必须是字母(下划线也被认定为字母):
字母 = unicode字母 | "_" .
标识符 = 字母 { 字母 | unicode数字 }
关键字
以下为保留关键字,不能用作标识符:
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
运算符与分隔符
以下字符序列表示运算符,分隔符和其它特殊标记:
+ & += &= && == != ( )
- | -= |= || < <= [ ]
* ^ *= ^= <- > >= { }
/ << /= <<= ++ = := , ;
% >> %= >>= -- ! ... . :
&^ &^=
整数字面
整数字面由数字序列组成,默认为十进制数,非十进制数由特殊前缀定义: 0 为八进制数前缀,0x 或 0X为十六进制数前缀。 在十六进制数字面中,字母 a-f 或 A-F 表示值10到15。
整数字面 = 十进制数字面 | 八进制数字面 | 十六进制数字面 .
十进制数字面 = ( "1" … "9" ) { 十进制数字 } .
八进制数字面 = "0" { 八进制数字 } .
十六进制数字面 = "0" ( "x" | "X" ) 十六进制数字 { 十六进制数字 } .
浮点数字面
浮点数字面由十进制浮点常量表示。 它由整数部分,小数点,小数部分和指数部分构成。整数部分与小数部分由十进制数字组成; 指数部分由一个 e 或 E 紧跟一个带可选正负号的十进制指数构成。 整数部分或小数部分可以省略;小数点或指数亦可省略。
浮点数字面 = 十进制数 "." [ 十进制数 ] [ 指数 ] |
十进制指数 |
"." 十进制数 [ 指数 ] .
十进制数 = 十进制数字 { 十进制数字 } .
指数 = ( "e" | "E" ) [ "+" | "-" ] 十进制数 .
0.
.25
6.67428e-11
.25e+10
虚数字面
虚数字面由十进制复数常量的虚部表示。 它由浮点数字面或十进制整数紧跟小写字母 i
构成。
虚数字面 = (十进制数 | 浮点数字面) "i" .
5i
2.71828i
1.e+0i
符文字面
符文字面由一个符文常量表示。一个符文字面由围绕在单引号中的一个或更多字符表示。在引号内,除单引号和换行符外的任何字符都可出现。引号内的单个字符通常代表该字符的Unicode值自身, 而以反斜杠开头的多字符序列则会编码为不同的形式。
反斜杠转义允许用ASCII文本来编码任意值。将整数值表示为数字常量有4种方法: \x
紧跟2个十六进制数字;\u
紧跟4个十六进制数字; \U
紧跟8个十六进制数字;单个\
紧跟3个八进制数字。
尽管这些表示方式都会产生整数,其有效范围却不相同。八进制转义只能表示 0 到 255 之间的值。 十六进制转义则视其结构而定。转义符 \u 和 \U 表示Unicode码点, 因此其中的一些值是非法的,特别是那些大于 0x10FFFF 的值和半替代值。
符文字面 = "'" ( unicode值 | 字节值 ) "'" .
unicode值 = unicode字符 | 小Unicode值 | 大Unicode值 | 转义字符 .
字节值 = 八进制字节值 | 十六进制字节值 .
八进制字节值 = `\` 八进制数字 八进制数字 八进制数字 .
十六进制字节值 = `\` "x" 十六进制数字 十六进制数字 .
小Unicode值 = `\` "u" 十六进制数字 十六进制数字 十六进制数字 十六进制数字 .
大Unicode值 = `\` "U" 十六进制数字 十六进制数字 十六进制数字 十六进制数字
十六进制数字 十六进制数字 十六进制数字 十六进制数字 .
转义字符 = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | "'" | `"` ) .
'\377'
'\x07'
'\xff'
'\u12e4'
'\U00101234'
'aa' // 非法:太多字符
'\xa' // 非法:太少16进制数字
'\0' // 非法:太少8进制数字
'\uDFFF' // 非法:半代理值
'\U00110000' // 非法:无效Unicode码点
在反斜杠后,某些单字符转义表示特殊值:
\a U+0007 警报或铃声
\b U+0008 退格
\f U+000C 换页
\n U+000A 换行
\r U+000D 回车
\t U+0009 横向制表
\v U+000b 纵向制表
\\ U+005c 反斜杠
\' U+0027 单引号(仅在符文字面中有效)
\" U+0022 双引号(仅在字符串字面中有效)
所有其它以反斜杠开始的序列都是非法的。
字符串字面
字符串字面表示字符串常量,可通过连结字符序列获得。 它有两种形式:原始字符串字面和解译字符串字面。
原始字符串字面为反引号 ``之间的字符序列,在该引号内, 除反引号外的任何字符都是合法的。反斜杠没有特殊意义且字符串可包含换行符。原始字符串字面中的回车符将会从原始字符串的值中丢弃。
解译字符串字面为双引号 “” 之间的字符序列。在该引号内,反斜杠转义序列如同在符文字面中一样以相同的限制被解译。
字符串字面 = 原始字符串字面 | 解译字符串字面 .
原始字符串字面 = "`" { unicode字符 | 换行符 } "`" .
解译字符串字面 = `"` { unicode值 | 字节值 } `"` .
`abc` // 等价于 "abc"
`\n
\n` // 等价于 "\\n\n\\n"
"Hello, world!\n"
"\uD800" // 非法:半代理值
"\U00110000" // 非法:无效的Unicode码点
下面的字符串都表示相同的内容:
"日本語" // UTF-8输入的文本
`日本語` // UTF-8输入的原始字面文本
"\u65e5\u672c\u8a9e" // 显式的Unicode码点
"\U000065e5\U0000672c\U00008a9e" // 显式的Unicode码点
"\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e" // 显式的UTF-8字节
常量
常量包含布尔常量,字符常量, 整数常量,浮点数常量, 复数常量和字符串常量。 字符,整数,浮点数和复数常量统称为数值常量。
尽管数值常量在该语言中可拥有任意精度, 但编译器可能使用其有限精度的内部表示来实现它们。即,每个实现必须:
- 使用至少256位表示整数常量
- 使用至少256位表示浮点常量,包括复数常量及尾数部分; 使用至少32位表示指数符号。
- 若无法精确表示一个整数常量,则给出一个错误。
- 若由于溢出而无法表示一个浮点或复数常量,则给出一个错误。
- 若由于精度限制而无法表示一个浮点或复数常量,则舍入为最近似的可表示常量。
类型
类型可通过 类型名或类型字面指定, 它将根据之前声明的类型组成新的类型。
布尔值,数值与字符串类型的实例的命名是预声明的。 数组,结构,指针,函数,接口,切片,映射和信道这些复合类型可由类型字面构造。
变量的静态类型(或类型)是通过其声明定义的类型。 接口类型的变量也有一个独特的动态类型,这是在运行时存储在变量中的值的实际类型。 动态类型在执行过程中可能会有所不同,但对于接口变量的静态类型,它总是可赋值的。 对于非接口类型,其动态类型始终为其静态类型。(类似c++的运行时多态?)
每个类型 T 都有一个 基本类型:若 T 为预声明类型或类型字面, 其相应的基本类型为 T 本身。否则,T的基本类型为其类型声明中所依据类型的基本类型。
type T1 string
type T2 T1 /
type T3 []T1
type T4 T3
T1 和 T2 的基本类型为 string。 T3 和 T4 的基本类型为 []T1 。
方法集
类型可拥有一个与其相关联的 方法集(§接口类型,§方法声明)。 接口类型的方法集为其接口。其它任意类型 T 的方法集由所有带接收者类型 T 的方法组成。与指针类型 *T 相应的方法集为所有带接收者 *T 或 T 的方法的集(就是说,它也包含 T 的方法集)。任何其它类型都有一个空方法集。 在方法集中,每个方法都必须有唯一的方法名。一个类型的方法集决定了其所实现的接口 与可使用该类型接收者调用的方法。
布尔类型
布尔类型 表示由预声明常量 true 和 false所代表的布尔值的集。 预声明的布尔类型为 bool
。
数值类型
数值类型表示整数值和浮点数值的集。 架构中立的预声明数值类型为:
uint8 所有无符号 8位整数集(0 到 255)
uint16 所有无符号16位整数集(0 到 65535)
uint32 所有无符号32位整数集(0 到 4294967295)
uint64 所有无符号64位整数集(0 到 18446744073709551615)
int8 所有带符号 8位整数集(-128 到 127)
int16 所有带符号16位整数集(-32768 到 32767)
int32 所有带符号32位整数集(-2147483648 到 2147483647)
int64 所有带符号64位整数集(-9223372036854775808 到 9223372036854775807)
float32 所有IEEE-754 32位浮点数集
float64 所有IEEE-754 64位浮点数集
complex64 所有带float32实部和虚部的复数集
complex128 所有带float64实部和虚部的复数集
byte uint8的别名
rune int32的别名
当不同的数值类型混合在一个表达式或赋值操作中时,必须进行类型转换。
字符串类型
字符串是不可变的: 一旦被创建,字符串的内容就不能更改。预声明的字符串类型为string
。
字符串 s 的长度可使用内建函数len
获取。若该字符串为常量,则其长度即为编译时常量。 字符串的字节可通过整数 0 至 len(s)-1 访问。 获取这样一个元素的地址是非法的;若 s[i] 为字符串的第 i 个字节,&s[i] 就是无效的。
数组类型
数组是单一类型元素的编号序列,该单一类型称为元素类型。
数组类型 = "[" 数组长度 "]" 元素类型 .
数组长度 = 表达式 .
元素类型 = 类型 .
长度是数组类型的一部分,其求值结果必须可表示为 int 类型的非负常量。 数组 a 的长度可使用内建函数 len获取, 其元素可通过整数下标 0 到 len(a)-1 寻址。 数组类型总是一维的,但可组合构成多维的类型。
[32]byte
[2*N] struct { x, y int32 }
[1000]*float64 //float64指针构成的数组
[3][5]int //二维数组,3为第一维长度,5为第二维长度
[2][2][2]float64 // 多维数组
切片类型
切片是数组连续段的引用及包含此数组的元素的编号序列。 切片类型表示元素类型为数组的所有切片的集。未初始化切片的值为 nil。切片(slice)是go中最重要的类型,要熟练掌握。
切片类型 = "[" "]" 元素类型 .
类似于数组,切片是可索引的且拥有一个长度。切片 s 的长度可通过内建函数len
获取;不同于数组的是,切片可在执行过程中被改变, 其元素可通过整数 0 到 len(s)-1 寻址。 给定元素的切片下标可能小于它在其基本数组中的下标。
切片一旦初始化,就总是伴随着一个包含其元素的基本数组。 因此,切片与其数组及其它本数组的切片共享存储; 与此相反,不同的数组总是表示其不同的存储。
切片的基本数组可扩展其切片的结尾。容量是该扩展的量度: 它是切片的长度和切片往后数组的长度之和;长度达到其容量的切片可通过从原切片‘切下’一个新的来创建。 切片 a 的容量可使用内建函数 cap(a)
获取。
给定元素类型 T 的一个新的,已初始化的切片值使用内建函数make
创建, 它需要一个切片类型和指定其长度与可选容量的形参:
make([]T, length)
make([]T, length, capacity)
直接生成切片与分配数组后再对其进行切片相同,因此这两个例子的结果为相同的切片:
make([]int, 50, 100)
new([100]int)[0:50]
结构类型
结构是已命名的元素序列,其中每一个元素都有一个名字和类型。字段名可显示地指定(标识符列表)或隐式地指定(匿名字段)。 在结构中,非空白字段名必须是唯一的。
结构类型 = "struct" "{" { 字段声明 ";" } "}" .
字段声明 = (标识符列表 类型 | 匿名字段) [ 标注 ] .
匿名字段 = [ "*" ] 类型名 .
标注 = 字符串字面 .
// 空结构.
struct {}
// 带6个字段的结构
struct {
x, y int
u float32
_ float32 // 填充
A *[]int
F func()
}
通过有类型而无显式字段名声明的字段为 匿名字段,亦称为嵌入式字段或该结构中此种类型的嵌入。 这种字段类型*必须作为一个类型名 T 或一个非接口类型名的指针 T来实现, 且 T 本身不能为指针类型。未限定类型名的行为类似于字段名。
// 带类型为T1,*T2,P.T3和*P.T4的4个匿名字段的结构
struct {
T1 // 字段名为T1
*T2 // 字段名为T2
P.T3 // 字段名为T3
*P.T4 // 字段名为T4
x, y int // 字段名为x和y
}
以下为非法声明,因为字段名在结构类型中必须是唯一的:
struct {
T // 与匿名字段*T及*P.T相冲突
*T // 与匿名字段T及*P.T相冲突
*P.T // 与匿名字段T及*T相冲突
}
看下面的实际使用例子:
package main
import "fmt"
type Person struct{
name string
sex string
age int32
}
type Student struct{
Person
id int32
}
func main() {
s1 := Student{Person{"zwf", "man", 20}, 10047813}
fmt.Println(s1)
}
输出如下:
指针类型
指针类型表示一个所有给定类型变量的指针的集,称为指针的 基础类型。 未初始化的指针的值为 nil。
指针类型 = "*" 基础类型 .
基础类型 = 类型 .
*Point
*[4]int
函数类型
函数类型表示所有带相同形参和返回类型的集。未初始化的函数类型变量的的值为 nil。
函数类型 = "func" 签名 .
签名 = 形参 [ 结果 ] .
结果 = 形参 | 类型 .
形参 = "(" [ 形参列表 [ "," ] ] ")" .
形参列表 = 形参声明 { "," 形参声明 } .
形参声明 = [ 标识符列表 ] [ "..." ] 类型 .
函数可以指定多个返回值。
函数签名中的最后一个形参可能有一个带 ...
前缀的类型。 带这样形参的函数被称为 变参函数 它可接受零个或多个实参的函数。
在形参或结果的列表中,其名称(标识符列表)必须都存在或都不存在。 若存在,则每个名称代表一个指定类型的项(形参或结果),所有在签名中的非 空白名称必须是唯一的。 若不存在,则每个类型代表一个此类型的项。若恰好有一个未命名的值,它可能写作一个不加括号的类型, 除此之外,形参和结果的列表总是在括号中:
type FuncA func(int, int, int) (bool, bool) //正确,此函数类型有两个返回值
type FuncB func(i int, int, int) (bool, bool) //错误,形参列表混用有命名和无命名的选项
type FuncC func(int, int, int) (b bool, bool) //错误,返回值列表混用有命名和无命名的选项
func(prefix string, values ...int)//可变参数列表
接口类型
接口类型指定一个称为接口的方法集。 接口类型变量可存储任何带方法集类型的值,该方法集为此接口的超集。 这种类型表示 实现此接口。未初始化的接口类型变量的值为 nil。
接口类型 = "interface" "{" { 方法实现 ";" } "}" .
方法实现 = 方法名 签名 | 接口类型名 .
方法名 = 标识符 .
接口类型名 = 类型名 .
在一个接口类型中,每个方法必须有唯一的名字
// 一个简单的File接口
interface {
Read(b Buffer) bool
Write(b Buffer) bool
Close()
}
一个接口可通过包含一个名为 T 的接口类型来代替一个方法的实现。 这称之为嵌入接口,其效果等价于在接口中显式枚举出 T 中的方法。
type Man interface{
run()
walk()
}
type SuperMan interface{
Man //等于枚举了Man中的方法
fly()
}
接口类型不能直接或间接嵌套自身。
映射类型
映射即map,是一个同种类型元素的无序组,该类型称为元素类型; 映射通过另一类型唯一的 键 集索引,该类型称为键类型。 未初始化的映射值为 nil。
映射类型 = "map" "[" 键类型 "]" 元素类型 .
键类型 = 类型 .
比较操作符 == 和 != 必须由键类型的操作数完全定义; 因此键类型不能是函数,映射或切片。若该键类型为接口类型,这些比较运算符必须由动态键值定义; 失败将导致一个运行时恐慌(即异常)。
元素的数量称为长度。 对于映射 m,其长度可使用内建函数 len
获取并可在执行时更改。元素可在执行时使用赋值来添加并通过 下标表达式 来检索;它们也可通过内建函数 delete
删除。
一个新的,空的映射值使用内建函数 make 创建, 它使该映射类型和可选容量作为实参提示:
make(map[string]int)
make(map[string]int, 100)
初始容量不能限定映射的大小,因为映射会自增长来适应存储在其中的项数。
信道类型
信道提供一种机制使两个并发执行的函数同步执行,并通过传递具体元素类型的值来通信。 未初始化的信道值为 nil。
信道类型 = ( "chan" [ "<-" ] | "<-" "chan" ) 元素类型 .
<-
操作符指定信道的 方向,发送 或 接收。 若没有给定方向,那么该信道就是 双向的。 信道可通过类型转换 或 赋值被强制为只发送或只接收。
chan T // 可以被用来发送和接收类型T的值
chan<- float64 // 只能被用来发送浮点数
<-chan int // 只能被用来接收整数
一个新的,已初始化的信道值可使用内建函数 make 创建, 它接受信道类型和一个可选的容量作为实参:
make(chan int, 100)
容量根据元素的数量设置信道中缓存的大小。若容量大于零,则信道是异步的: 若缓存未满(发送)或非空(接收),则通信操作无阻塞成功,且元素在发送序列中被接收。 若容量为零或无,则只有当发送者和接收者都做好准备时通信才会成功。 nil 信道永远不会准备好通信。
信道可通过内建函数close
关闭; 接收操作符的多值赋值形式可测试信道是否关闭。
类型与值的性质
类型标识
一个已命名类型和一个未命名类型总不相同。若两个未命名类型其相应的类型字面相同, 那么它们的类型相同。类型相同判断遵循下面的规则:
- 若两个数组类型其元素类型相同且长度相同,那么它们的类型相同。
- 若两个切片类型其元素类型相同,那么它们的类型相同。
- 若两个结构类型其字段序列相同,相应字段名相同,类型相同,标注相同,那么它们的类型相同。 两个匿名字段其名字被认为相同。出自不同包的小写字段名总不相同。
- 若两个指针类型其基础类型相同,那么它们的类型相同。
- 若两个函数类型其形参个数相同,返回值相同,相应形参类型相同,返回值类型相同, 两函数都可变或都不可变,那么它们的类型相同。形参和返回值名无需匹配。
- 若两个接口类型其方法集相同,名字相同,函数类型相同,那么它们的类型相同。 出自不同包的小写方法名总不相同。两接口类型是否相同与方法的次序无关。
- 若两个映射类型其键值类型相同,那么它们的类型相同。
- 若两个信道类型其值类型相同,方向相同,那么它们的类型相同。
给定如下声明:
type (
T0 []string
T1 []string
T2 struct{ a, b int }
T3 struct{ a, c int }
T4 func(int, float64) *T0
T5 func(x int, y float64) *[]string
)
T0 和 T1 是不同的,因为它们有不同声明的类型命名; func(int, float64) *T0 和 func(x int, y float64) *[]string 是不同的, 因为 T0 不同于 []string。
可赋值性
在下列情况下,值 x 可赋予 类型为 T 的变量:
- 当 x 的类型和 T 相同时。
- 当 x 的类型 V 和 T 有相同的 基本类型 且在 V 或 T 中至少有一个不是已命名类型时。
- 当 T 为接口类型且 x 实现了 T时。
- 当 x 为双向信道值、T 为信道类型、 x 的类型 V 和 T 的元素类型相同且在 V 或 T 中至少有一个不是已命名类型时。
- 当 x 为预声明标识符 nil 且 T 为指针、函数、切片、映射、通道或接口类型时。
- 当 x 为无类型化,可通过类型 T 的值来表示的 常量时。
任何类型都可赋予空白标识符_
.
块
块 为一对大括号括住的声明和语句。除显式源码块外,还有隐式块:
- 全域块 包含所有的Go源码文本。
- 每个包都有包含其所有Go源码文本的 包块。
- 每个文件都有包含其所有Go源码文本的 文件块。
- 每个 if、for 和 switch 语句都被视为处于其自身的隐式块中。
- 每个 switch 或 select 语句中的子句其行为如同隐式块。
块可嵌套并会影响作用域。
声明与作用域
在程序中,每个标识符都必须被声明。同一标识符不能在同一块中声明两次,且在文件与包块中不能同时声明。
Go使用块表示词法作用域:
- 预声明标识符的作用域为全域块。
- 在顶级(即在任何函数之外)声明的表示常量、类型、变量或函数 (而非方法)的标识符其作用域为该包块。
- 已导入包的包名作用域为包含该导入声明的文件块。
- 表示方法接收器、函数形参或返回值变量的标识符,其作用域为该函数体。
- 在函数中声明为常量或变量的标识符,其作用域始于该函数中具体常量实现或变量实现 (ShortVarDecl表示短变量声明)的结尾,止于最内部包含块的结尾。
- 在函数中声明为类型的标识符,其作用域始于该函数中具体类型实现的标识符, 止于最内部包含块的结尾。
在块中声明的标识符可在其内部块中重新声明。
包子句(比如package main
)并非声明,包名不会出现在任何作用域中。 其目的是为了识别该文件是否属于相同的包并为导入声明指定默认包名。
标签作用域
标签通过标签语句声明,并用于 break、continue 和 goto 语句:
Error: log.Panic("error encountered") //设定标签Error
定义不会使用的标签是非法的。
与其它标识符相反,标签并不限定作用域且与非标签标识符并不冲突。 标签的作用域为除任何嵌套函数体外其声明的函数体。
空白标识符
通过下划线字符 _
表示, 它可像其它标识符一样用于声明,但该标识符不能传入一个新的绑定。
预声明标识符
在全域块中,以下标识符是隐式声明的:
类型:
bool byte complex64 complex128 error float32 float64
int int8 int16 int32 int64 rune string
uint uint8 uint16 uint32 uint64 uintptr
常量:
true false iota
零值:
nil
函数:
append cap close complex copy delete imag len
make new panic print println real recover
可导出标识符
标识符可被导出以允许从另一个包访问。同时符合以下条件即为可导出标识符:
- 标识符名的第一个字符为Unicode大写字母(Unicode类别“Lu”)
- 该标识符在包块中已声明或为字段名或 方法名。
标识符的唯一性
若两个标识符拼写不同,或它们出现在不同的包中且未导出,那么它们就是不同的。否则,它们就是相同的。
常量声明
常量声明将一个标识符(即常量名)列表绑定至一个常量表达式列表的值。 标识符的数量必须与表达式的数量相等,且左边第 n 个标识符会绑定至右边的第 n 个表达式的值。
若指定了类型,所有常量都将获得该类型实现,且该表达式对于该类型必须是 可赋值的。若类型被省略,则该常量将获得其对应表达式的具体类型。 若该表达式值为无类型化常量,则其余已声明无类型化常量与该常量标识符表示其常量值。
const Pi float64 = 3.14159265358979323846
const zero = 0.0 // 无类型化浮点常量
const (
size int64 = 1024
eof = -1 // 无类型化整数常量
)
const a, b, c = 3, 4, "foo" // a = 3, b = 4, c = "foo", 无类型化整数和字符串常量
const u, v float32 = 0, 3 // u = 0.0, v = 3.0
在 const 后括号中的声明列表,除第一句声明外,任何表达式列表都可省略。 若前面第一个非空表达式有类型,那么这样的空列表等价于该表达式原文和类型的代换。 因此,省略表达式的列表等价于重复前面的列表。其标识符的数量必须与上一个表达式的数量相等:
const (
i1 int64 = 1
i2 //i2为1
f11, f12 float32 = 1.1, 1.2
f21, f22 //f21为1.1, f21为1.2
)
iota
在常量声明中预声明标识符 iota 表示连续的无类型化整数 常量。每当保留字 const 出现在源码中和每个常量实现增量后,它都会被重置为0。它可被用来构造相关常量的集:
const (
c0 = iota // c0 == 0
c1 // c0 == 1
c2 // c1 == 2
)
const (
a = 1 << iota // a == 1 (iota已重置)
b = 1 << iota // b == 2
c = 1 << iota // c == 4
)
在表达式列表中,每个 iota 的值都相同,因为它只在每个常量实现后增量:
const (
bit0, mask0 = 1 << iota, 1<<iota - 1 // bit0 == 1, mask0 == 0, 注意mask0并不是1
bit1, mask1 // bit1 == 2, mask1 == 1
_, _ // 跳过 iota == 2
bit3, mask3 // bit3 == 8, mask3 == 7
)
类型声明
类型声明将标识符、类型名 绑定至一个与现存类型有相同的 基本类型的新类型。新类型不同于现有类型。
type IntArray [16]int
type (
Point struct{ x, y float64 }
Polar Point
)
声明类型不继承任何方法绑定到现存类型, 但接口类型或复合类型元素的方法集保持不变:
// Mutex为带有Lock和Unlock两个方法的数据类型.
type Mutex struct { /* Mutex字段 */ }
func (m *Mutex) Lock() { /* Lock实现*/ }
func (m *Mutex) Unlock() { /* Unlock实现*/ }
// NewMutex和Mutex拥有相同的组成,但它的方法集为空.
type NewMutex Mutex
// PtrMutex的基础类型的方法集保持不变.
// 但PtrMutex的方法集为空.
type PtrMutex *Mutex
// *PrintableMutex的方法集包含方法
// Lock和Unlock绑定至其匿名字段Mutex.
type PrintableMutex struct {
Mutex
}
// MyBlock为与Block拥有相同方法集的接口类型.
type MyBlock Block
类型声明可用来定义不同的布尔值、数字或字符串类型并对其附上方法:
// 类型声明重定义int类型
type TimeZone int
// 为其附加String接口
func (tz TimeZone) String() string {
return fmt.Sprintf("GMT + %dh", tz)
}
//调用
func main() {
var tz TimeZone = 10
fmt.Println(tz.String())
}
变量声明
变量声明将一个标识符绑定至一个创建的变量并赋予其类型和可选的初始值,声明使用var
关键字.。
若给定一个表达式列表,则变量通过按顺序将该表达式赋予该变量来初始化; 所有表达式必须用尽且所有变量根据它们初始化。否则,每个变量初始化为零值。
若该类型已存在,每个变量都赋予该类型。否则,该类型根据该表达式列表赋值。
若该类型不存在且其对应表达式计算结果为无类型化常量, 则该声明变量的类型由其赋值描述。
若在函数体内声明不会使用的变量,编译器可能将其判定为非法。
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] // 映射检查;只与“found”有关
短变量声明
短变量声明是有初始化表达式无类型化的常规变量声明的缩写,使用如下语法:
短变量声明 = 标识符列表 ":=" 表达式列表
等价于
"var" 标识符列表 = 表达式列表 .
短变量声明只能出现在函数内部
func main() {
i, j := 0, 10
f := func() int { return 7 }
fmt.Println(i, j, f())
}
不同于常规变量声明,在至少有一个非空白变量时, 短变量声明可在相同块中,对原先声明的变量以相同的类型重声明。因此,重声明只能出现在多变量短声明中。 重声明不能生成新的变量;它只能赋予新的值给原来的变量:
field1, offset := nextField(str, 0)
field2, offset := nextField(str, offset) // 重声明 offset
a, a := 1, 2 // 非法:重复声明了 a,或者若 a 在别处声明,但此处没有新的变量
函数声明
函数声明将标识符,即 函数名 绑定至函数。
函数声明 = "func" 函数名(形参列表)(返回值列表) [ 函数体 ] .
func swap(x, y int) (int, int) {
return y, x
}
函数声明可省略函数体。这样的标识符为Go外部实现的函数提供签名:
func flushICache(begin, end uintptr) // 外部实现
方法声明
方法为带 接收者 的函数。方法声明将标识符,即 方法名 绑定至方法。 它也将该接收者的 基础类型 关联至该方法:
方法声明 = "func" 接收者 方法名 签名 [ 函数体 ] .
接收者 = "(" [ 标识符 ] [ "*" ] 基础类型名 ")" .
基础类型名 = 标识符 .
接收者类型必须为形式 T 或 *T,其中 T 为类型名。 由 T 表示的类型称为接收者的 基础类型, 它不能为指针或接口类型且必须在同一包中声明为方法。 也就是说,该方法被绑定至基础类型且该方法名只对其内部此类型选择者可见:
//Vector 向量
type Vector struct{ x, y float64 }
//Length 获取向量的长度
func (p *Vector) Length() float64 {
return math.Sqrt(p.x*p.x + p.y*p.y)
}
//Scale 使向量按比例伸缩
func (p *Vector) Scale(factor float64) *Vector {
return &Vector{p.x * factor, p.y * factor}
}
func main() {
v := Vector{1, 1}
fmt.Println(v.Length(), v.Scale(2).Length())
}
输出如下:
方法的类型就是将接收者作为第一个实参的函数类型。例如,上面例子中的方法 Scale 拥有类型
func(p *Point, factor float64)
然而,通过这种方式声明的函数不是方法。
表达式
限定标识符
限定标识符.
为使用包名前缀限定的标识符。包名与标识符均不能为空白的,限定标识符用于访问另一个包中的标识符,它必须被导入。 标识符必须是已导出且在该包的包块中声明:
math.Sin
复合字面
复合字面每次为结构、数组、切片、映射构造值,或创建一个新值时,它们都会被求值。 它们由值的类型后跟一个大括号括住的列表组成。元素可为单个表达式或一个键-值对。
字面类型必须为结构、数组、切片或映射类型(语法规则强制实施此约束,除非该类型作为类型名给定)。 表达式的类型对于其各自的字段、元素以及该字面类型的键类型必须为可赋值的, 即没有附加转换。作为结构字面的字段名,即数组和切片的下标以及映射字面的键,其键是可解译的。 对于映射字面,所有元素都必须有键。指定多个具有相同字段名或常量键值的元素会产生一个错误。
以下规则适用于结构字面:
- 键必须为字面类型中声明的字段名
- 不包含任何键的元素列表必须按字段的声明顺序列出每个结构字段的元素。
- 若其中任何一个元素有键,那么每个元素都必须有键。
- 包含键的元素列表无需每个结构字段都有元素。被忽略的字段会获得零值
- 字面可忽略元素列表;这样的字面对其类型求值为零值。
- 为属于不同包的结构的未导出字段指定一个元素会产生一个错误。
给定类型声明:
//Point32 表示一个点
type Point32 struct{ x, y, z float64 }
//Line 表示线
type Line struct{ p, q Point32 }
其变量声明如下:
origin := Point3D{} // Point3D 为零值
line := Line{origin, Point3D{y: -4, z: 12.3}} // line.q.x 为零值,
注意列表中为键值对,必须用:
而不能用=
。
可以用取址符&
为复合字面的唯一实例生成指针:
var pointer *Point3D = &Point3D{y: 1000}
数组字面的长度为字面类型指定的长度。 若元素少于字面提供的长度,则缺失的元素会置为该数组元素类型的零值。 向超出数组下标范围的下标值提供元素会产生一个错误。
记法 ...
指定一个数组,其长度等于最大元素下标加一:
buffer := [10]string{} // len(buffer) == 10,默认为空字符串
intSet := [6]int{1, 2, 3, 5} // len(intSet) == 6,最后两个数字为0
days := [...]string{"Sat", "Sun"} // len(days) == 2
切片字面描述全部的基本数组字面。因此,切片字面的长度和容量为其最大元素下标加一。下面是切片操作应用到数组的捷径:
tmp : [n]T{x1, x2, ... xn}
tmp[0:n]
实例:
tmp := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
var t = tmp[5:9]
for k, v := range t {
fmt.Println(k, v)
}
在数组、切片或映射类型 T 的复合字面中,若其元素本身亦为复合字面, 且该复合字面的元素类型与 T 的相同,则可省略其各自的元素类型。 类似地,当元素类型为 *T 时,若其元素为复合字面的地址,则可省略 &T:
points := [...]Point{{1.0, 1.0}, {2.0, 2.0}} //元素省略Point
pointersOfPoints := [...]*Point{{1.5, -3.5}, {0, 0}} //元素省略了&Point
当复合字面使用字面类型的类型名形式时,若它出现在关键字 “if”、“for” 或 “switch” 语句及其开大括号之间,就会产生解析歧义。因为在该字面中, 表达式外围的大括号会和那些语句块前的混淆。为解决此罕见情况中的歧义,该复合字面必须出现在小括号中:
if x == (T{a,b,c}[i]) { … } //错误用法
if (x == T{a,b,c}[i]) { … } //正确用法
函数字面
函数字面表示匿名函数。它由函数类型和函数体的规范组成。函数字面可赋予一个变量或直接调用:
//函数赋值给变量
f := func(x, y int) int { return x + y }
fmt.Println(f(3, 4))
//函数直接调用
fmt.Println(func(x, y int) int { return x + y }(3, 4))
选择者
查看如下形式:
x.f
表示值 x(有时为 *x,见下)的字段或方法 f。 标识符 f 称为(字段或方法)选择者,它不能为空白标识符。 该选择者表达式的类型即为 f 的类型。
选择者 f 可代表类型为 T 的字段或方法 f, 或引用 T 中嵌套匿名字段的字段或方法 f。 在 T 中遍历区域 f 的匿名字段所得的数量称为它的 深度。 以 T 声明的字段或方法 f 的深度为0。 在 T 中以匿名字段 A 声明的字段或方法 f 的深度 为 f 在 A 中的深度加1。
以下规则适用于选择者:
- 对于非接口类型 T 或 *T 的值 x, x.f 中的 f 表示在 T 中最浅深度的字段或方法。 若并非只有一个 f,该选择者表达式即为非法的。
- 对于接口类型 I 的变量 x,x.f 表示赋予 x 的值的名为 f 的真实方法。若在 I 的方法集中没有名为 f 的方法,该选择者即为非法的。
- 其它情况下,所有 x.f 均为非法的。
- 若 x 为指针或接口类型且值为 nil,对 x.f 进行赋值、求值或调用会产生 运行时恐慌.
选择者会自动解引用指向结构的指针。 若 x 为指向结构的指针,x.y
即为 (*x).y
的缩写; 若字段 y 亦为指向结构的指针,x.y.z
即为 (*(*x).y).z
的缩写, 以此类推。 若 x 包含类型为 *A 的匿名字段,且 A 亦为结构类型, x.f
即为(*x.A).f
的缩写。(特别注意最后一种情况。)
查看以下例子:
package main
import "fmt"
type T0 struct {
x int
}
func (recv *T0) M0() {
fmt.Println("T0.M0")
}
type T1 struct {
y int
}
func (recv *T1) M1() {
fmt.Println("T1.M1")
}
type T2 struct {
z int
T1
*T0
}
func (recv *T2) M1() {
fmt.Println("T2.M1")
}
func (recv *T2) M2() {
fmt.Println("T2.M2")
}
func main() {
var p *T2 = new(T2)
p.T0 = new(T0)
p.M0()
p.M1()
p.M2()
fmt.Println(p.x, p.y, p.z)
}
输出
方法表达式
若 M 在类型为 T 的方法集中, T.M 即为可调用函数,
查看下面的结构体:
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 (
v T
p = &v
)
//以下两种调用等价
v.Mv(0)
T.Mv(v, 0)
//以下3种调用等价
p.Mp(0)
(*p).Mp(0)
(*T).Mp(p, 0)
下标表达式
形式为a[x]
的主表达式表示数组、切片、字符串或映射 a 的元素通过 x 检索。 值 x 称为 下标 或 映射键。
若 a 并非一个映射:
- 下标 x 必须为整数值;若 0 <= x < len(a) 则该下标在界内,否则即为越界
- a 常量下标必须为可表示成 int 类型的值
对于数组类型 A 或 *A 的 a:
- 常量下标必在界内
- 若 a 为 nil 或 x 在运行时越界, 就会引发一个运行时恐慌
- a[x] 是下标为 x 的数组元素,且 a[x] 的类型即为 A 的元素类型
对于切片类型 S 的 a:
- 若该切片为 nil,或 x 在运行时越界, 就会引发一个运行时恐慌
- a[x] 是下标为 x 的切片元素且 a[x] 的类型为 S 的元素类型
对于类型为字符串类型 T 的 a:
- 若字符串 a 也为常量,常量下标必在界内。
- 若 x 超出范围,就会出现一个运行时恐慌
- a[x] 为下标 x 的字节且 a[x] 的类型为 byte
- a[x] 不可赋值
对于类型为映射类型 M 的 a:
- x 的类型必须可赋值至 M 的键类型
- 若映射包含键为 x 的项,则 a[x] 为键 x 的映射值, 且 a[x] 的类型为 M 的值类型
- 若映射为 nil 或不包含这样的项, a[x] 为 M 值类型的零值
在类型为 map[K]V 的映射 a 中,下标表达式可使用特殊形式:
v, ok := a[x]
赋值或初始化,该下标表达式结果的类型为 (V, bool) 的值对。 在此形式中,若键 x 已在映射中,则 ok 的值为 true, 否则即为 false。v 的值为 a[x] 的单值形式。向 nil 映射的元素赋值会引发运行时恐慌
切片
对于字符串,数组,数组指针或切片 a,a[low : high]
会构造一个字串或切片。
切片的下标起始于 0 且长度等于 high - low,为方便起见,任何下标都可省略。略去的 low 下标默认为零; 略去的 high 下标默认为已切下的操作数的长度:
a := [5]int{1, 2, 3, 4, 5}
s1 := a[1:4]
s2 := a[:4] //等价于a[0:4]
s3 := a[1:] //等价于a[1:5]
对于切片,其上界为该切片的容量 cap(a) 而非长度。常量下标必为非负值, 且可表示为 int 类型的值。若其下标也为常量,它们必定满足 low <= high。 若 a 为 nil 或其下标在运行时越界,就会引发一个运行时恐慌。
类型断言
记法 x.(T)
称为 类型断言。断言 x 不为 nil 且存储于 x 中的值其类型为 T。
更确切地说,若 T 为非接口类型,x.(T) 断言 x 的动态类型 与 T相同。在此情况下,T 必须实现 x 的(接口)类型,除非其类型断言由于无法为 x 存储类型为 T 的值而无效。若 T 为接口类型, x.(T) 则断言 x 的动态类型实现了接口 T。
若该类型断言成立,该表达式的值即为存储于 x 中的值,且其类型为 T。若该类型断言不成立, 就会出现一个运行时恐慌:
var x interface{} = 7 // x 拥有动态类型 int 与值 7
i := x.(int) // i 拥有类型 int 与值 7
type I interface { m() }
var y I
s := y.(string) // 非法:string 没有实现 I(缺少方法 m)
r := y.(io.Reader) // r 拥有 类型 io.Reader 且 y 必须同时实现了 I 和 io.Reader,特别注意这种情况!
若类型断言以
v, ok := x.(T)
的形式用于赋值或初始化,该断言的结果即为类型为 (T, bool) 的值对。 若该断言成立,该表达式返回值对 (x.(T), true);否则,该表达式返回 (Z, false), 其中 Z 为类型为 T 的零值。此种情况不会产生运行时恐慌。 类型断言在这种构造中,其行为类似于函数调用返回一个值与一个布尔值以表示成功。
调用
在函数调用中,函数值与实参按一般顺序(即从左到右依次)求值。
传递实参至…形参
若 f 为最后带有形参类型 ...T
的可变参函数, 那么在该函数中,实参等价于类型为 []T 的形参。 对于每一个 f 的调用,传递至最后形参的实参为类型为 []T 的一个新切片, 其连续的元素即为实际的实参,它们必须都可赋予类型 T。 因此,该切片的长度为绑定至最后形参的实参的个数,且对于每一个调用位置可能都不同。
func Greeting(prefix string, who ...string)
Greeting("hello:", "Joe", "Anna", "Eileen")
若最后的实参可赋予类型为 []T 的切片且后跟着 …, 它可能作为 …T 形参的值不变而被传入:
s := []string{"James", "Jasmine"}
Greeting("goodbye:", s...) //who 将作为与 s 一样的值拥有与其相同的基本数组
操作符
二元操作符 = "||" | "&&" | 关系操作符 | 加法操作符 | 乘法操作符 .
关系操作符 = "==" | "!=" | "<" | "<=" | ">" | ">=" .
加法操作符 = "+" | "-" | "|" | "^" .
乘法操作符 = "*" | "/" | "%" | "<<" | ">>" | "&" | "&^" .
一元操作符 = "+" | "-" | "!" | "^" | "*" | "&" | "<-" .
四个基本算数操作符 (+,-,*,/)适用于整数、浮点数和复数类型; + +=
也适用于字符串。
x / y 向零截断。若被除数为常量,则它必不为零。若被除数在运行时为零, 就会出现一个运行时恐慌。
对于整数操作数,一元操作符 +、- 和 ^ 的定义如下:
+x 即为 0 + x
-x 相反数 即为 0 - x
^x 按位补码 即为 m ^ x 对于无符号的 x,m = 所有位置为1;对于带符号的 x,m = -1
操作符优先级
总体顺序为
一元操作符 > 算术操作符 > 比较操作符 > 逻辑操作符
优先级 操作符
6 + - ! ^ * & <-
5 * / % << >> & &^
4 + - | ^
3 == != < <= > >=
2 &&
1 ||
相同优先级的二元操作符从左到右结合。
特别要注意的是,在go语言中 ++ 和 – 操作符是语句,而非表达式,它们不属于运算符一级。 因此,语句 *p++ 等价于 (*p)++。
算数操作符
四个基本算数操作符 (+,-,*,/)适用于整数、浮点数和复数类型; + 也适用于字符串。其它所有算数操作符仅适用于整数。
+ 和 integers, floats, complex values, strings
- 差 integers, floats, complex values
* 积 integers, floats, complex values
/ 商 integers, floats, complex values
% 余 integers
& 按位与 integers
| 按位或 integers
^ 按位异或 integers
&^ 位清除(与非) integers
<< 向左移位 integer << unsigned integer
>> 向右移位 integer >> unsigned integer
对于整数x,y如果q=x/y,r= x%y,则它们满足下面的关系:
r = x - x/y*y and |r| < |y|
同时x/y会向零截断,所以有以下结果:
x y x / y x % y
5 3 1 2
-5 3 -1 -2
5 -3 -1 2
-5 -3 1 -2
从结果上来看余数的符号和x的符号相同。
有一条意外的规则是,如果为x为其类型的最大负数,则x/-1==x:
x x/-1
int8 -128 -128
int16 -32768 -32768
int32 -2147483648 -2147483648
int64 -9223372036854775808 -9223372036854775808
若被除数为常量,则它必不为零。若被除数在运行时为零, 就会出现一个运行时恐慌。
字符串可使用 + 操作符连结或 += 赋值操作符:
在这里插入代码片
移位操作符通过右操作数指定的移位计数来移位左操作数。若左操作数为带符号整数,它们就执行算术移位(即需要考虑符号位,通常为负数右移时左边补1); 若左操作数为无符号整数,它们则执行逻辑移位(即不考虑符号位,高低位均补0)。移位计数没有上界。 若左操作数移 n 位,其行为如同移 1 位 n 次。 按照其结果,无论x为何种整形,x << 1 等价于 x*2,而 x >> 1 等价于 x/2 但向负无穷大截断:
var (
x int8 = 3
y int8 = -3
)
x<<1 // 结果为int8(6)
x>>1 // 结果为int8(1)
y<<1 // 结果为int8(-6)
y>>1 // 结果为int8(-2)```
对于整数操作数,一元操作符 +、- 和 ^ 的定义如下:
+x 即为 0 + x
-x 相反数 即为 0 - x
^x 按位补码 即为 m ^ x 对于无符号的 x,m = "所有位置为1"
整数溢出
无符号整数操作抛弃高位向上溢出
对于带符号整数,操作 +、-、* 和 << 可合法溢出, 而由此产生的值会继续存在,并由该带符号整数表现、操作、与其操作数决定性地定义。
比较操作符
比较操作符比较两个操作数并产生一个布尔值。在任何比较中,第一个操作数必须为可赋予第二个操作数的类型,反之亦然。相等性操作符 == 和 != 适用于可比较操作数。 顺序操作符 <、<=、> 和 >= 适用于有序的操作数。这些比较操作的关系和值定义如下:
- 布尔值之间可比较。若两个布尔值同为 true 或同为 false,它们即为相等。
- 通常情况下,整数值之间可比较或排序。
- 根据 IEEE-754 标准的定义,浮点数值之间可比较或排序。
- 复数值之间可比较。对于两个复数值 u 与 v, 若 real(u) == real(v) 且 imag(u) == imag(v),它们即为相等。
- 根据按字节词法,字符串值之间可比较或排序。
- 指针值之间可比较。若两个指针指向相同的值或其值同为 nil,它们即为相等。 指向明显为零大小变量的指针可能相等也可能不相等。
- 信道值可比较。若两个信道值通过相同的 make 调用 (§创建切片、映射和信道)创建或同为 nil 值,它们即为相等。
- 接口值可比较。若两个接口值拥有相同的动态类型与相等的动态值,或同为 nil 值,它们即为相等。
- 当非接口类型 X 的值可比较且 X 实现了 T 时, 非接口类型 X 的值 x 与接口类型 T 的值 t 则可比较。 若 t 的
- 态类型与 X 相同且 t 动态值等于 x,它们即为相等。
- 若两个结构值的所有字段可比较,它们即可比较。若其相应的非空白字段相等,它们即为相等。
- 若两个数组元素类型的值可比较,则数组值可比较。若其相应的元素相等,它们即为相等。
两个动态类型相同的接口值进行比较,若该动态类型的值不可比较,将引发一个运行时恐慌。 此行为不仅适用于直接的接口值比较,当比较接口值的数组或接口值作为字段的结构时也适用。
切片、映射、函数、指针、信道、接口值等类型之间不可作比较,但是它们都可与预声明标识符 nil 进行比较。
逻辑操作符
逻辑操作符适用于布尔值并根据操作数产生一个相同类型的结果。右操作数有条件地求值。
&& 条件与 p && q 即 “若 p 成立则判断 q 否则返回 false”
|| 条件或 p || q 即 “若 p 成立则返回 true 否则判断 q”
! 非 !p 即 “非 p”
接收操作符
对于信道类型的操作数 ch,接收操作符 <-ch 的值即为从信道 ch 接收的值。该信道的方向必须允许接收操作, 且该接收操作的类型即为该信道的元素类型。该值前的表达式块是有效的。 从 nil 信道接收将永远阻塞。从已关闭的信道接收将总是成功, 它会立刻返回其元素类型的零值:
v1 := <-ch
v2 = <-ch
f(<-ch)
<-strobe // 在时钟脉冲和丢弃接收值之前等待
接收表达式以
x, ok = <-ch
x, ok := <-ch
var x, ok = <-ch
的形式用于赋值或初始化将产生一个类型为 bool 的附加结果,来报告通信是否成功。 若接收的值由一次成功向信道发送的操作发出的,则 ok 的值为 true; 若接收的值是由于信道被关闭或为空而产生的零值,则为 false。
类型转换
求值顺序
当对一个表达式、赋值或Return语句进行求值时, 所有函数调用、方法调用以及通信操作均按从左到右的词法顺序求值。
语句
标签语句
标签语句可作为 goto、break 或 continue 语句的目标:
Error: log.Panic("error encountered")
发送语句
发送语句在信道上发送值。信道表达式必须为信道类型, 信道的方向必须允许发送操作,且被发送的值的类型必须可赋予该信道的元素类型。
信道与值表达式均在通信开始前求值。通信会阻塞,直到发送可继续进行。 若接收者已就绪,在无缓存信道上发送可继续进行。 若缓存中有空间,在有缓存信道上发送可继续进行。 在已关闭信道上进行发送会引发一个运行时恐慌。 在 nil 信道上进行发送将永远阻塞。
ch <- 3
赋值
赋值语句中,左边的下标表达式与指针间接寻址的操作数 (包括选择者中隐式的指针间接寻址)和右边的表达式都会按通常顺序求值。 其次,赋值会按照从左到右的顺序进行。所以可以使用下面的表达式做值交换:
a, b = b, a // 交换a和b
if语句
// 简单语句将在表达式求值前执行
if x := f(); x < y {
return x
} else if x > z {
return z
} else {
return y
}
Switch语句
它有两种形式:表达式选择与类型选择。在表达式选择中,case包含的表达式针对switch表达式的值进行比较, 在类型选择中,case包含的类型针对特别注明的switch表达式的类型进行比较。
表达式选择
在表达式选择中,switch表达式会被求值,而case表达式无需为常量,它按从上到下,从左到右的顺序求值; 第一个等于switch表达式的case表达式将引发相应情况的语句的执行;其它的情况将被跳过。 若没有情况匹配且有"default"情况,则该语句将被执行。 最多只能有一个默认情况且它可以出现在"switch"语句的任何地方。 缺失的switch表达式等价于表达式 true。
在case或default子句中,最后一个语句可能为fallthrough
语句 , 它表明该控制流应从该子句的结尾转至下一个子句的第一个语句。 否则,控制流转至该"switch"语句的结尾。
switch tag {
default: s3()
case 0, 1, 2, 3: s1()
case 4, 5, 6, 7: s2()
}
switch {
case x < y: f1()
case x < z: f2()
case x == 4: f3()
}
类型选择
例子如下:
switch i := x.(type) {
case nil:
printString("x is nil") // i 的类型为 x 的类型(interface{})
case int:
printInt(i) // i 的类型为 int
case float64:
printFloat64(i) // i 的类型为 float64
case func(int) float64:
printFunction(i) // i 的类型为 func(int) float64
case bool, string:
printString("type is bool or string") // i 的类型为 x 的类型(interface{})
default:
printString("don't know the type") // i 的类型为 x 的类型(interface{})
其中x.(type)
为使用保留字 type
而非实际类型的类型断言的形式.此时的case针对表达式 x 的动态类型匹配实际的类型 T。 就像类型断言一样,x 必须为接口类型, 而每一个在case中列出的非接口类型 T 必须实现了 x 的类型。
fallthrough
语句在类型选择中不被允许。
For语句
在最简单的形式中,只要布尔条件求值为真,"for"语句指定的块就重复执行。 条件会在每次迭代前求值。若缺少条件,则它等价于 true:
for a < b {
a *= 2
}
带For子句的"for"语句也通过其条件控制,此外,它也可指定一个初始化或步进语句, 例如一个赋值、一个递增或递减语句。初始化语句可为一个短变量声明, 而步进语句则不能:
for i := 0; i < 10; i++ {
f(i)
}
带"range"子句的"for"语句通过遍历数组、切片、字符串或映射的所有项,以及从信道上接收的值来迭代。 对于每一项,它将迭代值赋予其相应的迭代变量,然后执行该块:
var a [10]string
m := map[string]int{"mon":0, "tue":1, "wed":2, "thu":3, "fri":4, "sat":5, "sun":6}
for i, s := range a {
g(i, s)
}
Go语句
go语句将函数调用的执行作为控制独立的并发线程或相同地址空间中的Go程来启动:
go Server()
go func(ch chan<- bool) { for { sleep(10); ch <- true; }} (c)
Select语句
Return语句
函数 F 中的“return”语句终止 F 的执行,并可选地提供一个或多个返回值。
Break语句
“break"语句终止最内层的"for”、“switch"或"select"语句的执行。
若存在标签,则它必须为闭合的"for”、"switch"或"select"语句,而此执行就会终止:
L:
for i < n {
switch i {
case 5:
break L
}
}
Continue语句
"continue"语句在最内层"for"循环的步进语句处开始下一次迭代。若存在标签,则它必须为闭合的"for"语句,而此执行就会前进。
Goto语句
"goto"语句用于将控制转移到与其标签相应的语句。
执行"goto"不能在跳转点处产生任何还未在作用域中的变量来使其进入作用域:
goto L // 这样不好
v := 3
L: //错误, 因为跳转至标签 L 处将跳过 v 的创建
在块外的"goto"语句不能跳转至该块中的标签:
if n%2 == 1 {
goto L1
}
for n > 0 {
f()
n--
L1: //错误,因为标签 L1 在"for"语句的块中而 goto 则不在
f()
n--
}
Defer语句
"defer"语句调用的函数将被推迟到其外围函数返回时执行,不论是因为该外围函数执行了 return 语句,到达了其函数体的末尾, 还是因为其对应的Go程进入了恐慌过程。在外围的函数返回前,被推迟的函数会按照它们被推迟的相反顺序立即执行。
// 在外围函数返回前打印 3 2 1 0
for i := 0; i <= 3; i++ {
defer fmt.Print(i)
}
内建函数
包
Go程序由联系在一起的包构造。包由一个或更多源文件构造转化而来, 源文件与其常量、类型、变量和函数声明一同属于该包,且在相同包的所有文件中它们可互相访问。
源文件的组织
每个源文件都由这些部分构成:
源文件 = 包子句 ";" { 导入声明 ";" } { 顶级声明 ";" } .
包子句
包子句起始于每个源文件并定义该文件所属的包:
package math
一个文件集通过共享相同的包名来构成包的实现。实现可能要求包的所有源文件放置在同一目录下。一个目录文件下不能有不同包的源文件。
导入声明
导入声明用来声明导入包与被导入包之间的从属关系。包导入其自身或导入一个不涉及任何可导出标识符的包是非法的:
import "lib/math"
程序初始化与执行
零值
声明变量而未提供显式的初始化时,内存将被赋予一个默认的初始化零值:布尔类型为 false,整数类型为 0,浮点数类型为 0.0, 字符串类型为 “”,而指针、函数、接口、切片、信道及映射类型则为 nil。
程序执行
没有导入声明的包通过向所有其包级变量赋予初始值,并调用任何在其源中定义的名字和签名为func init()
的包级函数来初始化。一个包也可能包含多个 init 函数,它们会按照不确定的顺序执行。
init 函数不能在程序中的任何地方被引用。确切地说,init 既不能被显式地调用, 也不能被指针指向 init 以赋予函数变量。
若一个包拥有导入声明,则被导入的包会在初始化该包自身前初始化。若有多个包导入包 P,则 P 只会初始化一次。
一个完整的程序通过链接一个单一的、不会被导入的、称为 主包 的,带所有其传递地导入包的包创建。 主包必须拥有包名 main 且声明一个无实参无返回值的函数 func main() {...}
。
包初始化—变量初始化与 init 函数的调用—连续地发生在单一的Go程中,一次一包。 一个 init 函数可能在其它Go程中启动,它可以与初始化代码一同运行。然而,初始化总是按顺序执行 init 函数:直到上一个 init 返回后,它才会启动下一个。
错误
预声明类型 error 定义为:
type error interface {
Error() string
}
它是表示一种错误状态的传统接口,用 nil 值表示没有错误。
运行时恐慌
系统考虑
包 unsafe
unsafe 为包括违反类型系统操作在内的低级编程提供工具。使用 unsafe 的包为了类型安全必须手动进行审查。该包提供以下接口:
package unsafe
type ArbitraryType int // 任意Go类型的简写,它并非真正的类型
type Pointer *ArbitraryType
func Alignof(variable ArbitraryType) uintptr
func Offsetof(selector ArbitraryType) uintptr
func Sizeof(variable ArbitraryType) uintptr
任何基本类型为 uintptr 的指针或值均可转换为 Pointer,反之亦然。
函数 Alignof 与 Sizeof 接受一个任何类型的表达式 x, 分别返回其列数或大小。
函数 Offsetof 接受一个表示任何类型结构字段的结构体成员,并返回该字段相对于该结构地址偏移的字节。
大小与对齐保证
对于数值类型,以下大小给予保证:
类型 字节大小
byte, uint8, int8 1
uint16, int16 2
uint32, int32, float32 4
uint64, int64, float64, complex64 8
complex128 16
以下最小对齐属性给予保证:
- 对于任何类型的变量 x:unsafe.Alignof(x) 至少为1。
- 对于结构类型的变量 x:对于 x 的每一个字段 f, unsafe.Alignof(x) 的值为所有 unsafe.Alignof(x.f) 值中最大的,但至少为1。
- 对于数组类型的变量 x:unsafe.Alignof(x) 与 unsafe.Alignof(x[0]) 相同,但至少为1
- 若结构或数组类型不包含大小大于零的字段或元素,它们的大小即为零。两个不同的零大小变量在内存中可能有相同的地址。