1、Go 编程语言规范 2016 年 01 月 05 日版本 简介 记号 源代码表示 字符 字母和数字 词法元素 注释 符号 分号 标识 符 关键字 运算符和分隔符 整型字面量 浮点字面量 虚数字面量 文符字面量 字符串字面量 常量 变量 类型 方法集 布尔类型 数值类型 字符串类型 数组类型 分片类型 结构体类型 指针类型 函数类型 接口类型 映射类型 管道类型 类型 和值的性质 类型一致 转换 块 调用 传递参数 操作符 /运算符 运算符优先级 算术运算符 整数溢出 比较运算符 逻辑运算符 地址运算符 接收运算符 方法表达式 转换 常量表达式 值的计算顺序 语句 空语句 标号语句 表达式语句
2、发送语句 自加自减语句 赋值语句 if 语句 switch 语句 for 语句 go 语句 select 语句 return 语句 break 语句 continue 语句 goto 语句 fallthrough 语句 defer 语句 内置函数 关闭 长度和容量 分配 声明和作用域 标号作用域 空白标识符 预声明标识符 导出的标识符 唯一标识符 常量声明 Iota 类型声明 变量声明 短变量声明 函数声明 方法声明 表达式 操作数 合法标识符 复合值 函数值 主表达式 Selectors 下标 分片 类型推断 构造分片、映射和管道 追加和拷贝分片 映射元素删除 复数操作 问题处理 Boots
3、trapping 包 源文件的组织 Package clause 导入声明 一个例子 程序初始化和执行 0 值 程序执行 错误 运行时问题 系统考量 包不安全 尺寸大小和对齐保证 简介 这是 Go 语言的一个参考手册。如果想了解更多信息或是其他文档的话,可以去http:/golang.org 查看。 Go 是一门立志于系统编程的通用语言。它是强类型,带有垃圾回收,并且内在支持并发编程。程序由 包 组成,可以方便地管理它们之间的依赖。已有的实现采用了传统的编译 /链接模型,最终生成可执行的二进制代码。 Go 语言语法紧凑,非常有规则可循,可以很容易地被集成开发环境 (IDE)等自动工具分析。 记
4、号 后面的语法使用扩展的巴克斯 -诺尔范式 (EBNF)进行描述 : Production = production_name “=“ Expression “.“ . Expression = Alternative “|“ Alternative . Alternative = Term Term . Term = production_name | token “ token | Group | Option | Repetition . Group = “(“ Expression “)“ . Option = “ Expression “ . Repetition = “ Expre
5、ssion “ . 产生式由一些术语和下面的几个按优先级从低到高的运算符组成: | 任选其一 () 一个整体 可选 /可有可无 (0 或是 1 次 ) 重复多次 (0 到 n 次 ) 小写的产生式的名字通常用于表示一个词法符号;非终结符一般用驼峰式命名。词法符号我们使用双引号 “或是反向单引号 引住。 a b 这种形式代表的是从 a到 b可选的字符集合。省略号 在本规范中也用于某些处的表示不完全枚举或是不再详细列出的代码分片。字符 (不同于三个字符的 .),它不是 Go 语言的一个词法符号。 源代码表示 源文件是用 UTF-8 编码的 Unicode 文本。文本并不是规范化的,所以,一个加重音
6、的代码点不同于重音再加一个字符,后面的认为是两个字符。为了简化,本文档使用了并不是很规范的 字符 术语来指代源文本中的一个 Unicode 代码点。 每一个代码点都应该进行区分,比如说,大写字母和小写字母就是不同的字符。 实现限制 : 为了和其他的工具兼容,一个编译器不允许在源文本中出现 NUL 字符(U+0000)。 字符 下面是一些比较特殊的 Unicode 字符类: newline = /* Unicode 代码点 U+000A */ . unicode_char = /* 除了 newline 之外的其他 Unicode 代码点 */ . unicode_letter = /* Uni
7、code 代码点中归为 “字母 “ 的字符 */ . unicode_digit = /* Unicode 代码点中归为 “十进制数字 “ 的字符 */ . 在 Unicode 标准 6.0 中, 4.5 章节 “ 通用分类 ” 定义了一系列的分类。 Go 会认为在 这些分类中的 Lu, Ll, Lt, Lm,或是 Lo 是 Unicode 字符, Nd 是 Unicode 数字。 字母和数字 下划线 _(U+005F) 被认定为一个字母。 letter = unicode_letter | “_“ . decimal_digit = “0“ “9“ . octal_digit = “0“ “
8、7“ . hex_digit = “0“ “9“ | “A“ “F“ | “a“ “f“ . 词法元素 注释 有两种形式的注释: 1. 行注释 从两斜杠 /开始到这一行结束。一个行注释给人的感觉就是一个换行; 2. 通用注释 (块注释 )从 /*开始到 */结束。块注释中如果有行注释的话,那么它像是换行;其他情况下,它像是空白。 注释不能嵌套。 词法符号 词法符号是 Go 语言的词汇表。它们分为四类: 标识符 、 关键字 、 操作符 和 分隔符 。由空格 (U+0020)、水平制表符 (U+0009)、回车符 (U+000D)和换行符 (U+000A)所组成的空白 除了可能是用于组成符号之外,
9、其他的时候用作分隔符,在 分析阶段会被忽略掉。 还有就是,换行符或是页面结束可能导致 分号 的插入。在将代码文本分割成符号的过程中,下一个符号应该是能组成一个合法符号的最长字符序列。 分号 正式语法使用分号 “;“ 作为一些产生式的分隔符。 但是 Go 程序可以基于下面两条规则省略多数时候的分号: 1. 当输入文本在被拆成了记号的时候,在一些情况下分号会自动被插入非空白行的尾部的记号流中去,但是需要这一行的最后一个记号是: o 标识符 o 整数 、 浮点数 、 虚数 、 分符 ,或是 字符串 值 o 关键字 break、 continue、 fallthrough,或是 return o 操作
10、符和分隔符 +、 -, )、 ,或是 2. 为了允许复杂的语句能够挤在一行中,所以在 “)“ or “前面的分号可能省略掉。 为了反映通常的使用习惯,本文档中的代码例子通常使用这些规则而省略了分号。 标识符 标识符用来命名变量、类型等程序实体。一个标识符实际上就是一个或是多个字母 /数字序列,不过第一个字符应该是字母而不能是数字。 identifier = letter letter | unicode_digit . a _x9 ThisVariableIsExported 有一些标识符是 预声明的 。 关键字 下面的关键字被保留了因而不能作为标识符使用: break default fun
11、c interface select case defer go map struct chan else goto package switch const fallthrough if range type continue for import return var 操作符 /运算符和间隔符 下面的一些字符序列被当做 操作符 /运算符 、分隔符或是其他一些特殊的符号: + “ “ . FieldDecl = (IdentifierList Type | AnonymousField) Tag . AnonymousField = “*“ TypeName . Tag = string_l
12、it . / 空结构体 struct / 有 6 个字段的结构体 struct x, y int u float32 _ float32 / 占位 /填充 A *int F func() 一个字段声明的时候只有类型却没有名字,我们叫它为 匿名字段 ,或是 嵌入 字段或是类型嵌入。一个嵌入的类型必须指定一个类型名 T或是一个非接口的指针 *T,而且T本身不能是指针类型。这些嵌入类型的类型名作为对应的字段名。 / 带有四个匿名字段 T1, *T2, P.T3 和 *P.T4 的结构体 struct T1 / field name is T1 *T2 / field name is T2 P.T3
13、/ field name is T3 *P.T4 / field name is T4 x, y int / field names are x and y 下面的声明是不合法的,因为字段名在结构体中并不唯一: struct T / 和匿名字段 *T , *P.T 冲突 *T / 和匿名字段 T ,*P.T 冲突 *P.T / 和匿名字段 T , *T 冲突 对于结构体 x的一个匿名字段的字段或是 方法 f,如果 x.f是一个合法的 选择子(可能是一个字段或是一个方法 f),我们就说,它被 提升 了。 提升后的字段用起来就赶脚是结构体的普通字段,只不过它们在结构体的 复合表示 中不能用作字段名
14、。 给一个结构体类型 S和一个命名类型 T,提升了的方法按照下面所说的包括在结构体的方法集中: 如果 S 包括一个匿名字段 T,那么 S 和 *S 的 方法集 都包括以 T 作为接收器而提升的方法 . 而 *S 的方法集又包括以 *T 为接收器提升的方法。 如果 S 包括一个匿名字段 *T,那么 S 和 *S 的 方法集 都包括以 T 或是 *T 作为接收器而提升的方法 . 一个字段的声明中可以跟着一个可选的字符串 标签 ,它在相应的字段声明中会算做字段的一种属性 /性质。这些标签在 反射接口 和 类型一致 那里中是可见的,其他的时候可以认为是忽略不计的。 / 一个用于时间戳协议缓冲区的结构体
15、 / 标签字符串定义了协议缓冲区字段号 struct microsec uint64 “field 1“ serverIP6 uint64 “field 2“ process string “field 3“ 指针类型 一个指针就是可以指向其他类型变量的变量。被指向的变量的类型叫做指针的 基类型 ;如果么有初始化的话,指针值是 nil。 PointerType = “*“ BaseType . BaseType = Type . *Point *4int 函数类型 一个函数类型指代的是带有相同参数和返回值类型的一类函数。一个未初始化的函数变量的值是 nil。 FunctionType = “f
16、unc“ Signature . Signature = Parameters Result . Result = Parameters | Type . Parameters = “(“ ParameterList “, “ “)“ . ParameterList = ParameterDecl “, “ ParameterDecl . ParameterDecl = IdentifierList “.“ Type . 在函数的参数 /结果列表中,名字(标识符列表)可以都有也可以都么有。如果有的话,一个名字代表对应类型的一项(参数 /结果),非 空 名称必须不相同;如果没有,一个类型代表该类
17、型的一项。参数 /结果列表通常用小括号括起来,不过当只有一个返回值且没有名字的情况下,这个括号可以省略掉。 一个函数签名的最后一个参数可能以 .为前缀,这样的函数我们叫 可变 函数,它们在调用的时候对于那个参数可以传递 0 或是多个值。 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
18、(int, int, float64) (float64, *int) func(n int) func(p *T) 接口类型 一个接口类型指定了一个称为 接口 的 方法集 。一个接口类型的变量可以存某个类型的值,只要这种类型的方法集是接口方法集的超集;这样的一种类型,我们说它 实现了接口 .一个无初始化的接口的值为 nil。 InterfaceType = “interface“ “ MethodSpec “;“ “ . MethodSpec = MethodName Signature | InterfaceTypeName . MethodName = identifier . Inte
19、rfaceTypeName = TypeName . 在一个接口类型中对于所有的方法集,每一个方法必须有 唯一的 非 空 名字。 / 一个简单的文件接口 interface Read(b Buffer) bool Write(b Buffer) bool Close() 可以有多种类型都实现了一个接口。比如 ,如果 S1 和 S2 都有方法集: func (p T) Read(b Buffer) bool return func (p T) Write(b Buffer) bool return func (p T) Close() (其中 T代表 S1 或是 S2)那么, File接口就是被
20、 S1和 S2 都实现了,而我们并不再去关心 S1和 S2 是否是有其他方法或是共享了其他神马方法。 一个类型实现了一个接口,只要它的所有方法中的一个子集是接口的方法即可;所以,一种类型可能实现了多个接口。比如说,所有的类型都实现了 空接口 : interface 类似的,考虑下面的接口说明,这个说明出现在 类型声明 中,这个类型声明定义了一个叫做 Lock的接口 : type Lock interface Lock() Unlock() 如果 S1和 S2 都实现了 func (p T) Lock() func (p T) Unlock() 那么,它们就实现了 Lock接口,当然它们也实现了
21、 File接口(看上面)。 一个接口 T可以使用一个(可能带限定的)接口类型 E来代替一系列方法说明,我们叫 T中嵌入了接口 E;这样做意味着把 E中所有(导出和非导出)的方法添加到 T中。 type ReadWriter interface Read(b Buffer) bool Write(b Buffer) bool type File interface ReadWrite / 和一一枚举 ReadWrite 中的方法效果一样 Lock / 和一一枚举 Lock 中的方法效果一样 Close() type LockedFile interface Locker File / 不合法 :
22、 Lock 不唯一 Lock() / 不合法 : Lock 不唯一 一个接口类型 T不能自身嵌套在自身之中, 或是递归地嵌套一个包含它自身 T的接口。 / 非法:不能自身嵌套 type Bad interface Bad / 非法: Bad1 不能通过 Bad2 来嵌套自身 type Bad1 interface Bad2 type Bad2 interface Bad1 映射类型 一个 map/映射是一群无序的元素构成的组,这些元素的类型以一种类型的值作为唯一的索引 key 然后访问到另一种类型的某个值。一个未初始化的映射变量的值为 nil。 MapType = “map“ “ KeyTyp
23、e “ ElementType . KeyType = Type . 对于 kye/关键字类型的比较运算符 = and !=( 比较运算符 ) 必须是完整定义的;于是 key 的类型不能是函数、映射或是分片 。 如果 key 的类是一个接口的话,这两个比较运算符应该对动态的两个 key 值是完整定义的;失败会引起一个 运行时问题 。 mapstringint map*Tstruct x, y float64 mapstringinterface map 元素的个数叫做它的长度。对于一个 map m我们可以通过 一个内置函数len(m)访问它的长度, 不过它的长度在执行过程中可能会发生变化。元素
24、可以在 赋值的时候进行添加,可以通过使用 索引 /下标 表达式来获取; 我们也可以使用内置的函数 delete来删除元素。 一个新的空的 map 值可以使用内置的函数 make来构造,这个函数带有 map 的类型和一个可选的容量长度作为参数: make(mapstringint) make(mapstringint, 100) 一个初始过的 map 的容量不受尺寸的限制: map 根据存的东西的多少会自我调整,当然有个例外就是 nil。 nil map 等价于一个空 map,只不过 它还不能添加任何元素。 may be added. 管道类型 管道提供了一种两个并发执行的函数进行同步执行或是通
25、信的机制。管道里面只能传输某一种指定类型的值,初始化的管道的值是 nil。 ChannelType = ( “chan“ “-“ | “-“ “chan“ ) ElementType . -运算符指定了管道中输出传输的 方向 : 发送 或是 接收 。如果管道的方向并没有指定的话,那么就认为是 双向的 。 管道经过 装换 或是 赋值 后可能就变成只能发送或是只能接收 的了。 chan T / 可以发送或是接收 T 类型的数据 chan- float64 / 只能发送 float64 数据 -chan int / 只能接收 int 数据 -尽可能地左结合 chan: chan- chan int
26、/ 等同于 chan- (chan int) chan- -chan int / 等同于 chan- (-chan int) -chan -chan int / 等同于 -chan (-chan int) chan (-chan int) 一个新的未初始化的管道可以使用 make进行构造,构造的时候需要指定管道中数据的类型,而管道的容量则是可选的, 也就是可以指定可以不指定: make(chan int, 100) 容量 也就是管道中元素的个数,指定了管道中的缓冲区的大小。如果容量大于 0,那么管道就是异步的,也就是说只有满的时候阻塞发送、空的时候阻塞接收,而其他的时候不阻塞; 当然,元素的接
27、收顺序和发送的顺序一致。如果容量是 0 或是不指定,那么,只有在发送和接收都准备好的时候,通信正常进行,否则都进行阻塞。 一个 nil 管道不能进行通信。 管道可以通过内置的函数 close进行关闭;而对管道是否关闭的测试可以通过 接收操作符 的多值赋值来实现。 类型以及值的属性 类型一致 两种类型要么它们是 一致的 ,要么它们是 不同的 。 两个命名类型一致只有当它们可以追溯到相同的 TypeSpec。 一个命名类型和一个无名类型总是不同的类型。两个无名类型如果它们对应的类型字面量是一致的,也就是它们要有相同的结构以及每一部分都是相同的,那么它们就是相同类型 。 详述如下: 两个数组类型当它
28、们的元素类型以及长度相同时,那么它们是相同类型。 两个分片类型只要它们的元素类型相同,那么它们就是相同类型。 两个结构体类型相同,需要它们有相同的字段序列,对一个的字段具有相同的名称、类型以及标签。两个匿名字段算是相同的名称。不同包的小写的字段名总认为是不同的。 两个指针类型当它们的基类型是相同的,那么它们就是相同的。 两个函数类型是相同的,需要它们有相同的参数个数和返回值,参数以及返回值的类型也需要相同;它们要么都是可变参数,要么都不是。参数和返回值的名称不需要相同。 两个接口类型是相同的,需要它们有相同的方法集。不同包的小写方法名总认为是不同的。方法集中的方法顺序无关紧要。 两个 map
29、类型当它们有相同的 key 和 value 类型,那么它们是相同类型。 两个管道类型当它们有相同的值类型以及方向,那么它们是相同类型 给一些声明: 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 和 T0 int 和 int struct a, b *T5 和 struct a, b *T5 func(x int, y float64) *string 和 f
30、unc(int, float64) (result *string) T0和 T1是不同的类型,因为它们是不同声明中的命名类型。 func(int, float64) *T0 和 func(x int, y float64) *string 是不同类型,因为 T0 的类型不同于 string。 可赋值 只有在下面的情况下,一个值 x才可以 赋值 给一个 T类型的变量,或是说 x对于 T是可赋值的: x 的类型和 T 的类型一样; x 的类型 V 和 T 有一样的 底层类型 ,并且 V 和 T 至少有一个不是有名类型; T 是一个接口类型,而 x 实现了 接口 T; x 是双向管道,而 T 是个
31、管道类型, x 的类型 V 和 T 有相同的元素类型,并且 V 和 T 至少有一个不是有名类型; x 是预声明的值 nil,而 T 是指针、函数、分片、映射、管道或是接口类型; x 是一个无类型的 constant,可以表示 T 类型的值。 任何一个值都可以赋值给 空标识符 。 块 一个 块 就是放置在一对大括号内的一系列声明和语句。 Block = “ Statement “;“ “ . 除了源文本中明确的块之外,还有一些不显眼的块: 1. 包围着 Go 源文本的 整体块 ; 2. 任何一个 package 都有一个将包中的所有源文本包住的 包块 。 3. 任何一个文件都有一个将文件中的所有
32、 Go 源文本包住的 文件块 。 4. 每一个 if、 for 和 switch 语句都认为它们在一个隐含的块之中。 5. 在 switch 或是 select 语句中的每个子句都像是个隐式块。 块可以嵌套而且影响 作用域 。 声明和作用域 一个声明绑定了一个非 空 的标识符至一个常量、类型、变量、函数或是包。程序中的每一个标识符都必须声明 。相同的块中同一个标识符不能声明两次,文件块和包块不能声明相同的标识符。 Declaration = ConstDecl | TypeDecl | VarDecl . TopLevelDecl = Declaration | FunctionDecl |
33、MethodDecl . 一个标识符的 域 指的是标识符在文件内的影响的范围。 Go 的作用域是分块的: 1. 预声明的标识符的域是一个全局的块; 2. 顶层 (在函数外的 )的常量、类型、变量、函数 (不包括方法 )的域是包块; 3. 导入包的标识符的域,是导入的文件的文件块; 4. 函数参数或是返回值变量的域,是整个函数体; 5. 函数内的常量或是变量标识符开始于声明的位置,结束于所在块的最右侧; 6. 函数内的类型标识符开始于类型声明处,结束于所在块的最右侧; 一个块的标识符可以在内层块中再声明,这种情况下标识符的域是在内层块的域。 package 并不是一个声明。包名不 属于任何域。它
34、的意义就是标识某一个文件所属的 包 ,以及指定导入声明的默认包名。 Label 域 label 语句 用来声明标号,它们会用在 break, continue, and goto语句中( break 语句 , continue 语句 , goto 语句 )。声明一个标号但是不使用是不允许的。和其他的标识符不同的是,标号并不是块作用域,它不会与其他的非标号冲突。标号的域是所在函数的函数体,但不包含嵌套函数的函数体。 空标识符 /通配符 空标识符 /通配符 ,使用下划线表示 _,它可以像其他标识符一样用在声明之中,不过空标识符在声明中并不会将名字和值的绑定。 预声明的标识符 下面的一些标识符在 通
35、用块 中预声明了 : 类型 : bool byte complex64 complex128 error float32 float64 int int8 int16 int32 int64 rune string uint uint8 uint16 uint32 uint64 uintptr 常量 : true false iota 0 值 : nil 函数 : append cap close complex copy delete imag len make new panic print println real recover 导出的标识符 一个标志符被 导出 后就可以在其他包中使用
36、,但是必须满足下面两个条件: 1. 标识符的首字母是 Unicode 大写字母 (Unicode “Lu“ 类 ); 而且 2. 标识符要在 包块 中进行了声明,或是它是个 字段名 /方法名 。 而其他所有的标识符都不是导出的。 唯一的标识符 给定一些标识符,如果在这些中某一个 不同 于其他一个,我们就说它是 唯一的 。如果两个标识符拼写都不一样,那么肯定是不同的,或者是它们处于不同的 包 之内而又没有被 导出 ,这也是不同的;除此之外的,就认为是相同的标识符。 常量声明 常量声明绑定一系列标识符到一系列 常量表达式 的值。标识符的数目应该和表达式的数目相等,第 n 个标识符绑定到第 n 个表
37、达式。 ConstDecl = “const“ ( ConstSpec | “(“ ConstSpec “;“ “)“ ) . ConstSpec = IdentifierList Type “=“ ExpressionList . IdentifierList = identifier “, “ identifier . ExpressionList = Expression “, “ Expression . 如果指定了类型,那么所有的常量都是指定的类型,而且表达式对于类型来说得是 可赋值的 。如果类型没有指定,那么常量取的是表达式对应的类型。如果表达式是无类型 常量 ,那么声明的常量也是
38、无类型的,常量值就是表达式的值。举个例子,如果表达式是一个浮点数字面量,那么常量标识符就是一个浮点数常量,尽管小数部分为 0。 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 stri
39、ng 常量 const u, v float32 = 0, 3 / u = 0.0, v = 3.0 在带括号的 常量 声明中,表达式列表除了第一个之外其他的可以省略。这种情况下省略的表达式等价于前置的第一个非空表达式的文本替换,省略表达式等价于重复前面的。标识符的数量应该等于表达式的 数量。 iota提供了一种生成序列常量值的机制: const ( Sunday = iota Monday Tuesday Wednesday Thursday Friday Partyday numberOfDays / this constant is not exported ) iota 在 常量声明
40、中, 预定义标识符 iota 代表了连续的无类型整数 常量 . 当在源代码中一个遇到 常量声明 的保留字 const它就会被置为 0,然后依次增加。 它可以用来构造一系列常量: const ( / iota 重置为 0 c0 = iota / c0 = 0 c1 = iota / c1 = 1 c2 = iota / c2 = 2 ) const ( a = 1 iota / a = 1 (iota 又被重置 ) b = 1 iota / b = 2 c = 1 iota / c = 4 ) const ( u = iota * 42 / u = 0 (无类型整型常量 ) v float64
41、= iota * 42 / v = 42.0 (float64 常量 ) w = iota * 42 / w = 84 (无类型整型常量 ) ) const x = iota / x = 0 (iota 被重置 ) const y = iota / y = 0 (iota 被重置 ) 在常量列表中, 每一个 iota的值是相同的,因为前面说了, 它只会在常量声明之后增加: const ( bit0, mask0 = 1 iota, 1iota - 1 / bit0 = 1, mask0 = 0 bit1, mask1 / bit1 = 2, mask1 = 1 _, _ / 跳过 iota =
42、 2 bit3, mask3 / bit3 = 8, mask3 = 7 ) 上面这个例子利用了隐式地最后一个非空表达式的重复。 类型声明 一个类型声明绑定一个标识符, 类型名 ,至一个新的类型。新的类型和其 底层类型 是一样的,但 不同于 已经存在的类型。 TypeDecl = “type“ ( TypeSpec | “(“ TypeSpec “;“ “)“ ) . TypeSpec = identifier Type . type IntArray 16int type ( Point struct x, y float64 Polar Point ) type TreeNode stru
43、ct 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
44、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 the base type of PtrMutex remains unchanged, / but the method set of PtrMutex is empty. type PtrMutex *Mutex / The
45、method set of *PrintableMutex contains the methods / Lock and Unlock bound to its anonymous 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 Tim
46、eZone = -(5 + iota) CST MST PST ) func (tz TimeZone) String() string return fmt.Sprintf(“GMT+%dh“, tz) 变量声明 一个变量声明创建一个变量,并绑定一个标识符到该变量;声明的时候需要指定类型,而初始值则是可选的。 VarDecl = “var“ ( VarSpec | “(“ VarSpec “;“ “)“ ) . VarSpec = IdentifierList ( Type “=“ ExpressionList | “=“ ExpressionList ) . var i int var U
47、, 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 = entriesname / map 查找;这里只对 “found“ 感兴趣 如果指定了初始化表达式列表,那么变量就按顺序使用表达式进行 赋值 ;所有的表达式都会用来初始化某个变量,没有对应表达式的变量会初始化为 0 值 。 如果类型指定,那么每个变量都是那种类型。否则的话,类型通过对表达式列表进行推断而得。 如果类型没有指定而且对应的表达式求得的是一个 常量 ,那么这个声明的变量取 赋值 这里描述的类型。 实现限制:一个编译器可以不允许在 函数体 内声明变量但是却从来不适用的情况。 短变量声明 可以使用 短变量声明 : ShortVarDecl = IdentifierList “:=“ ExpressionList . 它是一个 变量声明 不带类型的简化版: “var“ IdentifierList = ExpressionList . i, j := 0, 10 f