Go 编程语言规范
2021 年 10 月 15 日版本
简介
这是 Go 编程语言的参考手册,其内容基于 2021 年 10 月语言版本 1.17,在泛型引入之前。出于历史参考目的提供此手册。当前的参考手册可在此处找到。有关更多信息和其他文档,请参阅go.dev。
Go 是一种通用语言,其设计考虑了系统编程。它具有强类型和垃圾回收机制,并明确支持并发编程。程序由包构建,其属性允许有效管理依赖项。
语法简洁且易于解析,这使得自动工具(如集成开发环境)能够轻松地进行分析。
符号
语法使用扩展巴克斯-诺尔范式 (EBNF) 指定。
Production = production_name "=" [ Expression ] "." . Expression = Alternative { "|" Alternative } . Alternative = Term { Term } . Term = production_name | token [ "…" token ] | Group | Option | Repetition . Group = "(" Expression ")" . Option = "[" Expression "]" . Repetition = "{" Expression "}" .
产生式是由术语和以下运算符构成的表达式,按优先级递增排列
| alternation () grouping [] option (0 or 1 times) {} repetition (0 to n times)
小写产生式名称用于标识词法标记。非终结符采用驼峰命名法。词法标记用双引号""
或反引号``
括起来。
形式a … b
表示字符集,其中a
到b
作为备选方案。水平省略号…
也用于规范的其他地方,以非正式方式表示未进一步指定的各种枚举或代码片段。字符…
(与三个字符...
相对)不是 Go 语言的标记。
源代码表示
源代码是使用UTF-8编码的 Unicode 文本。文本未规范化,因此单个带重音的代码点与由组合重音和字母构成的相同字符不同;这些被视为两个代码点。为简单起见,本文档将使用未限定的术语字符来指代源文本中的 Unicode 代码点。
每个代码点都是不同的;例如,大写字母和小写字母是不同的字符。
实现限制:为了与其他工具兼容,编译器可能会禁止源文本中的 NUL 字符 (U+0000)。
实现限制:为了与其他工具兼容,如果 UTF-8 编码的字节顺序标记 (U+FEFF) 是源文本中的第一个 Unicode 代码点,则编译器可能会忽略它。在源代码的其他任何地方都可能不允许使用字节顺序标记。
字符
以下术语用于表示特定的 Unicode 字符类
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 数字。
字母和数字
下划线字符_
(U+005F) 被视为字母。
letter = unicode_letter | "_" . decimal_digit = "0" … "9" . binary_digit = "0" | "1" . octal_digit = "0" … "7" . hex_digit = "0" … "9" | "A" … "F" | "a" … "f" .
词法元素
注释
注释用作程序文档。有两种形式
-
行注释以字符序列
//
开头,到行尾结束。 -
一般注释以字符序列
/*
开头,到第一个后续字符序列*/
结束。
注释不能在rune或字符串字面量内部或注释内部开始。不包含换行符的一般注释的作用类似于空格。任何其他注释都类似于换行符。
标记
标记构成 Go 语言的词汇。有四类:标识符、关键字、运算符和标点符号以及字面量。空白由空格 (U+0020)、水平制表符 (U+0009)、回车符 (U+000D) 和换行符 (U+000A) 组成,除了用于分隔否则将组合成单个标记的标记外,其他情况下都会被忽略。此外,换行符或文件结尾可能会触发分号的插入。在将输入分解为标记时,下一个标记是构成有效标记的最长字符序列。
分号
正式语法在许多产生式中使用分号";"
作为终止符。Go 程序可以使用以下两条规则省略大多数这些分号
- 当输入被分解成标记时,如果该行的最后一个标记是:
- 则会自动将分号插入标记流中,紧跟在该行的最后一个标记之后。
为了允许复杂的语句占据一行,可以在闭合")"
或"}"
之前省略分号。
为了反映习惯用法,本文档中的代码示例使用这些规则省略分号。
标识符
identifier = letter { letter | unicode_digit } .
a _x9 ThisVariableIsExported αβ
标识符为程序实体(如变量和类型)命名。标识符是由一个或多个字母和数字组成的序列。标识符中的第一个字符必须是字母。
某些标识符是预声明的。
关键字
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
以下关键字是保留关键字,不能用作标识符。
运算符和标点符号
+ & += &= && == != ( ) - | -= |= || < <= [ ] * ^ *= ^= <- > >= { } / << /= <<= ++ = := , ; % >> %= >>= -- ! ... . : &^ &^=
以下字符序列表示运算符(包括赋值运算符)和标点符号
整数字面量
整数字面量是表示整数常量的数字序列。可选的前缀设置非十进制基数:0b
或0B
表示二进制,0
、0o
或0O
表示八进制,0x
或0X
表示十六进制。单个0
被视为十进制零。在十六进制字面量中,字母a
到f
以及A
到F
分别表示值 10 到 15。
int_lit = decimal_lit | binary_lit | octal_lit | hex_lit . decimal_lit = "0" | ( "1" … "9" ) [ [ "_" ] decimal_digits ] . binary_lit = "0" ( "b" | "B" ) [ "_" ] binary_digits . octal_lit = "0" [ "o" | "O" ] [ "_" ] octal_digits . hex_lit = "0" ( "x" | "X" ) [ "_" ] hex_digits . decimal_digits = decimal_digit { [ "_" ] decimal_digit } . binary_digits = binary_digit { [ "_" ] binary_digit } . octal_digits = octal_digit { [ "_" ] octal_digit } . hex_digits = hex_digit { [ "_" ] hex_digit } .
42 4_2 0600 0_600 0o600 0O600 // second character is capital letter 'O' 0xBadFace 0xBad_Face 0x_67_7a_2f_cc_40_c6 170141183460469231731687303715884105727 170_141183_460469_231731_687303_715884_105727 _42 // an identifier, not an integer literal 42_ // invalid: _ must separate successive digits 4__2 // invalid: only one _ at a time 0_xBadFace // invalid: _ must separate successive digits
为了提高可读性,可以在基数前缀之后或连续数字之间使用下划线字符_
;这些下划线不会更改字面量的值。
浮点数字面量
浮点数字面量是浮点数常量的十进制或十六进制表示形式。
十进制浮点数字面量由整数部分(十进制数字)、小数点、小数部分(十进制数字)和指数部分(e
或E
后跟可选符号和十进制数字)组成。整数部分或小数部分之一可以省略;小数点或指数部分之一也可以省略。指数值 exp 将尾数(整数和小数部分)按 10exp缩放。
十六进制浮点数字面量由0x
或0X
前缀、整数部分(十六进制数字)、基数点、小数部分(十六进制数字)和指数部分(p
或P
后跟可选符号和十进制数字)组成。整数部分或小数部分之一可以省略;基数点也可以省略,但指数部分是必需的。(此语法与 IEEE 754-2008 §5.12.3 中给出的语法匹配。)指数值 exp 将尾数(整数和小数部分)按 2exp缩放。
float_lit = decimal_float_lit | hex_float_lit . decimal_float_lit = decimal_digits "." [ decimal_digits ] [ decimal_exponent ] | decimal_digits decimal_exponent | "." decimal_digits [ decimal_exponent ] . decimal_exponent = ( "e" | "E" ) [ "+" | "-" ] decimal_digits . hex_float_lit = "0" ( "x" | "X" ) hex_mantissa hex_exponent . hex_mantissa = [ "_" ] hex_digits "." [ hex_digits ] | [ "_" ] hex_digits | "." hex_digits . hex_exponent = ( "p" | "P" ) [ "+" | "-" ] decimal_digits .
0. 72.40 072.40 // == 72.40 2.71828 1.e+0 6.67428e-11 1E6 .25 .12345E+5 1_5. // == 15.0 0.15e+0_2 // == 15.0 0x1p-2 // == 0.25 0x2.p10 // == 2048.0 0x1.Fp+0 // == 1.9375 0X.8p-0 // == 0.5 0X_1FFFP-16 // == 0.1249847412109375 0x15e-2 // == 0x15e - 2 (integer subtraction) 0x.p1 // invalid: mantissa has no digits 1p-2 // invalid: p exponent requires hexadecimal mantissa 0x1.5e-2 // invalid: hexadecimal mantissa requires p exponent 1_.5 // invalid: _ must separate successive digits 1._5 // invalid: _ must separate successive digits 1.5_e1 // invalid: _ must separate successive digits 1.5e_1 // invalid: _ must separate successive digits 1.5e1_ // invalid: _ must separate successive digits
为了提高可读性,可以在基数前缀之后或连续数字之间使用下划线字符_
;这些下划线不会更改字面量的值。
虚数字面量
imaginary_lit = (decimal_digits | int_lit | float_lit) "i" .
虚数字面量表示复数常量的虚部。它由一个整数或浮点数字面量后跟小写字母i
组成。虚数字面量的值是相应整数或浮点数字面量的值乘以虚数单位i。
0i 0123i // == 123i for backward-compatibility 0o123i // == 0o123 * 1i == 83i 0xabci // == 0xabc * 1i == 2748i 0.i 2.71828i 1.e+0i 6.67428e-11i 1E6i .25i .12345E+5i 0x1p-2i // == 0x1p-2 * 1i == 0.25i
为了向后兼容,仅由十进制数字(可能还有下划线)组成的虚数字面量的整数部分被视为十进制整数,即使它以前导0
开头。
Rune 字面量
Rune 字面量表示一个rune 常量,一个标识 Unicode 代码点的整数值。Rune 字面量表示为用单引号括起来的一个或多个字符,例如'x'
或'\n'
。在引号内,除了换行符和未转义的单引号外,可以出现任何字符。单个带引号的字符表示字符本身的 Unicode 值,而以反斜杠开头的多字符序列则使用各种格式编码值。
最简单的形式表示引号内的单个字符;由于 Go 源文本是 UTF-8 编码的 Unicode 字符,因此多个 UTF-8 编码的字节可能表示单个整数值。例如,字面量'a'
包含一个表示字面量a
的字节,Unicode U+0061,值0x61
,而'ä'
包含两个字节 (0xc3
0xa4
),表示字面量a
-分音符,U+00E4,值0xe4
。
尽管这些表示形式最终都得到一个整数,但它们的有效范围不同。八进制转义符必须表示 0 到 255(含)之间的值。十六进制转义符在构造上满足此条件。转义符 \u
和 \U
表示 Unicode 代码点,因此在其中一些值是非法的,特别是那些高于 0x10FFFF
和代理对半字节的值。
在反斜杠之后,某些单字符转义符表示特殊值。
\a U+0007 alert or bell \b U+0008 backspace \f U+000C form feed \n U+000A line feed or newline \r U+000D carriage return \t U+0009 horizontal tab \v U+000B vertical tab \\ U+005C backslash \' U+0027 single quote (valid escape only within rune literals) \" U+0022 double quote (valid escape only within string literals)
所有其他以反斜杠开头的序列在 rune 字面量内都是非法的。
rune_lit = "'" ( unicode_value | byte_value ) "'" . unicode_value = unicode_char | little_u_value | big_u_value | escaped_char . byte_value = octal_byte_value | hex_byte_value . octal_byte_value = `\` octal_digit octal_digit octal_digit . hex_byte_value = `\` "x" hex_digit hex_digit . little_u_value = `\` "u" hex_digit hex_digit hex_digit hex_digit . big_u_value = `\` "U" hex_digit hex_digit hex_digit hex_digit hex_digit hex_digit hex_digit hex_digit . escaped_char = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | "'" | `"` ) .
'a' 'ä' '本' '\t' '\000' '\007' '\377' '\x07' '\xff' '\u12e4' '\U00101234' '\'' // rune literal containing single quote character 'aa' // illegal: too many characters '\xa' // illegal: too few hexadecimal digits '\0' // illegal: too few octal digits '\uDFFF' // illegal: surrogate half '\U00110000' // illegal: invalid Unicode code point
字符串字面量
字符串字面量表示通过连接一系列字符获得的字符串常量。有两种形式:原始字符串字面量和解释字符串字面量。
原始字符串字面量是在反引号之间的字符序列,例如 `foo`
。在引号内,除了反引号之外,任何字符都可以出现。原始字符串字面量的值是由引号之间未解释的(隐式 UTF-8 编码)字符组成的字符串;特别是,反斜杠没有任何特殊含义,并且字符串可能包含换行符。原始字符串字面量内的回车符 ('\r') 将从原始字符串值中丢弃。
解释字符串字面量是在双引号之间的字符序列,例如 "bar"
。在引号内,除了换行符和未转义的双引号之外,任何字符都可以出现。引号之间的文本形成字面量的值,反斜杠转义符的解释方式与rune 字面量中的相同(除了 \'
是非法的,而 \"
是合法的),并具有相同的限制。三位八进制 (\
nnn) 和两位十六进制 (\x
nn) 转义符表示结果字符串的单个字节;所有其他转义符表示单个字符的(可能是多字节的)UTF-8 编码。因此,在字符串字面量 \377
和 \xFF
中表示值为 0xFF
=255 的单个字节,而 ÿ
、\u00FF
、\U000000FF
和 \xc3\xbf
表示字符 U+00FF 的 UTF-8 编码的两个字节 0xc3
0xbf
。
string_lit = raw_string_lit | interpreted_string_lit . raw_string_lit = "`" { unicode_char | newline } "`" . interpreted_string_lit = `"` { unicode_value | byte_value } `"` .
`abc` // same as "abc" `\n \n` // same as "\\n\n\\n" "\n" "\"" // same as `"` "Hello, world!\n" "日本語" "\u65e5本\U00008a9e" "\xff\u00FF" "\uD800" // illegal: surrogate half "\U00110000" // illegal: invalid Unicode code point
这些示例都表示相同的字符串。
"日本語" // UTF-8 input text `日本語` // UTF-8 input text as a raw literal "\u65e5\u672c\u8a9e" // the explicit Unicode code points "\U000065e5\U0000672c\U00008a9e" // the explicit Unicode code points "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e" // the explicit UTF-8 bytes
如果源代码将字符表示为两个代码点,例如涉及重音和字母的组合形式,则如果将其放在 rune 字面量中,则会导致错误(它不是单个代码点),如果将其放在字符串字面量中,则会显示为两个代码点。
常量
有布尔常量、rune 常量、整数常量、浮点数常量、复数常量和字符串常量。rune、整数、浮点数和复数常量统称为数值常量。
常量值由rune、整数、浮点数、虚数或字符串字面量、表示常量的标识符、常量表达式、结果为常量的转换或某些内置函数(如应用于任何值的 unsafe.Sizeof
、应用于某些表达式的 cap
或 len
、应用于复数常量的 real
和 imag
以及应用于数值常量的 complex
)的结果值表示。布尔真值由预声明的常量 true
和 false
表示。预声明的标识符iota表示整数常量。
通常,复数常量是常量表达式的一种形式,并在该部分中进行讨论。
数值常量表示任意精度的精确值,并且不会溢出。因此,没有表示 IEEE 754 负零、无穷大和非数字值的常量。
常量可以是类型化的或未类型化的。字面量常量、true
、false
、iota
以及仅包含未类型化常量操作数的某些常量表达式是未类型化的。
常量可以通过常量声明或转换显式地赋予类型,或者在变量声明或赋值中或作为表达式中的操作数时隐式地赋予类型。如果常量值不能表示为相应类型的值,则会发生错误。
未类型化常量具有一个默认类型,该类型是在需要类型化值的上下文中常量隐式转换为的类型,例如,在简短变量声明(如 i := 0
)中,其中没有显式类型。未类型化常量的默认类型分别为 bool
、rune
、int
、float64
、complex128
或 string
,具体取决于它是布尔、rune、整数、浮点数、复数还是字符串常量。
实现限制:尽管数值常量在语言中具有任意精度,但编译器可以使用具有有限精度的内部表示来实现它们。也就是说,每个实现都必须
- 用至少 256 位表示整数常量。
- 用至少 256 位的尾数和至少 16 位的有符号二进制指数表示浮点数常量(包括复数常量的一部分)。
- 如果无法精确地表示整数常量,则给出错误。
- 如果由于溢出而无法表示浮点数或复数常量,则给出错误。
- 如果由于精度限制而无法表示浮点数或复数常量,则四舍五入到最接近的可表示常量。
这些要求既适用于字面量常量,也适用于评估常量表达式的结果。
变量
变量是用于存储值的存储位置。允许的值集由变量的类型确定。
变量声明或对于函数参数和结果,函数声明或函数字面量的签名为命名变量保留存储空间。调用内置函数new
或获取复合字面量的地址会在运行时为变量分配存储空间。此类匿名变量通过(可能是隐式的)指针间接寻址来引用。
数组、切片和结构体类型的结构化变量具有可以单独寻址的元素和字段。每个这样的元素都像一个变量。
变量的静态类型(或简称类型)是在其声明中给出的类型、在 new
调用或复合字面量中提供的类型,或结构化变量的元素的类型。接口类型变量还具有不同的动态类型,该类型是在运行时分配给变量的值的具体类型(除非该值是预声明的标识符 nil
,它没有类型)。动态类型在执行期间可能会发生变化,但存储在接口变量中的值始终可以赋值给变量的静态类型。
var x interface{} // x is nil and has static type interface{} var v *T // v has value nil, static type *T x = 42 // x has value 42 and dynamic type int x = v // x has value (*T)(nil) and dynamic type *T
通过在表达式中引用变量来检索变量的值;它是最近赋值给变量的值。如果变量尚未赋值,则其值为其类型的零值。
类型
类型确定一组值以及特定于这些值的运算和方法。类型可以通过类型名称(如果存在)来表示,或者使用类型字面量来指定,后者从现有类型组合类型。
Type = TypeName | TypeLit | "(" Type ")" . TypeName = identifier | QualifiedIdent . TypeLit = ArrayType | StructType | PointerType | FunctionType | InterfaceType | SliceType | MapType | ChannelType .
该语言预声明某些类型名称。其他类型通过类型声明引入。复合类型——数组、结构体、指针、函数、接口、切片、映射和通道类型——可以使用类型字面量构造。
每个类型 T
都具有一个底层类型:如果 T
是预声明的布尔、数值或字符串类型之一,或类型字面量,则相应的底层类型为 T
本身。否则,T
的底层类型是 T
在其类型声明中引用的类型的底层类型。
type ( A1 = string A2 = A1 ) type ( B1 string B2 B1 B3 []B1 B4 B3 )
string
、A1
、A2
、B1
和 B2
的底层类型为 string
。[]B1
、B3
和 B4
的底层类型为 []B1
。
方法集
类型与其关联的(可能是空的)方法集。接口类型的方法集是其接口。任何其他类型 T
的方法集由所有以接收器类型 T
声明的方法组成。相应指针类型 *T
的方法集是以接收器 *T
或 T
声明的所有方法的集合(即,它还包含 T
的方法集)。进一步的规则适用于包含嵌入字段的结构体,如结构体类型部分所述。任何其他类型都具有空方法集。在方法集中,每个方法都必须具有唯一的非空白方法名。
类型的��方法集确定该类型实现的接口以及可以使用该类型的接收器调用的方法。
布尔类型
布尔类型表示由预声明的常量 true
和 false
表示的布尔真值集。预声明的布尔类型为 bool
;它是一个已定义类型。
数值类型
数值类型表示整数或浮点值的集合。预声明的与体系结构无关的数值类型为
uint8 the set of all unsigned 8-bit integers (0 to 255) uint16 the set of all unsigned 16-bit integers (0 to 65535) uint32 the set of all unsigned 32-bit integers (0 to 4294967295) uint64 the set of all unsigned 64-bit integers (0 to 18446744073709551615) int8 the set of all signed 8-bit integers (-128 to 127) int16 the set of all signed 16-bit integers (-32768 to 32767) int32 the set of all signed 32-bit integers (-2147483648 to 2147483647) int64 the set of all signed 64-bit integers (-9223372036854775808 to 9223372036854775807) float32 the set of all IEEE 754 32-bit floating-point numbers float64 the set of all IEEE 754 64-bit floating-point numbers complex64 the set of all complex numbers with float32 real and imaginary parts complex128 the set of all complex numbers with float64 real and imaginary parts byte alias for uint8 rune alias for int32
n 位整数的值为 n 位宽,并使用二进制补码算术表示。
还有一组具有实现特定大小的预声明数值类型
uint either 32 or 64 bits int same size as uint uintptr an unsigned integer large enough to store the uninterpreted bits of a pointer value
为了避免可移植性问题,所有数值类型都是已定义类型,因此除了 byte
(它是 uint8
的别名)和 rune
(它是 int32
的别名)之外,都是不同的。当在表达式或赋值中混合使用不同的数值类型时,需要显式转换。例如,即使 int32
和 int
在特定体系结构上可能具有相同的大小,它们也不是相同的类型。
字符串类型
字符串类型表示字符串值的集合。字符串值是(可能是空的)字节序列。字节数称为字符串的长度,绝不为负。字符串是不可变的:一旦创建,就无法更改字符串的内容。预声明的字符串类型为 string
;它是一个已定义类型。
可以使用内置函数len
发现字符串 s
的长度。如果字符串是常量,则长度是编译时常量。字符串的字节可以通过整数索引 0 到 len(s)-1
访问。获取此类元素的地址是非法的;如果 s[i]
是字符串的第 i
个字节,则 &s[i]
是无效的。
数组类型
数组是由同一类型元素组成的编号序列,该类型称为元素类型。元素的数量称为数组的长度,且永远不会为负。
ArrayType = "[" ArrayLength "]" ElementType . ArrayLength = Expression . ElementType = Type .
长度是数组类型的一部分;它必须计算为一个非负的常量,并且可以通过类型为int
的值进行表示。可以使用内置函数len
来获取数组a
的长度。可以使用整数索引 0 到 len(a)-1
来访问元素。数组类型始终是一维的,但可以组合形成多维类型。
[32]byte [2*N] struct { x, y int32 } [1000]*float64 [3][5]int [2][2][2]float64 // same as [2]([2]([2]float64))
切片类型
切片是底层数组的连续段的描述符,并提供对该数组中编号元素序列的访问。切片类型表示其元素类型的所有数组切片的集合。元素的数量称为切片的长度,且永远不会为负。未初始化切片的值为nil
。
SliceType = "[" "]" ElementType .
可以使用内置函数len
来获取切片s
的长度;与数组不同,它在执行期间可能会发生变化。可以使用整数索引 0 到 len(s)-1
来访问元素。给定元素的切片索引可能小于该元素在底层数组中的索引。
切片一旦初始化,就会始终与保存其元素的底层数组关联。因此,切片与它的数组以及同一数组的其他切片共享存储空间;相比之下,不同的数组始终表示不同的存储空间。
切片底层的数组可能超出切片的末尾。容量是该范围的度量:它是切片长度与数组超出切片部分长度的总和;可以通过切片从原始切片创建一个长度不超过该容量的新切片。可以使用内置函数cap(a)
来获取切片a
的容量。
可以使用内置函数make
为给定的元素类型T
创建一个新的初始化切片值,该函数接受切片类型以及指定长度和可选容量的参数。使用make
创建的切片始终会分配一个新的隐藏数组,返回的切片值会引用该数组。也就是说,执行
make([]T, length, capacity)
产生的切片与分配数组并切片它产生的切片相同,因此这两个表达式是等价的
make([]int, 50, 100) new([100]int)[0:50]
与数组类似,切片始终是一维的,但可以组合构造更高维度的对象。对于数组的数组,内部数组在构造时始终具有相同的长度;但是对于切片的切片(或数组的切片),内部长度可以动态变化。此外,必须单独初始化内部切片。
结构体类型
结构体是由命名元素组成的序列,称为字段,每个字段都有一个名称和一个类型。字段名称可以显式指定(IdentifierList)或隐式指定(EmbeddedField)。在结构体中,非空白字段名称必须唯一。
StructType = "struct" "{" { FieldDecl ";" } "}" . FieldDecl = (IdentifierList Type | EmbeddedField) [ Tag ] . EmbeddedField = [ "*" ] TypeName . Tag = string_lit .
// An empty struct. struct {} // A struct with 6 fields. struct { x, y int u float32 _ float32 // padding A *[]int F func() }
声明了类型但没有显式字段名称的字段称为嵌入字段。嵌入字段必须指定为类型名称T
或指向非接口类型名称*T
的指针,并且T
本身不能是指针类型。未限定的类型名称充当字段名称。
// 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 }
以下声明是非法的,因为字段名称在结构体类型中必须唯一
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 }
如果x.f
是一个合法的选择器,表示该字段或方法f
,则结构体x
中嵌入字段的字段或方法f
称为提升的。
提升的字段的行为类似于结构体的普通字段,但它们不能用作结构体复合字面量中的字段名称。
给定一个结构体类型S
和一个定义类型T
,提升的方法包含在结构体的方法集中,如下所示
- 如果
S
包含一个嵌入字段T
,则S
和*S
的方法集都包含接收者为T
的提升方法。*S
的方法集还包含接收者为*T
的提升方法。 - 如果
S
包含一个嵌入字段*T
,则S
和*S
的方法集都包含接收者为T
或*T
的提升方法。
字段声明后面可以跟一个可选的字符串文字标签,该标签成为对应字段声明中所有字段的属性。空标签字符串等效于不存在标签。这些标签可以通过反射接口显示,并参与结构体的类型标识,但在其他情况下会被忽略。
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"` }
指针类型
指针类型表示所有指向给定类型变量的指针的集合,该类型称为指针的底层类型。未初始化指针的值为nil
。
PointerType = "*" BaseType . BaseType = Type .
*Point *[4]int
函数类型
函数类型表示所有具有相同参数和结果类型的函数的集合。未初始化的函数类型变量的值为nil
。
FunctionType = "func" Signature . Signature = Parameters [ Result ] . Result = Parameters | Type . Parameters = "(" [ ParameterList [ "," ] ] ")" . ParameterList = ParameterDecl { "," ParameterDecl } . ParameterDecl = [ IdentifierList ] [ "..." ] Type .
在参数或结果列表中,名称(IdentifierList)必须全部存在或全部不存在。如果存在,每个名称代表一个指定类型项(参数或结果),并且签名中所有非空白名称必须唯一。如果不存在,每个类型代表一个该类型的项。参数和结果列表始终用括号括起来,但如果只有一个未命名的结果,则可以将其写为未括起来的类型。
函数签名中的最后一个传入参数可以具有以...
为前缀的类型。具有此类参数的函数称为可变参数函数,并且可以使用该参数的零个或多个参数进行调用。
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)
接口类型
接口类型指定一个称为其接口的方法集。接口类型的变量可以存储任何类型的值,该类型的方法集是接口的任何超集。这样的类型被称为实现了该接口。未初始化的接口类型变量的值为nil
。
InterfaceType = "interface" "{" { ( MethodSpec | InterfaceTypeName ) ";" } "}" . MethodSpec = MethodName Signature . MethodName = identifier . InterfaceTypeName = TypeName .
接口类型可以通过方法规范显式地指定方法,或者可以通过接口类型名称嵌入其他接口的方法。
// A simple File interface. interface { Read([]byte) (int, error) Write([]byte) (int, error) Close() error }
interface { String() string String() string // illegal: String not unique _(x int) // illegal: method must have non-blank name }
多个类型可以实现一个接口。例如,如果两种类型S1
和S2
具有方法集
func (p T) Read(p []byte) (n int, err error) func (p T) Write(p []byte) (n int, err error) func (p T) Close() error
(其中T
代表S1
或S2
)则File
接口由S1
和S2
都实现,无论S1
和S2
可能具有或共享的其他方法是什么。
一个类型实现了包含其任何子集方法的任何接口,因此可以实现几个不同的接口。例如,所有类型都实现了空接口
interface{}
同样,考虑此接口规范,它出现在类型声明中以定义名为Locker
的接口
type Locker interface { Lock() Unlock() }
如果S1
和S2
也实现了
func (p T) Lock() { … } func (p T) Unlock() { … }
它们也实现了Locker
接口以及File
接口。
接口T
可以使用(可能是限定的)接口类型名称E
代替方法规范。这称为在T
中嵌入接口E
。T
的方法集是T
的显式声明方法和T
的嵌入接口的方法集的并集。
type Reader interface { Read(p []byte) (n int, err error) Close() error } type Writer interface { Write(p []byte) (n int, err error) Close() error } // ReadWriter's methods are Read, Write, and Close. type ReadWriter interface { Reader // includes methods of Reader in ReadWriter's method set Writer // includes methods of Writer in ReadWriter's method set }
方法集的并集包含每个方法集(导出和未导出)的方法各一次,并且名称相同的方法必须具有相同的签名。
type ReadCloser interface { Reader // includes methods of Reader in ReadCloser's method set Close() // illegal: signatures of Reader.Close and Close are different }
接口类型T
不能嵌入自身或任何递归嵌入T
的接口类型。
// illegal: Bad cannot embed itself type Bad interface { Bad } // illegal: Bad1 cannot embed itself using Bad2 type Bad1 interface { Bad2 } type Bad2 interface { Bad1 }
映射类型
映射是一组无序的元素,这些元素属于一种类型,称为元素类型,由另一类型的一组唯一键索引,称为键类型。未初始化映射的值为nil
。
MapType = "map" "[" KeyType "]" ElementType . KeyType = Type .
比较运算符==
和!=
必须针对键类型的操作数完全定义;因此,键类型不能是函数、映射或切片。如果键类型是接口类型,则必须为动态键值定义这些比较运算符;否则将导致运行时恐慌。
map[string]int map[*T]struct{ x, y float64 } map[string]interface{}
映射元素的数量称为其长度。对于映射m
,可以使用内置函数len
来获取它,并且它可以在执行期间发生变化。可以在执行期间使用赋值添加元素,并使用索引表达式检索元素;可以使用delete
内置函数删除它们。
可以使用内置函数make
创建一个新的空映射值,该函数接受映射类型和可选的容量提示作为参数
make(map[string]int) make(map[string]int, 100)
初始容量不会限制其大小:映射会增长以适应其中存储的项目数量,nil
映射除外。nil
映射等效于空映射,但不能添加任何元素。
通道类型
通道提供了一种机制,用于通过发送和接收指定元素类型的值,使并发执行的函数能够进行通信。未初始化通道的值为nil
。
ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .
可选的<-
运算符指定通道的方向,即发送或接收。如果没有给出方向,则通道为双向的。通道可以通过赋值或显式转换仅限于发送或仅限于接收。
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
<-
运算符与最左边的chan
关联
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)
容量(以元素数表示)设置通道中缓冲区的大小。如果容量为零或不存在,则通道为无缓冲通道,并且只有在发送方和接收方都准备好时,通信才会成功。否则,通道为缓冲通道,如果缓冲区未满(发送)或不为空(接收),则通信将不会阻塞。nil
通道永远不会准备好进行通信。
可以使用内置函数close
关闭通道。接收运算符的多值赋值形式报告接收到的值是在通道关闭之前发送的还是之后发送的。
任何数量的goroutine都可以使用单个通道进行发送语句、接收操作以及对内置函数cap
和len
的调用,而无需进一步同步。通道充当先进先出队列。例如,如果一个goroutine在通道上发送值,而第二个goroutine接收它们,则接收到的值的顺序与发送的顺序相同。
类型和值的属性
类型标识
两种类型要么是相同的,要么是不同的。
一个已定义类型总是与任何其他类型不同。否则,如果两个类型的底层类型字面量在结构上等价,则这两个类型相同;也就是说,它们具有相同的字面量结构,并且对应的组件具有相同的类型。详细如下
- 如果两个数组类型具有相同的元素类型和相同的数组长度,则它们是相同的。
- 如果两个切片类型具有相同的元素类型,则它们是相同的。
- 如果两个结构体类型具有相同的字段序列,并且如果对应的字段具有相同的名称、相同的类型和相同的标签,则它们是相同的。未导出的来自不同包的字段名称总是不同的。
- 如果两个指针类型具有相同的基类型,则它们是相同的。
- 如果两个函数类型具有相同数量的参数和结果值,对应的参数和结果类型相同,并且两个函数都是可变参数或都不是可变参数,则它们是相同的。参数和结果名称不需要匹配。
- 如果两个接口类型具有相同的方法集,并且方法具有相同的名称和相同的函数类型,则它们是相同的。未导出的来自不同包的方法名称总是不同的。方法的顺序无关紧要。
- 如果两个映射类型具有相同的键和元素类型,则它们是相同的。
- 如果两个通道类型具有相同的元素类型和相同的方向,则它们是相同的。
给定以下声明
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
这些类型是相同的
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
不同,因为它们是由不同的类型定义创建的新类型;func(int, float64) *B0
和 func(x int, y float64) *[]string
不同,因为 B0
与 []string
不同。
可赋值性
如果以下条件之一适用,则值 x
可赋值给类型为 T
的变量(“x
可赋值给 T
”)
-
x
的类型与T
相同。 -
x
的类型V
和T
具有相同的底层类型,并且V
或T
中至少有一个不是已定义类型。 -
T
是一个接口类型,并且x
实现了T
。 -
x
是一个双向通道值,T
是一个通道类型,x
的类型V
和T
具有相同的元素类型,并且V
或T
中至少有一个不是已定义类型。 -
x
是预声明标识符nil
,并且T
是一个指针、函数、切片、映射、通道或接口类型。 -
x
是一个无类型常量,可表示为类型T
的值。
可表示性
如果以下条件之一适用,则常量 x
可表示为类型 T
的值
-
x
位于由T
确定的值集中。 -
T
是一个浮点数类型,并且x
可以舍入到T
的精度而不会溢出。舍入使用 IEEE 754 向偶数舍入规则,但 IEEE 负零进一步简化为无符号零。请注意,常量值永远不会导致 IEEE 负零、NaN 或无穷大。 -
T
是一个复数类型,并且x
的分量real(x)
和imag(x)
可表示为T
的分量类型(float32
或float64
)的值。
x T x is representable by a value of T because 'a' byte 97 is in the set of byte values 97 rune rune is an alias for int32, and 97 is in the set of 32-bit integers "foo" string "foo" is in the set of string values 1024 int16 1024 is in the set of 16-bit integers 42.0 byte 42 is in the set of unsigned 8-bit integers 1e10 uint64 10000000000 is in the set of unsigned 64-bit integers 2.718281828459045 float32 2.718281828459045 rounds to 2.7182817 which is in the set of float32 values -1e-1000 float64 -1e-1000 rounds to IEEE -0.0 which is further simplified to 0.0 0i int 0 is an integer value (42 + 0i) float32 42.0 (with zero imaginary part) is in the set of float32 values
x T x is not representable by a value of T because 0 bool 0 is not in the set of boolean values 'a' string 'a' is a rune, it is not in the set of string values 1024 byte 1024 is not in the set of unsigned 8-bit integers -1 uint16 -1 is not in the set of unsigned 16-bit integers 1.1 int 1.1 is not an integer value 42i float32 (0 + 42i) is not in the set of float32 values 1e1000 float64 1e1000 overflows to IEEE +Inf after rounding
块
块是在匹配的大括号内的可能为空的声明和语句序列。
Block = "{" StatementList "}" . StatementList = { Statement ";" } .
除了源代码中的显式块外,还有隐式块
- 宇宙块包含所有 Go 源文本。
- 每个包都有一个包块,其中包含该包的所有 Go 源文本。
- 每个文件都有一个文件块,其中包含该文件的所有 Go 源文本。
- 每个“if”、“for” 和“switch” 语句都被认为在其自己的隐式块中。
- 每个“switch” 或“select” 语句中的子句都充当一个隐式块。
块嵌套并影响作用域。
声明和作用域
声明将一个非空白标识符绑定到常量、类型、变量、函数、标签或包。程序中的每个标识符都必须声明。在同一个块中不允许声明两次相同的标识符,并且不允许在文件块和包块中都声明相同的标识符。
空白标识符可以像声明中的任何其他标识符一样使用,但它不会引入绑定,因此未声明。在包块中,标识符 init
只能用于init
函数声明,并且像空白标识符一样,它不会引入新的绑定。
Declaration = ConstDecl | TypeDecl | VarDecl . TopLevelDecl = Declaration | FunctionDecl | MethodDecl .
已声明标识符的作用域是源文本中标识符表示指定常量、类型、变量、函数、标签或包的范围。
Go 使用块进行词法作用域。
- 预声明标识符的作用域是宇宙块。
- 在顶层(在任何函数之外)声明的表示常量、类型、变量或函数(但不是方法)的标识符的作用域是包块。
- 导入包的包名的作用域是包含导入声明的文件的文件块。
- 表示方法接收器、函数参数或结果变量的标识符的作用域是函数体。
- 在函数内部声明的常量或变量标识符的作用域从 ConstSpec 或 VarSpec(对于短变量声明,为 ShortVarDecl)的末尾开始,到最内层包含块的末尾结束。
- 在函数内部声明的类型标识符的作用域从 TypeSpec 中的标识符开始,到最内层包含块的末尾结束。
在块中声明的标识符可以在内部块中重新声明。在内部声明的标识符的作用域内,它表示由内部声明声明的实体。
包子句不是声明;包名不会出现在任何作用域中。其目的是识别属于同一个包的文件,并为导入声明指定默认包名。
标签作用域
标签由带标签的语句声明,并在“break”、“continue” 和“goto” 语句中使用。定义一个从未使用的标签是非法的。与其他标识符相反,标签不是块作用域的,并且不会与不是标签的标识符冲突。标签的作用域是声明它的函数的主体,并且不包括任何嵌套函数的主体。
空白标识符
空白标识符由下划线字符 _
表示。它充当匿名占位符而不是常规(非空白)标识符,并在声明、作为操作数以及在赋值中具有特殊含义。
预声明标识符
以下标识符在宇宙块中隐式声明
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
导出标识符
标识符可以导出以允许从另一个包访问它。如果同时满足以下条件,则标识符被导出
所有其他标识符都不会导出。
标识符的唯一性
给定一组标识符,如果一个标识符与该集合中的所有其他标识符不同,则称该标识符为唯一的。如果两个标识符的拼写不同,或者如果它们出现在不同的包中且未导出,则它们是不同的。否则,它们是相同的。
常量声明
常量声明将标识符列表(常量的名称)绑定到常量表达式列表的值。标识符的数量必须等于表达式的数量,左侧的第 n 个标识符绑定到右侧第 n 个表达式的值。
ConstDecl = "const" ( ConstSpec | "(" { ConstSpec ";" } ")" ) . ConstSpec = IdentifierList [ [ Type ] "=" ExpressionList ] . IdentifierList = identifier { "," identifier } . ExpressionList = Expression { "," Expression } .
如果存在类型,则所有常量都采用指定的类型,并且表达式必须可赋值给该类型。如果省略类型,则常量采用对应表达式的各个类型。如果表达式值是无类型常量,则声明的常量保持无类型,并且常量标识符表示常量值。例如,如果表达式是浮点字面量,则常量标识符表示浮点常量,即使字面量的分数部分为零。
const Pi float64 = 3.14159265358979323846 const zero = 0.0 // untyped floating-point constant const ( size int64 = 1024 eof = -1 // untyped integer constant ) const a, b, c = 3, 4, "foo" // a = 3, b = 4, c = "foo", untyped integer and string constants const u, v float32 = 0, 3 // u = 0.0, v = 3.0
在带括号的 const
声明列表中,可以从除第一个 ConstSpec 之外的任何表达式列表中省略表达式列表。这样的空列表等效于第一个前面的非空表达式列表及其类型(如果有)的文本替换。因此,省略表达式列表等效于重复前面的列表。标识符的数量必须等于前面列表中表达式的数量。结合iota
常量生成器,此机制允许轻量级声明顺序值
const ( Sunday = iota Monday Tuesday Wednesday Thursday Friday Partyday numberOfDays // this constant is not exported )
Iota
在常量声明中,预声明标识符 iota
表示连续的无类型整数常量。它的值是在该常量声明中相应ConstSpec的索引,从零开始。它可以用来构建一组相关的常量
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
都具有相同的值。
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) )
最后一个示例利用了最后一个非空表达式列表的隐式重复。
类型声明
类型声明将一个标识符(即类型名称)绑定到一个类型。类型声明有两种形式:别名声明和类型定义。
TypeDecl = "type" ( TypeSpec | "(" { TypeSpec ";" } ")" ) . TypeSpec = AliasDecl | TypeDef .
别名声明
别名声明将一个标识符绑定到给定的类型。
AliasDecl = identifier "=" Type .
在标识符的作用域内,它充当该类型的别名。
type ( nodeList = []*Node // nodeList and []*Node are identical types Polar = polar // Polar and polar denote identical types )
类型定义
类型定义创建一个新的、不同的类型,它与给定类型具有相同的底层类型和操作,并将一个标识符绑定到它。
TypeDef = identifier Type .
新类型称为定义类型。它与任何其他类型(包括其创建的类型)不同。
type ( Point struct{ x, y float64 } // Point and struct{ x, y float64 } are different types polar Point // polar and Point denote different types ) type TreeNode struct { left, right *TreeNode value *Comparable } type Block interface { BlockSize() int Encrypt(src, dst []byte) Decrypt(src, dst []byte) }
定义类型可能与之关联方法。它不继承绑定到给定类型的任何方法,但接口类型或复合类型元素的方法集保持不变。
// 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) }
变量声明
变量声明创建一个或多个变量,将相应的标识符绑定到它们,并为每个变量赋予类型和初始值。
VarDecl = "var" ( VarSpec | "(" { VarSpec ";" } ")" ) . VarSpec = IdentifierList ( Type [ "=" ExpressionList ] | "=" ExpressionList ) .
var i int var U, V, W float64 var k = 0 var x, y float32 = -1, -2 var ( i int u, v, s = 2.0, 3.0, "bar" ) var re, im = complexSqrt(-1) var _, found = entries[name] // map lookup; only interested in "found"
如果给定了一个表达式列表,则变量将根据赋值规则使用表达式进行初始化。否则,每个变量都初始化为其零值。
如果存在类型,则每个变量都将赋予该类型。否则,每个变量都将赋予赋值中相应初始化值的类型。如果该值为无类型常量,则首先将其隐式转换为其默认类型;如果它是一个无类型布尔值,则首先将其隐式转换为bool
类型。预声明的值nil
不能用于初始化没有显式类型的变量。
var d = math.Sin(0.5) // d is float64 var i = 42 // i is int var t, ok = x.(T) // t is T, ok is bool var n = nil // illegal
实现限制:如果一个变量从未被使用,编译器可能会禁止在函数体内声明该变量。
短变量声明
短变量声明使用语法
ShortVarDecl = IdentifierList ":=" ExpressionList .
它是具有初始化表达式但没有类型的常规变量声明的简写形式。
"var" IdentifierList = ExpressionList .
i, j := 0, 10 f := func() int { return 7 } ch := make(chan int) r, w, _ := os.Pipe() // os.Pipe() returns a connected pair of Files and an error, if any _, y, _ := coord(p) // coord() returns three values; only interested in y coordinate
与常规变量声明不同,短变量声明可以重新声明变量,前提是这些变量最初是在相同块(如果块是函数体,则为参数列表)中以相同类型声明的,并且至少有一个非空白变量是新的。因此,重新声明只能出现在多变量短声明中。重新声明不会引入新的变量;它只是将新值赋给原始变量。
field1, offset := nextField(str, 0) field2, offset := nextField(str, offset) // redeclares offset a, a := 1, 2 // illegal: double declaration of a or no new variable if a was declared elsewhere
短变量声明只能出现在函数内部。在某些上下文中,例如"if"、"for"或"switch"语句的初始化程序,它们可用于声明局部临时变量。
函数声明
函数声明将一个标识符(即函数名称)绑定到一个函数。
FunctionDecl = "func" FunctionName Signature [ FunctionBody ] . FunctionName = identifier . FunctionBody = Block .
如果函数的签名声明了结果参数,则函数体的语句列表必须以终止语句结尾。
func IndexRune(s string, r rune) int { for i, c := range s { if c == r { return i } } // invalid: missing return statement }
函数声明可以省略函数体。此类声明提供了在 Go 之外实现的函数(例如汇编例程)的签名。
func min(x int, y int) int { if x < y { return x } return y } func flushICache(begin, end uintptr) // implemented externally
方法声明
方法是一个带有接收器的函数。方法声明将一个标识符(即方法名称)绑定到一个方法,并将该方法与接收器的基础类型关联。
MethodDecl = "func" Receiver MethodName Signature [ FunctionBody ] . Receiver = Parameters .
接收器通过在方法名称之前添加一个额外的参数部分来指定。该参数部分必须声明一个单一的非变长参数,即接收器。其类型必须是定义类型T
或指向定义类型T
的指针。T
称为接收器基础类型。接收器基础类型不能是指针或接口类型,并且必须在与方法相同的包中定义。据说该方法绑定到其接收器基础类型,并且方法名称仅在类型T
或*T
的选择器中可见。
非空白接收器标识符必须在方法签名中唯一。如果接收器的值在方法体内部没有被引用,则可以在声明中省略其标识符。这通常也适用于函数和方法的参数。
对于基础类型,绑定到它的非空白方法名称必须唯一。如果基础类型是结构体类型,则非空白方法和字段名称必须不同。
给定定义类型Point
,声明
func (p *Point) Length() float64 { return math.Sqrt(p.x * p.x + p.y * p.y) } func (p *Point) Scale(factor float64) { p.x *= factor p.y *= factor }
将接收器类型为*Point
的方法Length
和Scale
绑定到基础类型Point
。
方法的类型是具有接收器作为第一个参数的函数的类型。例如,方法Scale
的类型为
func(p *Point, factor float64)
但是,以这种方式声明的函数不是方法。
表达式
表达式通过对操作数应用运算符和函数来指定值的计算。
操作数
操作数表示表达式中的基本值。操作数可以是字面量、(可能限定的)非空白标识符(表示常量、变量或函数),或括号内的表达式。
Operand = Literal | OperandName | "(" Expression ")" . Literal = BasicLit | CompositeLit | FunctionLit . BasicLit = int_lit | float_lit | imaginary_lit | rune_lit | string_lit . OperandName = identifier | QualifiedIdent .
限定标识符
限定标识符是由包名称前缀限定的标识符。包名称和标识符都不得为空白。
QualifiedIdent = PackageName "." identifier .
限定标识符访问不同包中的标识符,该标识符必须已导入。该标识符必须已导出并在该包的包块中声明。
math.Sin // denotes the Sin function in package math
复合字面量
复合字面量构造结构体、数组、切片和映射的值,并在每次评估时创建一个新值。它们由字面量的类型后跟一个由大括号括起来的元素列表组成。每个元素前面都可以选择性地加上相应的键。
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 给出)。元素和键的类型必须可赋值到字面量类型的相应字段、元素和键类型;没有额外的转换。键被解释为结构体字面量的字段名称、数组和切片字面量的索引以及映射字面量的键。对于映射字面量,所有元素都必须具有键。指定具有相同字段名称或常量键值的多个元素是错误的。对于非常量映射键,请参阅有关评估顺序的部分。
对于结构体字面量,适用以下规则
- 键必须是结构体类型中声明的字段名称。
- 不包含任何键的元素列表必须按字段声明的顺序为每个结构体字段列出一个元素。
- 如果任何元素具有键,则每个元素都必须具有键。
- 包含键的元素列表不需要为每个结构体字段都有一个元素。省略的字段将获得该字段的零值。
- 字面量可以省略元素列表;此类字面量计算为其类型的零值。
- 为属于不同包的结构体的非导出字段指定元素是错误的。
给定以下声明
type Point3D struct { x, y, z float64 } type Line struct { p, q Point3D }
可以编写
origin := Point3D{} // zero value for Point3D line := Line{origin, Point3D{y: -4, z: 12.3}} // zero value for line.q.x
对于数组和切片字面量,适用以下规则
- 每个元素都有一个关联的整数索引,用于标记其在数组中的位置。
- 具有键的元素使用键作为其索引。键必须是非负常量,可表示为类型
int
的值;如果它是有类型的,它必须是整数类型。 - 没有键的元素使用前一个元素的索引加一。如果第一个元素没有键,则其索引为零。
获取复合字面量的地址会生成一个指向用字面量值初始化的唯一变量的指针。
var pointer *Point3D = &Point3D{y: 1000}
请注意,切片或映射类型的零值与相同类型的已初始化但为空的值不同。因此,获取空切片或映射复合字面量的地址与使用new分配新的切片或映射值的效果不同。
p1 := &[]int{} // p1 points to an initialized, empty slice with value []int{} and length 0 p2 := new([]int) // p2 points to an uninitialized slice with value nil and length 0
数组字面量的长度是在字面量类型中指定的长度。如果字面量中提供的元素少于长度,则缺少的元素将设置为数组元素类型的零值。为索引值超出数组索引范围的元素提供元素是错误的。...
表示法指定一个长度等于最大元素索引加一的数组。
buffer := [10]string{} // len(buffer) == 10 intSet := [6]int{1, 2, 3, 5} // len(intSet) == 6 days := [...]string{"Sat", "Sun"} // len(days) == 2
切片字面量描述了整个底层数组字面量。因此,切片字面量的长度和容量是最大元素索引加一。切片字面量具有以下形式
[]T{x1, x2, … xn}
并且是应用于数组的切片操作的简写形式
tmp := [n]T{x1, x2, … xn} tmp[0 : n]
在数组、切片或映射类型T
的复合字面量内,如果自身是复合字面量的元素或映射键与T
的元素或键类型相同,则可以省略相应的字面量类型。类似地,如果元素或键类型为*T
,则自身是复合字面量地址的元素或键可以省略&T
。
[...]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{})}
当使用 LiteralType 的 TypeName 形式的复合字面量作为"if"、"for"或"switch"语句的块的关键字和开头大括号之间的操作数时,会出现解析歧义,并且复合字面量未括在括号、方括号或花括号中。在这种罕见的情况下,字面量的开头大括号会被错误地解析为引入语句块的大括号。为了解决歧义,复合字面量必须出现在括号内。
if x == (T{a,b,c}[i]) { … } if (x == T{a,b,c}[i]) { … }
有效数组、切片和映射字面量的示例
// 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, }
函数字面量
函数字面量表示匿名函数。
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)
函数字面量是闭包:它们可以引用周围函数中定义的变量。然后,这些变量在周围函数和函数字面量之间共享,并且只要它们可访问,它们就会一直存在。
主表达式
初级表达式是单目和双目表达式的操作数。
PrimaryExpr = Operand | Conversion | MethodExpr | PrimaryExpr Selector | PrimaryExpr Index | PrimaryExpr Slice | PrimaryExpr TypeAssertion | PrimaryExpr Arguments . Selector = "." identifier . Index = "[" Expression "]" . Slice = "[" [ Expression ] ":" [ Expression ] "]" | "[" [ Expression ] ":" Expression ":" Expression "]" . TypeAssertion = "." "(" Type ")" . Arguments = "(" [ ( ExpressionList | Type [ "," ExpressionList ] ) [ "..." ] [ "," ] ] ")" .
x 2 (s + ".txt") f(3.1415, true) Point{1, 2} m["foo"] s[i : j + 1] obj.color f.p[i].x()
选择器
x.f
表示值 x
(或有时为 *x
;参见下文)的字段或方法 f
。标识符 f
被称为(字段或方法)选择器;它不能是空白标识符。选择器表达式的类型是 f
的类型。如果 x
是一个包名,请参见关于限定标识符的部分。
选择器 f
可以表示类型 T
的字段或方法 f
,或者可以引用 T
的嵌套嵌入字段的字段或方法 f
。遍历以到达 f
的嵌入字段的数量称为其在 T
中的深度。在 T
中声明的字段或方法 f
的深度为零。在 T
中的嵌入字段 A
中声明的字段或方法 f
的深度是 f
在 A
中的深度加一。
以下规则适用于选择器
- 对于类型为
T
或*T
的值x
,其中T
不是指针或接口类型,x.f
表示T
中最浅深度的字段或方法,其中存在这样的f
。如果不存在恰好一个f
具有最浅深度,则选择器表达式是非法的。 - 对于类型为
I
的值x
,其中I
是一个接口类型,x.f
表示x
的动态值的实际方法,其名称为f
。如果方法集I
中不存在名称为f
的方法,则选择器表达式是非法的。 - 作为例外,如果
x
的类型是定义的指针类型,并且(*x).f
是一个有效的选择器表达式,表示一个字段(但不是方法),则x.f
是(*x).f
的简写。 - 在所有其他情况下,
x.f
都是非法的。 - 如果
x
是指针类型并且值为nil
,并且x.f
表示结构体字段,则对x.f
进行赋值或求值会导致运行时恐慌。 - 如果
x
是接口类型并且值为nil
,则调用或求值方法x.f
会导致运行时恐慌。
例如,给定以下声明
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
但以下内容无效
q.M0() // (*q).M0 is valid but not a field selector
方法表达式
如果 M
位于类型 T
的方法集中,则 T.M
是一个函数,可以像常规函数一样调用,其参数与 M
的参数相同,并在前面添加一个作为方法接收者的附加参数。
MethodExpr = ReceiverType "." MethodName . ReceiverType = Type .
考虑一个具有两个方法的结构体类型 T
,Mv
的接收者类型为 T
,Mp
的接收者类型为 *T
。
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
表达式
T.Mv
生成一个等效于 Mv
但具有显式接收者作为其第一个参数的函数;它的签名为
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)
类似地,表达式
(*T).Mp
生成一个表示 Mp
的函数值,其签名为
func(tp *T, f float32) float32
对于具有值接收者的方法,可以派生出一个具有显式指针接收者的函数,因此
(*T).Mv
生成一个表示 Mv
的函数值,其签名为
func(tv *T, a int) int
这样的函数通过接收者进行间接寻址,以创建作为接收者传递给底层方法的值;该方法不会覆盖在函数调用中传递其地址的值。
最后一种情况,指针接收者方法的值接收者函数是非法的,因为指针接收者方法不在值类型的该方法集中。
从方法派生的函数值使用函数调用语法进行调用;接收者作为第一个参数提供给调用。也就是说,给定 f := T.Mv
,f
被调用为 f(t, 7)
而不是 t.f(7)
。要构造一个绑定接收者的函数,请使用函数字面量或方法值。
从接口类型的的某个方法派生函数值是合法的。生成的函数接收该接口类型的显式接收者。
方法值
如果表达式 x
的静态类型为 T
,并且 M
位于类型 T
的方法集中,则 x.M
被称为方法值。方法值 x.M
是一个函数值,可以使用与 x.M
的方法调用相同的参数进行调用。表达式 x
在方法值的求值过程中进行求值并保存;然后将保存的副本用作任何调用中的接收者,这些调用可能在稍后执行。
type S struct { *T } type T int func (t T) M() { print(t) } t := new(T) s := S{T: t} f := t.M // receiver *t is evaluated and stored in f g := s.M // receiver *(s.T) is evaluated and stored in g *t = 42 // does not affect stored receivers in f and g
类型 T
可以是接口类型或非接口类型。
如上文方法表达式的讨论中,考虑一个具有两个方法的结构体类型 T
,Mv
的接收者类型为 T
,Mp
的接收者类型为 *T
。
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
表达式
t.Mv
生成一个类型为
func(int) int
这两个调用是等效的
t.Mv(7) f := t.Mv; f(7)
类似地,表达式
pt.Mp
生成一个类型为
func(float32) float32
与选择器一样,使用指针引用具有值接收者的非接口方法将自动取消对该指针的引用:pt.Mv
等效于 (*pt).Mv
。
与方法调用一样,使用可寻址值引用具有指针接收者的非接口方法将自动获取该值的地址:t.Mp
等效于 (&t).Mp
。
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
尽管上面的示例使用非接口类型,但从接口类型的值创建方法值也是合法的。
var i interface { M(int) } = myVal f := i.M; f(7) // like i.M(7)
索引表达式
形式为
a[x]
的初级表达式表示由 x
索引的数组、指向数组的指针、切片、字符串或映射 a
的元素。值 x
分别称为索引或映射键。以下规则适用
如果 a
不是映射
- 索引
x
必须是整数类型或未类型常量 - 常量索引必须是非负的,并且可表示为类型
int
的值 - 未类型的常量索引将赋予类型
int
- 如果
0 <= x < len(a)
,则索引x
在范围内,否则超出范围
对于数组类型 A
的 a
对于指针到数组类型的 a
a[x]
是(*a)[x]
的简写
对于切片类型 S
的 a
- 如果
x
在运行时超出范围,则会发生运行时恐慌 a[x]
是索引x
处的切片元素,a[x]
的类型是S
的元素类型
对于字符串类型 的 a
对于映射类型 M
的 a
x
的类型必须可赋值为M
的键类型- 如果映射包含键为
x
的条目,则a[x]
是键为x
的映射元素,a[x]
的类型是M
的元素类型 - 如果映射为
nil
或不包含此类条目,则a[x]
是M
的元素类型的零值
否则 a[x]
非法。
在赋值或特殊形式的初始化中使用的类型为 map[K]V
的映射 a
上的索引表达式
v, ok = a[x] v, ok := a[x] var v, ok = a[x]
生成一个额外的未类型布尔值。如果映射中存在键 x
,则 ok
的值为 true
,否则为 false
。
为 nil
映射的元素赋值会导致运行时恐慌。
切片表达式
切片表达式从字符串、数组、指向数组的指针或切片构造子字符串或切片。有两种变体:一种简单的形式指定低位和高位边界,另一种完整的形式还指定容量边界。
简单的切片表达式
对于字符串、数组、指向数组的指针或切片 a
,初级表达式
a[low : high]
构造子字符串或切片。索引 low
和 high
选择操作数 a
的哪些元素出现在结果中。结果的索引从 0 开始,长度等于 high
- low
。切片数组 a
后
a := [5]int{1, 2, 3, 4, 5} s := a[1:4]
切片 s
的类型为 []int
,长度为 3,容量为 4,元素为
s[0] == 2 s[1] == 3 s[2] == 4
为方便起见,可以省略任何索引。缺少的 low
索引默认为零;缺少的 high
索引默认为被切片操作数的长度
a[2:] // same as a[2 : len(a)] a[:3] // same as a[0 : 3] a[:] // same as a[0 : len(a)]
如果 a
是指向数组的指针,则 a[low : high]
是 (*a)[low : high]
的简写。
对于数组或字符串,如果 0
<= low
<= high
<= len(a)
,则索引在范围内,否则超出范围。对于切片,上限索引边界是切片容量 cap(a)
而不是长度。一个常量索引必须是非负的,并且可表示为类型 int
的值;对于数组或常量字符串,常量索引也必须在范围内。如果两个索引都是常量,则它们必须满足 low <= high
。如果索引在运行时超出范围,则会发生运行时恐慌。
除了未类型字符串之外,如果被切片的操作数是字符串或切片,则切片操作的结果是与操作数类型相同的非常量值。对于未类型字符串操作数,结果是类型为 string
的非常量值。如果被切片的操作数是数组,则它必须是可寻址的,切片操作的结果是与数组具有相同元素类型的切片。
如果有效切片表达式的被切片操作数是 nil
切片,则结果是 nil
切片。否则,如果结果是切片,则它与其操作数共享其底层数组。
var a [10]int s1 := a[3:7] // underlying array of s1 is array a; &s1[2] == &a[5] s2 := s1[1:4] // underlying array of s2 is underlying array of s1 which is array a; &s2[1] == &a[5] s2[1] = 42 // s2[1] == s1[2] == a[5] == 42; they all refer to the same underlying array element
完整的切片表达式
对于数组、指向数组的指针或切片 a
(但不是字符串),初级表达式
a[low : high : max]
构造一个相同类型的切片,其长度和元素与简单的切片表达式a[low : high]
相同。此外,它通过将其设置为max - low
来控制生成的切片的容量。只有第一个索引可以省略;默认为 0。在切片数组a
之后
a := [5]int{1, 2, 3, 4, 5} t := a[1:3:5]
切片t
的类型为[]int
,长度为 2,容量为 4,元素为
t[0] == 2 t[1] == 3
对于简单的切片表达式,如果a
是指向数组的指针,则a[low : high : max]
是(*a)[low : high : max]
的简写。如果被切片的运算数是数组,则它必须是可寻址的。
如果0 <= low <= high <= max <= cap(a)
,则索引为在范围内,否则为超出范围。常量索引必须是非负的,并且可表示为类型int
的值;对于数组,常量索引也必须在范围内。如果多个索引是常量,则存在的常量必须相对于彼此在范围内。如果索引在运行时超出范围,则会发生运行时恐慌。
类型断言
对于接口类型的表达式x
和类型T
,主表达式
x.(T)
断言x
不为nil
,并且存储在x
中的值为类型T
。x.(T)
表示法称为类型断言。
更准确地说,如果T
不是接口类型,则x.(T)
断言x
的动态类型与类型T
相同。在这种情况下,T
必须实现x
的(接口)类型;否则类型断言无效,因为x
不可能存储类型T
的值。如果T
是接口类型,则x.(T)
断言x
的动态类型实现了接口T
。
如果类型断言成立,则表达式的值为存储在x
中的值,其类型为T
。如果类型断言为假,则会发生运行时恐慌。换句话说,即使x
的动态类型仅在运行时已知,但在正确的程序中,x.(T)
的类型已知为T
。
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 interface{} = x.(T) // dynamic types of v and ok are T and bool
产生一个额外的未类型化的布尔值。如果断言成立,则ok
的值为true
。否则为false
,v
的值为类型T
的零值。在这种情况下不会发生运行时恐慌。
调用
给定函数类型F
的表达式f
,
f(a1, a2, … an)
使用参数a1, a2, … an
调用f
。除了一种特殊情况外,参数必须是可赋值到F
的参数类型的单值表达式,并在调用函数之前进行求值。表达式的类型是F
的结果类型。方法调用类似,但方法本身被指定为接收器类型值的选取器,用于该方法。
math.Atan2(x, y) // function call var pt *Point pt.Scale(3.5) // method call with receiver pt
在函数调用中,函数值和参数按照通常的顺序进行求值。在它们被求值后,调用的参数按值传递给函数,并开始执行被调用的函数。函数的返回值在函数返回时按值传递回调用方。
调用nil
函数值会导致运行时恐慌。
作为一种特殊情况,如果函数或方法g
的返回值在数量上相等,并且可以分别赋值给另一个函数或方法f
的参数,则调用f(g(parameters_of_g))
将在将g
的返回值按顺序绑定到f
的参数后调用f
。f
的调用必须不包含除g
的调用之外的参数,并且g
必须至少有一个返回值。如果f
具有最终的...
参数,则将g
的剩余返回值分配给它。
func Split(s string, pos int) (string, string) { return s[0:pos], s[pos:] } func Join(s, t string) string { return s + t } if Join(Split(value, len(value)/2)) != value { log.Panic("test fails") }
如果x
的(类型)的方法集包含m
并且参数列表可以分配给m
的参数列表,则方法调用x.m()
有效。如果x
是可寻址的并且&x
的方法集包含m
,则x.m()
是(&x).m()
的简写
var p Point p.Scale(3.5)
没有不同的方法类型,也没有方法字面量。
将参数传递给...
参数
如果f
是可变参数的,其最终参数p
的类型为...T
,则在f
中,p
的类型等效于类型[]T
。如果f
没有为p
调用任何实际参数,则传递给p
的值为nil
。否则,传递的值是一个新的类型为[]T
的切片,它有一个新的底层数组,其连续元素是实际参数,这些参数都必须可赋值到T
。因此,切片的长度和容量是绑定到p
的参数数量,并且在每个调用站点可能不同。
给定函数和调用
func Greeting(prefix string, who ...string) Greeting("nobody") Greeting("hello:", "Joe", "Anna", "Eileen")
在Greeting
中,who
在第一次调用中将具有值nil
,在第二次调用中将具有值[]string{"Joe", "Anna", "Eileen"}
。
如果最终参数可赋值到切片类型[]T
并且后跟...
,则将其不变地传递为...T
参数的值。在这种情况下,不会创建新的切片。
给定切片s
和调用
s := []string{"James", "Jasmine"} Greeting("goodbye:", s...)
在Greeting
中,who
将与s
具有相同的值,并具有相同的底层数组。
运算符
运算符将操作数组合成表达式。
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 = "+" | "-" | "!" | "^" | "*" | "&" | "<-" .
在其他地方讨论了比较。对于其他二元运算符,操作数类型必须相同,除非操作涉及移位或未类型化的常量。对于仅涉及常量的操作,请参阅有关常量表达式的部分。
除了移位运算外,如果一个操作数是未类型化的常量而另一个操作数不是,则该常量会隐式转换为另一个操作数的类型。
移位表达式中的右操作数必须具有整数类型或为可表示为类型uint
的值的未类型化常量。如果非常量移位表达式的左操作数是未类型化的常量,则首先将其隐式转换为如果将移位表达式替换为其左操作数本身,它将假设的类型。
var a [1024]byte var s uint = 33 // The results of the following examples are given for 64-bit ints. 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 == 1<<33 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 == false var p = 1<<s == 1<<33 // 1 has type int; p == true 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; w == 1<<33 var x = a[1.0<<s] // panics: 1.0 has type int, but 1<<33 overflows array bounds var b = make([]byte, 1.0<<s) // 1.0 has type int; len(b) == 1<<33 // The results of the following examples are given for 32-bit ints, // which means the shifts will overflow. var mm int = 1.0<<s // 1.0 has type int; mm == 0 var oo = 1<<s == 2<<s // 1 and 2 have type int; oo == true var pp = 1<<s == 1<<33 // illegal: 1 has type int, but 1<<33 overflows int var xx = a[1.0<<s] // 1.0 has type int; xx == a[0] var bb = make([]byte, 1.0<<s) // 1.0 has type int; len(bb) == 0
运算符优先级
一元运算符具有最高优先级。由于++
和--
运算符构成语句而不是表达式,因此它们不在运算符层次结构中。因此,语句*p++
与(*p)++
相同。
二元运算符有五个优先级级别。乘法运算符绑定最强,其次是加法运算符、比较运算符、&&
(逻辑与)和最后是||
(逻辑或)。
Precedence Operator 5 * / % << >> & &^ 4 + - | ^ 3 == != < <= > >= 2 && 1 ||
相同优先级的二元运算符从左到右结合。例如,x / y * z
与(x / y) * z
相同。
+x 23 + 3*x[i] x <= f() ^a >> b f() || g() x == y+1 && <-chanInt > 0
算术运算符
算术运算符应用于数值,并产生与第一个操作数类型相同的类型的结果。四个标准算术运算符(+
、-
、*
、/
)适用于整数、浮点数和复数类型;+
也适用于字符串。按位逻辑和移位运算符仅适用于整数。
+ 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 << integer >= 0 >> right shift integer >> integer >= 0
整数运算符
对于两个整数值x
和y
,整数商q = x / y
和余数r = x % y
满足以下关系
x = q*y + r 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
的int类型的最负值,则商q = x / -1
等于x
(并且r = 0
),这是由于二进制补码整数溢出造成的。
x, q int8 -128 int16 -32768 int32 -2147483648 int64 -9223372036854775808
如果除数是常量,则它不能为零。如果除数在运行时为零,则会发生运行时恐慌。如果被除数是非负数且除数是 2 的常数幂,则除法可以用右移替换,计算余数可以用按位与运算替换。
x x / 4 x % 4 x >> 2 x & 3 11 2 3 2 3 -11 -2 -3 -3 1
移位运算符将左操作数移位由右操作数指定的移位计数,该计数必须是非负的。如果移位计数在运行时为负,则会发生运行时恐慌。如果左操作数是有符号整数,则移位运算符实现算术移位,如果它是无符号整数,则实现逻辑移位。移位计数没有上限。移位行为就像左操作数被移位n
次 1,移位计数为n
。因此,x << 1
与x*2
相同,x >> 1
与x/2
相同,但向负无穷大截断。
对于整数操作数,一元运算符+
、-
和^
定义如下
+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
整数溢出
对于无符号整数值,运算+
、-
、*
和<<
按模 2n计算,其中n是无符号整数类型的位宽。宽松地说,这些无符号整数运算在溢出时丢弃高位,并且程序可以依赖“环绕”。
对于有符号整数,运算符+
、-
、*
、/
和<<
可能会合法地发生溢出,并且产生的结果值存在,并且由有符号整数表示、运算符及其操作数确定性地定义。溢出不会导致运行时恐慌。编译器不能在溢出不会发生的假设下优化代码。例如,它不能假设x < x + 1
始终为真。
浮点数运算符
对于浮点数和复数,+x
与x
相同,而-x
是x
的负数。浮点数或复数除以零的结果未在 IEEE 754 标准之外指定;是否发生运行时恐慌是实现特定的。
实现可以将多个浮点运算合并到单个融合运算中,可能跨语句,并产生与通过单独执行和舍入指令获得的值不同的结果。显式浮点类型转换会舍入到目标类型的精度,从而防止会丢弃该舍入的融合。
例如,某些体系结构提供“融合乘加”(FMA)指令,该指令计算x*y + z
,而不会舍入中间结果x*y
。这些示例显示了 Go 实现何时可以使用该指令
// 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
字符串连接
可以使用+
运算符或+=
赋值运算符连接字符串。
s := "hi" + string(c) s += " and good bye"
字符串加法通过连接操作数创建一个新的字符串。
比较运算符
比较运算符比较两个操作数并产生一个未类型化的布尔值。
== equal != not equal < less <= less or equal > greater >= greater or equal
在任何比较中,第一个操作数必须可赋值到第二个操作数的类型,反之亦然。
相等运算符==
和!=
适用于可比较的操作数。排序运算符<
、<=
、>
和>=
适用于有序的操作数。这些术语以及比较的结果定义如下
- 布尔值是可比较的。如果两个布尔值都为
true
或都为false
,则它们相等。 - 整数是可比较且有序的,以通常的方式。
- 浮点数是可比较且有序的,如 IEEE 754 标准所定义。
- 复数是可比较的。如果
real(u) == real(v)
且imag(u) == imag(v)
,则两个复数u
和v
相等。 - 字符串值是可比较且有序的,按字典顺序逐字节排序。
- 指针值是可比较的。如果两个指针值指向同一个变量或都具有值
nil
,则它们相等。指向不同的零大小变量的指针可能相等也可能不相等。 - 通道值是可比较的。如果两个通道值由对
make
的相同调用创建,或者都具有值nil
,则它们相等。 - 接口值是可比较的。如果两个接口值具有相同的动态类型和相等的动态值,或者都具有值
nil
,则它们相等。 - 当类型
X
的值可比较且X
实现了T
时,非接口类型X
的值x
和接口类型T
的值t
是可比较的。如果t
的动态类型与X
相同且t
的动态值等于x
,则它们相等。 - 如果所有字段都可比较,则结构体值是可比较的。如果两个结构体的对应非空白字段相等,则它们相等。
- 如果数组元素类型的值可比较,则数组值是可比较的。如果两个数组值的对应元素相等,则它们相等。
如果该类型的值不可比较,则比较两个具有相同动态类型的接口值会导致运行时恐慌。此行为不仅适用于直接的接口值比较,也适用于比较接口值数组或具有接口值字段的结构体时。
切片、映射和函数值不可比较。但是,作为特例,切片、映射或函数值可以与预声明的标识符nil
进行比较。也允许将指针、通道和接口值与nil
进行比较,并且遵循上述一般规则。
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 )
逻辑运算符
逻辑运算符应用于布尔值,并产生与操作数类型相同的类型结果。右操作数是根据条件进行计算的。
&& 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"
地址运算符
对于类型为T
的操作数x
,地址运算&x
生成指向x
的类型为*T
的指针。操作数必须是可寻址的,即变量、指针间接寻址或切片索引操作;或可寻址结构体操作数的字段选择器;或可寻址数组的数组索引操作。作为可寻址性要求的例外,x
也可以是(可能带括号的)复合字面量。如果x
的计算会导致运行时恐慌,则&x
的计算也会导致运行时恐慌。
对于类型为*T
的指针操作数x
,指针间接寻址*x
表示由x
指向的类型为T
的变量。如果x
为nil
,则尝试计算*x
将导致运行时恐慌。
&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
接收运算符
对于通道类型的操作数ch
,接收操作<-ch
的值是从通道ch
接收的值。通道方向必须允许接收操作,并且接收操作的类型是通道的元素类型。表达式阻塞,直到有值可用。从nil
通道接收将永远阻塞。对已关闭通道的接收操作始终可以立即进行,在接收所有先前发送的值后,产生元素类型的零值。
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
会产生一个额外的未类型化的布尔结果,报告通信是否成功。如果接收到的值是由对通道的成功发送操作传递的,则ok
的值为true
;如果它是由于通道已关闭且为空而生成的零值,则为false
。
转换
转换将表达式的类型更改为转换指定的类型。转换可能在源代码中以字面形式出现,也可能由表达式出现的上下文隐式指定。
显式转换是形式为T(x)
的表达式,其中T
是类型,x
是可以转换为类型T
的表达式。
Conversion = Type "(" Expression [ "," ] ")" .
如果类型以运算符*
或<-
开头,或者如果类型以关键字func
开头且没有结果列表,则在需要时必须将其括起来以避免歧义
*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
的值表示,则可以将其转换为类型T
。作为特例,可以使用与非常量x
相同的规则将整数常量x
显式转换为字符串类型。
转换常量会产生一个类型化的常量作为结果。
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
。 - 忽略结构体标签(见下文),
x
的类型和T
具有相同的底层类型。 - 忽略结构体标签(见下文),
x
的类型和T
都是指针类型,而不是已定义类型,并且它们的指针基类型具有相同的底层类型。 -
x
的类型和T
都是整数或浮点类型。 -
x
的类型和T
都是复数类型。 -
x
是整数或字节或字符的切片,而T
是字符串类型。 -
x
是字符串,而T
是字节或字符的切片。 -
x
是切片,T
是指向数组的指针,并且切片和数组类型具有相同的元素类型。
为了转换的目的,在比较结构体类型以确定其是否相同的时候,结构体标签会被忽略。
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
特定规则适用于数值类型之间或与字符串类型之间(非常量)的转换。这些转换可能会更改x
的表示形式并产生运行时成本。所有其他转换仅更改类型,而不更改x
的表示形式。
没有语言机制可以在指针和整数之间进行转换。包unsafe
在受限情况下实现了此功能。
数值类型之间的转换
对于非常量数值的转换,适用以下规则
- 在整数类型之间转换时,如果该值为有符号整数,则将其符号扩展到隐式无限精度;否则将其零扩展。然后将其截断以适合结果类型的尺寸。例如,如果
v := uint16(0x10F0)
,则uint32(int8(v)) == 0xFFFFFFF0
。转换始终产生有效值;没有溢出的指示。 - 将浮点数转换为整数时,将丢弃小数部分(向零截断)。
- 将整数或浮点数转换为浮点类型,或将复数转换为其他复数类型时,结果值将舍入到目标类型指定的精度。例如,类型为
float32
的变量x
的值可以使用超出 IEEE 754 32 位数字精度的额外精度存储,但float32(x)
表示将x
的值舍入到 32 位精度的结果。类似地,x + 0.1
可能使用超过 32 位的精度,但float32(x + 0.1)
不会。
在所有涉及浮点或复数值的非常量转换中,如果结果类型无法表示该值,则转换会成功,但结果值是实现相关的。
与字符串类型的转换
- 将有符号或无符号整数值转换为字符串类型会产生一个包含整数的 UTF-8 表示形式的字符串。超出有效 Unicode 代码点范围的值将转换为
"\uFFFD"
。string('a') // "a" string(-1) // "\ufffd" == "\xef\xbf\xbd" string(0xf8) // "\u00f8" == "ø" == "\xc3\xb8" type MyString string MyString(0x65e5) // "\u65e5" == "日" == "\xe6\x97\xa5"
- 将字节切片转换为字符串类型会产生一个字符串,其连续字节是切片的元素。
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ø"
- 将切片rune转换为字符串类型会生成一个字符串,该字符串是将各个rune值转换为字符串后的连接结果。
string([]rune{0x767d, 0x9d6c, 0x7fd4}) // "\u767d\u9d6c\u7fd4" == "白鵬翔" string([]rune{}) // "" string([]rune(nil)) // "" type MyRunes []rune string(MyRunes{0x767d, 0x9d6c, 0x7fd4}) // "\u767d\u9d6c\u7fd4" == "白鵬翔"
- 将字符串类型的值转换为字节切片类型会生成一个切片,其连续元素是字符串的字节。
[]byte("hellø") // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'} []byte("") // []byte{} MyBytes("hellø") // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}
- 将字符串类型的值转换为rune切片类型会生成一个切片,其中包含字符串的各个Unicode代码点。
[]rune(MyString("白鵬翔")) // []rune{0x767d, 0x9d6c, 0x7fd4} []rune("") // []rune{} MyRunes("白鵬翔") // []rune{0x767d, 0x9d6c, 0x7fd4}
从切片到数组指针的转换
将切片转换为数组指针会生成一个指向切片底层数组的指针。如果切片的长度小于数组的长度,则会发生运行时panic。
s := make([]byte, 2, 4) s0 := (*[0]byte)(s) // s0 != nil s1 := (*[1]byte)(s[1:]) // &s1[0] == &s[1] s2 := (*[2]byte)(s) // &s2[0] == &s[0] s4 := (*[4]byte)(s) // panics: len([4]byte) > len(s) var t []string t0 := (*[0]string)(t) // t0 == nil t1 := (*[1]string)(t) // panics: len([1]string) > len(t) u := make([]byte, 0) u0 := (*[0]byte)(u) // u0 != nil
常量表达式
常量表达式只能包含常量操作数,并在编译时进行求值。
未类型化的布尔、数字和字符串常量可用作操作数,只要在使用布尔、数字或字符串类型操作数的地方都是合法的。
常量比较始终生成未类型化的布尔常量。如果常量移位表达式的左操作数是未类型化的常量,则结果为整数常量;否则,结果为与左操作数类型相同的常量,该常量必须为整数类型。
对未类型化常量的任何其他运算都会产生相同种类的未类型化常量;即布尔、整数、浮点数、复数或字符串常量。如果二元运算(移位运算除外)的未类型化操作数种类不同,则结果为操作数中在此列表中出现较晚的种类:整数、rune、浮点数、复数。例如,未类型化整数常量除以未类型化复数常量会产生未类型化复数常量。
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
应用于未类型化的整数、rune或浮点数常量会产生未类型化的复数常量。
const ic = complex(0, c) // ic == 3.75i (untyped complex constant) const iΘ = complex(0, Θ) // iΘ == 1i (type complex128)
常量表达式始终精确求值;中间值和常量本身可能需要比语言中任何预声明类型支持的精度大得多的精度。以下为合法的声明
const Huge = 1 << 100 // Huge == 1267650600228229401496703205376 (untyped integer constant) const Four int8 = Huge >> 98 // Four == 4 (type int8)
常量除法或取余运算的除数不能为零
3.14 / 0.0 // illegal: division by zero
类型化常量的值必须始终能够被常量类型的值准确表示。以下常量表达式是非法的
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 // 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
实现限制:编译器在计算未类型化的浮点数或复数常量表达式时可能会使用舍入;请参阅常量部分中的实现限制。这种舍入可能会导致浮点数常量表达式在整数上下文中无效,即使在使用无限精度计算时它是整数,反之亦然。
求值顺序
在包级别,初始化依赖项确定变量声明中各个初始化表达式的求值顺序。否则,在求值表达式的操作数、赋值或返回语句时,所有函数调用、方法调用和通信操作都按词法从左到右的顺序求值。
例如,在(函数局部)赋值中
y[f()], ok = g(h(), i()+x[j()], <-c), k()
函数调用和通信按f()
、h()
、i()
、j()
、<-c
、g()
和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()
和g()
的顺序发生。
单个表达式内的浮点运算根据运算符的结合性进行求值。显式括号通过覆盖默认结合性来影响求值。在表达式x + (y + z)
中,加法y + z
在加x
之前执行。
语句
语句控制执行。
Statement = Declaration | LabeledStmt | SimpleStmt | GoStmt | ReturnStmt | BreakStmt | ContinueStmt | GotoStmt | FallthroughStmt | Block | IfStmt | SwitchStmt | SelectStmt | ForStmt | DeferStmt . SimpleStmt = EmptyStmt | ExpressionStmt | SendStmt | IncDecStmt | Assignment | ShortVarDecl .
终止语句
终止语句会中断块中正常的控制流。以下语句为终止语句
- "return"或"goto"语句。
- 对内置函数
panic
的调用。 - 语句列表以终止语句结尾的块。
- "if"语句,其中
- "else"分支存在,并且
- 两个分支都是终止语句。
- "for"语句,其中
- 没有引用"for"语句的"break"语句,并且
- 循环条件不存在,并且
- "for"语句未使用范围子句。
- "switch"语句,其中
- 没有引用"switch"语句的"break"语句,
- 存在默认情况,并且
- 每个情况(包括默认情况)中的语句列表以终止语句或可能带有标签的"fallthrough"语句结尾。
- "select"语句,其中
- 没有引用"select"语句的"break"语句,并且
- 每个情况(包括默认情况,如果存在)中的语句列表以终止语句结尾。
- 带标签的语句为终止语句添加标签。
所有其他语句都不是终止语句。
如果语句列表不为空且其最后一个非空语句为终止语句,则语句列表以终止语句结尾。
空语句
空语句不执行任何操作。
EmptyStmt = .
带标签的语句
带标签的语句可能是goto
、break
或continue
语句的目标。
LabeledStmt = Label ":" Statement . Label = identifier .
Error: log.Panic("error encountered")
表达式语句
除了特定的内置函数外,函数和方法调用以及接收操作可以出现在语句上下文中。此类语句可以加括号。
ExpressionStmt = Expression .
以下内置函数不允许在语句上下文中使用
append cap complex imag len make new real unsafe.Add unsafe.Alignof unsafe.Offsetof unsafe.Sizeof unsafe.Slice
h(x+y) f.Close() <-ch (<-ch) len("foo") // illegal if len is the built-in function
发送语句
发送语句在通道上发送值。通道表达式必须为通道类型,通道方向必须允许发送操作,并且要发送的值的类型必须可赋值给通道的元素类型。
SendStmt = Channel "<-" Expression . Channel = Expression .
通道和值表达式都在通信开始前进行求值。通信会阻塞,直到发送可以继续进行。如果接收方已准备就绪,则在未缓冲的通道上发送可以继续进行。如果缓冲区中有空间,则在缓冲的通道上发送可以继续进行。在已关闭的通道上发送会导致运行时panic。在nil
通道上发送会永远阻塞。
ch <- 3 // send value 3 to channel ch
IncDec语句
"++"和"--"语句会将其操作数分别递增或递减未类型化的常量1
。与赋值一样,操作数必须可寻址或为映射索引表达式。
IncDecStmt = Expression ( "++" | "--" ) .
以下赋值语句在语义上等效
IncDec statement Assignment x++ x += 1 x-- x -= 1
赋值
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
一次。op=
结构是一个单一标记。在赋值操作中,左侧和右侧表达式列表都必须恰好包含一个单值表达式,并且左侧表达式不能是空白标识符。
a[i] <<= 2 i &^= 1<<n
元组赋值将多值操作的各个元素赋值给变量列表。有两种形式。在第一种形式中,右侧操作数是单个多值表达式,例如函数调用、通道或映射操作,或类型断言。左侧操作数的数量必须与值的数目匹配。例如,如果f
是返回两个值的函数,
x, y = f()
将第一个值赋值给x
,第二个值赋值给y
。在第二种形式中,左侧操作数的数量必须等于右侧表达式的数量,每个表达式都必须是单值表达式,并且右侧的第n个表达式被赋值给左侧的第n个操作数
one, two, three = '一', '二', '三'
空白标识符提供了一种在赋值中忽略右侧值的方法
_ = x // evaluate x but ignore it x, _ = f() // evaluate f() but ignore second result value
赋值分两个阶段进行。首先,左侧的索引表达式和指针间接寻址(包括选择器中的隐式指针间接寻址)的操作数以及右侧的表达式都按通常顺序求值。其次,赋值按从左到右的顺序执行。
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
类型。
if语句
"If"语句根据布尔表达式的值指定两个分支的条件执行。如果表达式求值为true,则执行"if"分支,否则,如果存在,则执行"else"分支。
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 }
switch语句
"Switch"语句提供多路执行。将表达式或类型与"switch"内部的"case"进行比较,以确定要执行哪个分支。
SwitchStmt = ExprSwitchStmt | TypeSwitchStmt .
有两种形式:表达式switch和类型switch。在表达式switch中,case包含与switch表达式的值进行比较的表达式。在类型switch中,case包含与特殊注释的switch表达式的类型进行比较的类型。switch表达式的值在switch语句中恰好求值一次。
表达式switch
在表达式 switch 中,会先计算 switch 表达式,然后从左到右、从上到下依次计算 case 表达式(case 表达式不必是常量);第一个与 switch 表达式相等的 case 表达式会触发其关联的语句的执行;其他 case 会被跳过。如果没有任何 case 匹配并且存在 "default" case,则会执行其语句。最多只能有一个 default case,并且它可以出现在 "switch" 语句中的任何位置。缺少 switch 表达式等同于布尔值 true
。
ExprSwitchStmt = "switch" [ SimpleStmt ";" ] [ Expression ] "{" { ExprCaseClause } "}" . ExprCaseClause = ExprSwitchCase ":" StatementList . ExprSwitchCase = "case" ExpressionList | "default" .
如果 switch 表达式计算结果为无类型常量,则首先会隐式地将其转换为其默认类型。预声明的无类型值 nil
不能用作 switch 表达式。switch 表达式的类型必须是可比较的。
如果 case 表达式是无类型的,则首先会隐式地将其转换为 switch 表达式的类型。对于每个(可能已转换的)case 表达式 x
和 switch 表达式的值 t
,x == t
必须是一个有效的比较。
换句话说,switch 表达式被视为用于声明和初始化一个名为 t
的临时变量(没有显式类型);正是针对这个 t
的值来测试每个 case 表达式 x
是否相等。
在 case 或 default 子句中,最后一个非空语句可以是(可能是带标签的)"贯穿语句",以指示控制应该从该子句的末尾流向下一个子句的第一条语句。否则,控制流向 "switch" 语句的末尾。在表达式 switch 的所有但最后一个子句中,"贯穿语句" 可以作为最后一个语句出现。
switch 表达式前面可以是一个简单语句,该语句在计算表达式之前执行。
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 表达式计算结果为同一个常量。例如,当前的编译器禁止在 case 表达式中出现重复的整数、浮点数或字符串常量。
类型 switch
类型 switch 比较类型而不是值。除此之外,它与表达式 switch 类似。它由一个特殊的 switch 表达式标记,该表达式具有使用关键字 type
而不是实际类型的类型断言的形式。
switch x.(type) { // cases }
然后,case 会将实际类型 T
与表达式 x
的动态类型进行匹配。与类型断言一样,x
必须是接口类型,并且 case 中列出的每个非接口类型 T
必须实现 x
的类型。类型 switch 的 case 中列出的类型必须全部不同。
TypeSwitchStmt = "switch" [ SimpleStmt ";" ] TypeSwitchGuard "{" { TypeCaseClause } "}" . TypeSwitchGuard = [ identifier ":=" ] PrimaryExpr "." "(" "type" ")" . TypeCaseClause = TypeSwitchCase ":" StatementList . TypeSwitchCase = "case" TypeList | "default" . TypeList = Type { "," Type } .
TypeSwitchGuard 可以包含一个短变量声明。当使用此形式时,变量在每个子句的隐式块中 TypeSwitchCase 的末尾声明。在只有一个类型列出的 case 的子句中,变量具有该类型;否则,变量具有 TypeSwitchGuard 中表达式的类型。
case 可以使用预声明的标识符nil
代替类型;当 TypeSwitchGuard 中的表达式是 nil
接口值时,会选择该 case。最多只能有一个 nil
case。
给定一个类型为 interface{}
的表达式 x
,以下类型 switch
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{}) }
可以改写为
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") } }
类型 switch 保护语句前面可以是一个简单语句,该语句在计算保护语句之前执行。
"贯穿语句" 不允许在类型 switch 中使用。
For 语句
“for” 语句指定重复执行一个代码块。有三种形式:迭代可以由单个条件、"for" 子句或 "range" 子句控制。
ForStmt = "for" [ Condition | ForClause | RangeClause ] Block . Condition = Expression .
带有单个条件的 For 语句
在最简单的形式中,"for" 语句指定重复执行一个代码块,只要布尔条件计算结果为 true。每次迭代前都会计算条件。如果条件不存在,则等同于布尔值 true
。
for a < b { a *= 2 }
带有 for
子句的 For 语句
带有 ForClause 的 "for" 语句也受其条件控制,但此外它还可以指定一个 init 和一个 post 语句,例如赋值语句、增量或减量语句。init 语句可以是短变量声明,但 post 语句不能是。init 语句声明的变量在每次迭代中都会被重复使用。
ForClause = [ InitStmt ] ";" [ Condition ] ";" [ PostStmt ] . InitStmt = SimpleStmt . PostStmt = SimpleStmt .
for i := 0; i < 10; i++ { f(i) }
如果非空,则在第一次迭代计算条件之前执行一次 init 语句;在每次执行代码块后执行 post 语句(并且仅当执行了代码块时才执行)。ForClause 的任何元素都可以为空,但分号是必需的,除非只有一个条件。如果条件不存在,则等同于布尔值 true
。
for cond { S() } is the same as for ; cond ; { S() } for { S() } is the same as for true { S() }
带有 range
子句的 For 语句
带有 "range" 子句的 "for" 语句迭代数组、切片、字符串或映射的所有条目,或在通道上接收到的值。对于每个条目,如果存在,则将 迭代值 分配给相应的 迭代变量,然后执行代码块。
RangeClause = [ ExpressionList "=" | IdentifierList ":=" ] "range" Expression .
"range" 子句右侧的表达式称为 范围表达式,它可以是数组、指向数组的指针、切片、字符串、映射或允许接收操作的通道。与赋值一样,如果存在,则左侧的操作数必须是可寻址的或映射索引表达式;它们表示迭代变量。如果范围表达式是通道,则最多允许一个迭代变量,否则最多可以有两个。如果最后一个迭代变量是空白标识符,则范围子句等同于没有该标识符的相同子句。
范围表达式 x
在循环开始之前计算一次,但有一个例外:如果最多存在一个迭代变量并且 len(x)
是常量,则不会计算范围表达式。
左侧的函数调用在每次迭代中计算一次。对于每次迭代,如果相应的迭代变量存在,则按如下方式生成迭代值。
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
- 对于数组、指向数组的指针或切片值
a
,索引迭代值按升序生成,从元素索引 0 开始。如果最多存在一个迭代变量,则范围循环会从 0 生成到len(a)-1
的迭代值,并且不会索引到数组或切片本身。对于nil
切片,迭代次数为 0。 - 对于字符串值,"range" 子句会迭代字符串中从字节索引 0 开始的 Unicode 代码点。在连续的迭代中,索引值将是字符串中连续 UTF-8 编码代码点的第一个字节的索引,第二个值(类型为
rune
)将是相应代码点的值。如果迭代遇到无效的 UTF-8 序列,则第二个值将为0xFFFD
(Unicode 替换字符),并且下一次迭代将使字符串中的单个字节前进。 - 映射上的迭代顺序未指定,并且不能保证从一次迭代到下一次迭代相同。如果在迭代期间删除了尚未到达的映射条目,则不会生成相应的迭代值。如果在迭代期间创建了映射条目,则该条目可能会在迭代期间生成或可能被跳过。对于每个创建的条目以及从一次迭代到下一次迭代,选择可能会有所不同。如果映射为
nil
,则迭代次数为 0。 - 对于通道,生成的迭代值是在通道上发送的连续值,直到通道关闭。如果通道为
nil
,则范围表达式将永远阻塞。
迭代值分配给相应的迭代变量,就像在赋值语句中一样。
迭代变量可以使用短变量声明(:=
)的形式由 "range" 子句声明。在这种情况下,它们的类型设置为相应迭代值的类型,并且它们的作用域是 "for" 语句的代码块;它们在每次迭代中都会被重复使用。如果迭代变量在 "for" 语句之外声明,则执行后,它们的值将是最后一次迭代的值。
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 {}
Go 语句
"go" 语句在同一个地址空间内启动函数调用的执行作为一个独立的并发控制线程,或称为 goroutine。
GoStmt = "go" Expression .
表达式必须是函数或方法调用;它不能加括号。内置函数的调用与表达式语句的限制相同。
函数值和参数在调用 goroutine 中按常规方式计算,但与常规调用不同的是,程序执行不会等待被调用函数完成。相反,函数会在新的 goroutine 中独立开始执行。当函数终止时,其 goroutine 也会终止。如果函数有任何返回值,则在函数完成时会丢弃这些返回值。
go Server() go func(ch chan<- bool) { for { sleep(10); ch <- true }} (c)
Select 语句
"select" 语句选择一组可能的发送或接收操作中的哪一个将继续进行。它看起来类似于"switch" 语句,但所有 case 都引用通信操作。
SelectStmt = "select" "{" { CommClause } "}" . CommClause = CommCase ":" StatementList . CommCase = "case" ( SendStmt | RecvStmt ) | "default" . RecvStmt = [ ExpressionList "=" | IdentifierList ":=" ] RecvExpr . RecvExpr = Expression .
带有 RecvStmt 的 case 可以将 RecvExpr 的结果分配给一个或两个变量,这些变量可以使用短变量声明声明。RecvExpr 必须是(可能加括号的)接收操作。最多只能有一个 default case,并且它可以出现在 case 列表中的任何位置。
"select" 语句的执行分几个步骤进行
- 对于语句中的所有情况,接收操作的通道操作数以及发送语句的通道和右侧表达式在进入“select”语句时恰好评估一次,并且按照源代码顺序进行。结果是一组要从中接收或发送到的通道,以及要发送的对应值。无论选择哪个(如果有)通信操作继续进行,该评估中的任何副作用都将发生。带有短变量声明或赋值的 RecvStmt 左侧的表达式尚未评估。
- 如果一个或多个通信可以继续进行,则通过统一的伪随机选择选择一个可以继续进行的通信。否则,如果存在默认情况,则选择该情况。如果不存在默认情况,则“select”语句将阻塞,直到至少有一个通信可以继续进行。
- 除非所选情况是默认情况,否则将执行相应的通信操作。
- 如果所选情况是带有短变量声明或赋值的 RecvStmt,则评估左侧表达式并将接收到的值(或值)赋值。
- 执行所选情况的语句列表。
由于在nil
通道上进行通信永远不会继续进行,因此仅包含nil
通道且没有默认情况的 select 将永远阻塞。
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
返回语句
函数F
中的“return”语句终止F
的执行,并可选地提供一个或多个结果值。F
延迟的任何函数deferred将在F
返回到其调用方之前执行。
ReturnStmt = "return" [ ExpressionList ] .
在没有结果类型的函数中,“return”语句不得指定任何结果值。
func noResult() { return }
有三种方法可以从具有结果类型的函数中返回值
- 返回值可以在“return”语句中显式列出。每个表达式必须是单值的,并且assignable到函数结果类型的相应元素。
func simpleF() int { return 2 } func complexF1() (re float64, im float64) { return -7.0, -4.0 }
- “return”语句中的表达式列表可以是对此多值函数的单个调用。其效果就好像从该函数返回的每个值都被分配给一个具有相应值类型的临时变量,然后是一个列出这些变量的“return”语句,此时应用前一个情况的规则。
func complexF2() (re float64, im float64) { return complexF1() }
- 如果函数的结果类型为其result parameters指定了名称,则表达式列表可以为空。结果参数充当普通的局部变量,函数可以根据需要为它们赋值。“return”语句返回这些变量的值。
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 }
无论它们如何声明,所有结果值在进入函数时都初始化为其类型的zero values。“return”语句在执行任何延迟函数之前设置结果参数。
实现限制:如果与结果参数同名的不同实体(常量、类型或变量)在“return”语句所在位置的scope中,编译器可能会禁止使用空的表达式列表。
func f(n int) (res int, err error) { if _, err := f(n-1); err != nil { return // invalid return statement: err is shadowed } return }
break语句
“break”语句终止同一函数内最内层"for"、"switch"或"select"语句的执行。
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 } } }
continue语句
“continue”语句在其后置语句处开始最内层"for"循环的下一个迭代。“for”循环必须在同一函数内。
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) } }
goto语句
“goto”语句将控制权转移到同一函数内具有相应标签的语句。
GotoStmt = "goto" Label .
goto Error
执行“goto”语句不得导致任何变量进入scope,而这些变量在 goto 点之前不在作用域内。例如,此示例
goto L // BAD v := 3 L:
是错误的,因为跳转到标签L
跳过了v
的创建。
位于block外部的“goto”语句不能跳转到该块内部的标签。例如,此示例
if n%2 == 1 { goto L1 } for n > 0 { f() n-- L1: f() n-- }
是错误的,因为标签L1
在“for”语句的块内,但goto
不在。
fallthrough语句
“fallthrough”语句将控制权转移到表达式“switch”语句中下一个case子句的第一个语句。它只能用作此类子句中最后一个非空语句。
FallthroughStmt = "fallthrough" .
defer语句
“defer”语句调用一个函数,该函数的执行被延迟到周围函数返回的那一刻,要么是因为周围函数执行了return语句,到达了其function body的末尾,要么是因为相应的goroutine正在panicking。
DeferStmt = "defer" Expression .
表达式必须是函数或方法调用;它不能加括号。内置函数的调用与表达式语句的限制相同。
每次“defer”语句执行时,对调用的函数值和参数都会evaluated as usual并重新保存,但不会实际调用该函数。相反,延迟函数在周围函数返回之前立即调用,并且按照延迟的相反顺序调用。也就是说,如果周围函数通过显式return语句返回,则延迟函数在该return语句设置任何结果参数之后但在函数返回到其调用方之前执行。如果延迟函数值计算结果为nil
,则在调用该函数时(而不是在执行“defer”语句时)执行panics。
例如,如果延迟函数是function literal,并且周围函数具有named result parameters,这些参数在文字内处于作用域内,则延迟函数可以在返回这些结果参数之前访问和修改它们。如果延迟函数有任何返回值,则在函数完成时将丢弃这些返回值。(另请参阅有关handling panics的部分)。
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 }
内置函数
内置函数是predeclared。它们像任何其他函数一样被调用,但其中一些函数接受类型而不是表达式作为第一个参数。
内置函数没有标准的 Go 类型,因此它们只能出现在call expressions中;它们不能用作函数值。
关闭
对于通道c
,内置函数close(c)
记录不再向通道发送任何值。如果c
是只接收通道,则这是一个错误。向已关闭的通道发送或关闭已关闭的通道会导致run-time panic。关闭nil
通道也会导致run-time panic。在调用close
之后,以及在所有先前发送的值都被接收之后,接收操作将返回通道类型的零值,而不会阻塞。多值receive operation将返回接收到的值以及通道是否已关闭的指示。
长度和容量
内置函数len
和cap
接受各种类型的参数并返回类型为int
的结果。实现保证结果始终适合int
。
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
切片的容量是在底层数组中分配空间的元素数。在任何时候,以下关系都成立
0 <= len(s) <= cap(s)
nil
切片、映射或通道的长度为 0。nil
切片或通道的容量为 0。
如果s
是字符串常量,则表达式len(s)
是constant。如果s
的类型是数组或指向数组的指针,并且表达式s
不包含channel receives或(非常量)function calls,则表达式len(s)
和cap(s)
是常量;在这种情况下,不会评估s
。否则,len
和cap
的调用不是常量,并且会评估s
。
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
分配
内置函数new
接受类型T
,在运行时为该类型的variable分配存储空间,并返回类型为*T
pointing的值,指向它。变量的初始化方式如“初始值”部分所述。
new(T)
例如
type S struct { a int; b float64 } new(S)
为类型S
的变量分配存储空间,对其进行初始化(a=0
、b=0.0
),并返回一个包含该位置地址的类型为*S
的值。
创建切片、映射和通道
内置函数make
接受类型T
,该类型必须是切片、映射或通道类型,后面可以选择跟类型特定的表达式列表。它返回类型为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
必须是整数类型或未类型化的constant。常量大小参数必须是非负数,并且representable为类型int
的值;如果它是未类型化的常量,则将其赋予类型int
。如果同时提供了n
和m
并且它们是常量,则n
必须不大于m
。如果在运行时n
为负数或大于m
,则会发生run-time panic。
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
个映射元素。精确的行为取决于实现。
追加和复制切片
内置函数append
和copy
有助于常见的切片操作。对于这两个函数,结果与参数引用的内存是否重叠无关。
函数 `append` 会将零个或多个值 `x` 追加到类型为 `S` 的 `s` 中,其中 `S` 必须是切片类型,并返回结果切片,结果切片的类型也是 `S`。值 `x` 传递给类型为 `...T` 的参数,其中 `T` 是 `S` 的 元素类型,并遵循相应的 参数传递规则。作为特殊情况,`append` 还接受一个可赋值为 `[]byte` 类型的第一个参数,以及一个字符串类型的第二个参数,后面跟着 `...`。这种形式会追加字符串的字节。
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` 还接受一个可赋值为 `[]byte` 类型的目标参数,以及一个字符串类型的源参数。这种形式会将字符串中的字节复制到字节切片中。
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")
删除映射元素
内置函数 `delete` 从 映射 `m` 中删除键为 `k` 的元素。`k` 的类型必须可 赋值 给 `m` 的键类型。
delete(m, k) // remove element m[k] from map m
如果映射 `m` 为 `nil` 或元素 `m[k]` 不存在,则 `delete` 为无操作。
操作复数
三个函数用于组装和分解复数。内置函数 `complex` 从浮点实部和虚部构造一个复数值,而 `real` 和 `imag` 则提取复数值的实部和虚部。
complex(realPart, imaginaryPart floatT) complexT real(complexT) floatT imag(complexT) floatT
参数和返回值的类型相对应。对于 `complex`,两个参数必须是相同的浮点类型,返回值是具有相应浮点成分的复数类型:对于 `float32` 参数,返回值为 `complex64`;对于 `float64` 参数,返回值为 `complex128`。如果其中一个参数求值为无类型常量,则首先将其隐式 转换为另一个参数的类型。如果两个参数都求值为无类型常量,则它们必须是非复数,或其虚部必须为零,并且函数的返回值为无类型复数常量。
对于 `real` 和 `imag`,参数必须是复数类型,返回值是相应的浮点类型:对于 `complex64` 参数,返回值为 `float32`;对于 `complex128` 参数,返回值为 `float64`。如果参数求值为无类型常量,则它必须是数字,并且函数的返回值为无类型浮点常量。
`real` 和 `imag` 函数共同构成 `complex` 的逆运算,因此对于复数类型 `Z` 的值 `z`,`z == Z(complex(real(z), imag(z)))`。
如果这些函数的操作数都是常量,则返回值为常量。
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 int = complex(1, 0) // untyped complex constant 1 + 0i can be converted to int _ = 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
处理恐慌
两个内置函数 `panic` 和 `recover` 用于报告和处理 运行时恐慌和程序定义的错误条件。
func panic(interface{}) func recover() interface{}
在执行函数 `F` 期间,显式调用 `panic` 或 运行时恐慌 会终止 `F` 的执行。然后,`F` 中 延迟 的任何函数将照常执行。接下来,`F` 的调用者运行的任何延迟函数都会运行,依此类推,直到执行 goroutine 中顶层函数的任何延迟函数。此时,程序将终止并报告错误条件,包括传递给 `panic` 的参数的值。此终止序列称为恐慌。
panic(42) panic("unreachable") panic(Error("cannot parse"))
函数 `recover` 允许程序管理恐慌 goroutine 的行为。假设函数 `G` 延迟了一个函数 `D`,该函数调用 `recover`,并且在 `G` 执行的相同 goroutine 中的某个函数中发生了恐慌。当延迟函数的运行到达 `D` 时,`D` 对 `recover` 的调用的返回值将是传递给 `panic` 调用的值。如果 `D` 正常返回,且没有启动新的恐慌,则恐慌序列停止。在这种情况下,`G` 和对 `panic` 的调用之间调用的函数的状态将被丢弃,并恢复正常执行。然后运行 `G` 在 `D` 之前延迟的任何函数,`G` 的执行通过返回到其调用者而终止。
如果满足以下任何条件,则 `recover` 的返回值为 `nil`
- `panic` 的参数为 `nil`;
- goroutine 未处于恐慌状态;
- `recover` 不是由延迟函数直接调用的。
下面的示例中的 `protect` 函数调用函数参数 `g`,并保护调用者免受 `g` 引发的运行时恐慌的影响。
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() }
引导
当前实现提供了一些在引导期间有用的内置函数。出于完整性考虑,这些函数有文档记录,但不能保证保留在语言中。它们不返回结果。
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` 不需要接受任意参数类型,但必须支持布尔值、数字和字符串 类型 的打印。
软件包
Go 程序是通过将包链接在一起构建的。一个包又由一个或多个源文件构成,这些源文件共同声明属于该包的常量、类型、变量和函数,这些函数在同一包的所有文件中都可以访问。这些元素可以 导出并在另一个包中使用。
源文件组织
每个源文件都由一个包声明组成,该声明定义它所属的包,后面可能跟着一组空的导入声明,这些声明声明它希望使用的包的内容,后面可能跟着一组空的函数、类型、变量和常量的声明。
SourceFile = PackageClause ";" { ImportDecl ";" } { TopLevelDecl ";" } .
包声明
包声明以每个源文件开头,并定义该文件所属的包。
PackageClause = "package" PackageName . PackageName = identifier .
PackageName 必须不是 空白标识符。
package math
共享相同 PackageName 的一组文件构成包的实现。实现可能要求包的所有源文件都位于同一个目录中。
导入声明
导入声明指出包含该声明的源文件依赖于导入包的功能(§程序初始化和执行),并允许访问该包的 导出标识符。导入指定一个标识符(PackageName)用于访问,以及一个 ImportPath,用于指定要导入的包。
ImportDecl = "import" ( ImportSpec | "(" { ImportSpec ";" } ")" ) . ImportSpec = [ "." | PackageName ] ImportPath . ImportPath = string_lit .
PackageName 用于 限定标识符,以在导入源文件中访问包的导出标识符。它在 文件块中声明。如果省略了 PackageName,则默认为导入包的 包声明中指定的标识符。如果出现显式句点(`.`),而不是名称,则该包的 包块中声明的所有包的导出标识符都将在导入源文件的文件块中声明,并且必须在没有限定符的情况下访问。
ImportPath 的解释是实现相关的,但它通常是编译包的完整文件名的一部分,并且可以相对于已安装包的存储库。
实现限制:编译器可以将 ImportPath 限制为仅使用属于 Unicode 的 L、M、N、P 和 S 通用类别(不带空格的图形字符)的字符的非空字符串,并且还可以排除字符 `!"#$%&'()*,:;<=>?[\]^`{|}` 和 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
导入声明声明导入包和导入包之间的依赖关系。对于一个包,直接或间接地导入自身或在不引用其任何导出标识符的情况下直接导入一个包都是非法的。要仅为其副作用(初始化)导入一个包,请使用 空白标识符作为显式包名
import _ "lib/math"
一个示例包
这是一个完整的 Go 包,它实现了并发素数筛。
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() }
程序初始化和执行
零值
当为 变量分配存储空间时,无论是通过声明还是调用 `new`,或者当创建新值时,无论是通过复合字面量还是调用 `make`,并且没有提供显式初始化,则变量或值将被赋予默认值。此类变量或值的每个元素都设置为其类型的零值:布尔值对应 `false`,数值类型对应 `0`,字符串对应 `""`,指针、函数、接口、切片、通道和映射对应 `nil`。此初始化是递归完成的,因此例如,如果未指定值,则结构体数组的每个元素的字段都将被清零。
这两个简单的声明是等价的
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
包初始化
在一个包中,包级变量初始化按步骤进行,每一步都选择声明顺序中最先的、没有依赖于未初始化变量的变量。
更准确地说,如果一个包级变量尚未初始化,并且要么没有 初始化表达式,要么其初始化表达式不依赖于未初始化变量,则认为它已准备好进行初始化。初始化过程通过重复初始化声明顺序中最先且已准备好进行初始化的下一个包级变量来进行,直到没有变量已准备好进行初始化为止。
如果此过程结束时仍有一些变量未初始化,则这些变量是多个初始化循环的一部分,程序无效。
变量声明左侧的多个变量由右侧的单个(多值)表达式初始化,它们同时初始化:如果左侧的任何变量已初始化,则所有这些变量都在同一步骤中初始化。
var x = a var a, b = f() // a and b are initialized together, before x is initialized
出于包初始化的目的,空白变量在声明中与任何其他变量一样对待。
在多个文件中声明的变量的声明顺序由这些文件提供给编译器的顺序决定:第一个文件中声明的变量在第二个文件中声明的任何变量之前声明,依此类推。
依赖分析不依赖于变量的实际值,只依赖于源代码中对它们的词法引用,并进行传递性分析。例如,如果变量x
的初始化表达式引用了一个其主体引用变量y
的函数,则x
依赖于y
。具体来说
- 对变量或函数的引用是表示该变量或函数的标识符。
- 对方法
m
的引用是形式为t.m
的方法值或方法表达式,其中t
的(静态)类型不是接口类型,并且方法m
位于t
的方法集中。结果函数值t.m
是否被调用无关紧要。 - 如果变量、函数或方法
x
的初始化表达式或主体(对于函数和方法)包含对y
或对依赖于y
的函数或方法的引用,则x
依赖于变量y
。
例如,给定以下声明
var ( a = c + b // == 9 b = f() // == 4 c = f() // == 5 d = 3 // == 5 after initialization has finished ) func f() int { d++ return d }
初始化顺序为d
、b
、c
、a
。请注意,初始化表达式中子表达式的顺序无关紧要:在本例中,a = c + b
和a = b + c
导致相同的初始化顺序。
依赖分析按包执行;只考虑引用当前包中声明的变量、函数和(非接口)方法的引用。如果变量之间存在其他隐藏的数据依赖关系,则这些变量之间的初始化顺序未指定。
例如,给定以下声明
var x = I(T{}).ab() // x has an undetected, hidden dependency on a and b var _ = sideEffect() // unrelated to x, a, or b var a = b var b = 42 type I interface { ab() []int } type T struct{} func (T) ab() []int { return []int{a, b} }
变量a
将在b
之后初始化,但x
是在b
之前、b
和a
之间还是在a
之后初始化,以及因此sideEffect()
调用的时间点(在x
初始化之前还是之后)都未指定。
变量也可以使用在包块中声明的名称为init
的函数初始化,这些函数没有参数也没有结果参数。
func init() { … }
每个包可以定义多个这样的函数,即使在一个源文件中也是如此。在包块中,init
标识符只能用于声明init
函数,但标识符本身没有声明。因此,无法从程序中的任何位置引用init
函数。
没有导入的包通过为其所有包级变量赋值初始值,然后按其在源代码中出现的顺序(可能在多个文件中,如提供给编译器的)调用所有init
函数来初始化。如果一个包有导入,则在初始化该包本身之前初始化导入的包。如果多个包导入一个包,则导入的包将只初始化一次。根据构造,包的导入保证不会存在循环初始化依赖关系。
包初始化——变量初始化和init
函数的调用——在一个 goroutine 中顺序发生,一次一个包。init
函数可以启动其他 goroutine,这些 goroutine 可以与初始化代码并发运行。但是,初始化始终对init
函数进行排序:在前面的函数返回之前,它不会调用下一个函数。
为了确保可重复的初始化行为,鼓励构建系统按词法文件名顺序向编译器提供属于同一包的多个文件。
程序执行
完整的程序是通过将一个名为主包的单个未导入的包与其所有导入的包(传递性)链接起来创建的。主包必须具有包名称main
并声明一个函数main
,该函数不带任何参数也不返回值。
func main() { … }
程序执行从初始化主包然后调用函数main
开始。当该函数调用返回时,程序退出。它不会等待其他(非main
)goroutine 完成。
错误
预声明类型error
定义为
type error interface { Error() string }
它是表示错误条件的传统接口,其中nil值表示没有错误。例如,从文件读取数据的函数可能定义为
func Read(f *File, b []byte) (n int, err error)
运行时恐慌
执行错误(例如尝试索引数组越界)会触发一个运行时恐慌,相当于使用实现定义的接口类型runtime.Error
的值调用内置函数panic
。该类型满足预声明的接口类型error
。表示不同运行时错误条件的确切错误值未指定。
package runtime type Error interface { error // and perhaps other methods }
系统注意事项
包unsafe
内置包unsafe
(编译器已知,可通过导入路径"unsafe"
访问)提供了用于低级编程的工具,包括违反类型系统的操作。使用unsafe
的包必须手动检查类型安全,并且可能不可移植。该包提供以下接口
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 type IntegerType int // shorthand for an integer type; it is not a real type func Add(ptr Pointer, len IntegerType) Pointer func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType
Pointer
是一种指针类型,但Pointer
值不能解引用。任何指针或底层类型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
,表示s
或*s
表示的结构体的字段f
,并返回相对于结构体地址的字段偏移量(以字节为单位)。如果f
是嵌入字段,则必须能够通过结构体的字段在没有指针间接寻址的情况下访问它。对于具有字段f
的结构体s
uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.f) == uintptr(unsafe.Pointer(&s.f))
计算机架构可能要求内存地址对齐;也就是说,变量的地址必须是某个因子的倍数,该因子是变量类型对齐方式。函数Alignof
接受表示任何类型变量的表达式,并返回变量(类型)的对齐方式(以字节为单位)。对于变量x
uintptr(unsafe.Pointer(&x)) % unsafe.Alignof(x) == 0
对Alignof
、Offsetof
和Sizeof
的调用是类型为uintptr
的编译时常量表达式。
函数Add
将len
添加到ptr
并返回更新后的指针unsafe.Pointer(uintptr(ptr) + uintptr(len))
。len
参数必须是整数类型或未类型化的常量。常量len
参数必须可以通过类型为int
的值表示;如果它是未类型化的常量,则将其类型设置为int
。有效使用Pointer
的规则仍然适用。
函数Slice
返回一个切片,其底层数组从ptr
开始,其长度和容量为len
。Slice(ptr, len)
等效于
(*[len]ArbitraryType)(unsafe.Pointer(ptr))[:]
但作为特例,如果ptr
为nil
且len
为零,则Slice
返回nil
。
len
参数必须是整数类型或未类型化的常量。常量len
参数必须是非负数,并且可以通过类型为int
的值表示;如果它是未类型化的常量,则将其类型设置为int
。在运行时,如果len
为负数,或者如果ptr
为nil
且len
不为零,则会发生运行时恐慌。
大小和对齐保证
对于数值类型,保证以下大小
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)
是所有值unsafe.Alignof(x.f)
(对于x
的每个字段f
)中最大的值,但至少为1。 - 对于数组类型变量
x
:unsafe.Alignof(x)
与数组元素类型的变量的对齐方式相同。
如果结构体或数组类型不包含大小大于零的字段(或元素),则其大小为零。两个不同的零大小变量在内存中可能具有相同的地址。