Go 编程语言规范
语言版本 go1.17(2021 年 10 月 15 日)
引言
这是 Go 编程语言版本 1.17(2021 年 10 月,在泛型引入之前)的参考手册。提供此版本仅供历史参考。最新的参考手册可在此处找到。更多信息和其它文档,请参阅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)
小写产生式名称用于标识词法记号(token)。非终结符使用驼峰命名法(CamelCase)。词法记号用双引号 ""
或反引号 ``
括起。
形式 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或字符串字面值内部开始,也不能在注释内部开始。不包含换行符的通用注释作用如同一个空格。任何其他注释作用如同一个换行符。
记号(Token)
记号构成了 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
虚数字面值
虚数字面值表示复数常量的虚部。它由一个整型或浮点型字面值后跟小写字母 i
组成。虚数字面值的值是相应整型或浮点型字面值的值乘以虚数单位 i。
imaginary_lit = (decimal_digits | int_lit | float_lit) "i" .
为了向后兼容,虚数字面值的整数部分(完全由十进制数字组成,可能包含下划线)被视为十进制整数,即使它以开头的 0
开始也是如此。
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
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
。
几种反斜杠转义允许将任意值编码为 ASCII 文本。有四种方式将整数值表示为数字常量:\x
后面紧跟两个十六进制数字;\u
后面紧跟四个十六进制数字;\U
后面紧跟八个十六进制数字,以及一个普通的反斜杠 \
后面紧跟三个八进制数字。在每种情况下,字面值的值是对应基数中数字表示的值。
尽管这些表示都产生一个整数,但它们的有效范围不同。八进制转义必须表示介于 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)和两位十六进制(\xnn)转义表示结果字符串的单个 字节;所有其他转义表示单个 字符 的(可能多字节)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 负零、无穷大和非数值(NaN)的常量。
常量可以是有类型的或无类型的。字面值常量、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
的容量。
对于给定的元素类型 T
,可以使用内置函数make
创建一个新的、已初始化的切片值,该函数接受一个切片类型以及指定长度和可选容量的参数。使用 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
被称为提升的,如果 x.f
是一个合法的选择器,用于指代该字段或方法 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
来代替方法规范。这称为将接口 E
嵌入到 T
中。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 .
键类型的操作数必须完全定义比较运算符 ==
和 !=
;因此键类型不能是函数、映射或切片类型。如果键类型是接口类型,则必须为动态键值定义这些比较运算符;否则将导致运行时 panic。
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
赋值或评估会引发运行时 panic。 - 如果
x
是接口类型且值为nil
,则调用或评估方法x.f
会引发运行时 panic。
例如,给定以下声明:
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
在运行时超出范围,会发生 运行时 panic。 a[x]
是索引x
处的切片元素,并且a[x]
的类型是S
的元素类型。
对于类型为 字符串类型 的 a
- 如果字符串
a
也是常量,则一个 常量索引必须在范围内。 - 如果
x
在运行时超出范围,会发生 运行时 panic。 a[x]
是索引x
处的非常量字节值,并且a[x]
的类型是byte
。a[x]
不可被赋值。
对于类型为 map 类型 M
的 a
x
的类型必须 可赋值给M
的键类型。- 如果 map 包含键为
x
的条目,则a[x]
是键为x
的 map 元素,并且a[x]
的类型是M
的元素类型。 - 如果 map 是
nil
或者不包含这样的条目,则a[x]
是M
的元素类型的 零值。
否则,a[x]
是非法的。
在类型为 map[K]V
的 map a
上的索引表达式,用于 赋值或特殊形式的初始化时
v, ok = a[x] v, ok := a[x] var v, ok = a[x]
会产生一个额外的无类型布尔值。如果 map 中存在键 x
,则 ok
的值为 true
,否则为 false
。
向一个 nil
map 的元素赋值会导致 运行时 panic。
切片表达式
切片表达式从字符串、数组、指向数组的指针或切片构造子字符串或切片。它们有两种形式:简单形式指定一个低位和高位边界,完整形式还指定一个容量边界。
简单切片表达式
对于字符串、数组、指向数组的指针或切片 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
。如果索引在运行时超出范围,会发生 运行时 panic。
除了 无类型字符串 外,如果被切片的操作数是字符串或切片,切片操作的结果是与操作数相同类型的非常量值。对于无类型字符串操作数,结果是类型为 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
的值 表示;对于数组,常量索引也必须在范围内。如果多个索引是常量,则存在的常量必须彼此相对在范围内。如果索引在运行时超出范围,会发生 运行时 panic。
类型断言
对于 接口类型的表达式 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
。如果类型断言失败,会发生 运行时 panic。换句话说,即使 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
的 零值。在这种情况下不会发生 运行时 panic。
调用
给定类型为 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
函数值会导致 运行时 panic。
作为特殊情况,如果函数或方法 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()
有效,如果 (x
类型的) 方法集 包含 m
并且参数列表可以 赋值给 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
如果除数是 常量,则不能为零。如果除数在运行时为零,会发生 运行时 panic。如果被除数是非负的,并且除数是 2 的常量幂,则除法可以用右移代替,计算余数可以用按位与操作代替。
x x / 4 x % 4 x >> 2 x & 3 11 2 3 2 3 -11 -2 -3 -3 1
位移运算符将左操作数按照右操作数指定的位移计数进行位移,右操作数必须是非负的。如果位移计数在运行时为负,会发生 运行时 panic。如果左操作数是有符号整数,位移运算符执行算术位移;如果左操作数是无符号整数,则执行逻辑位移。位移计数没有上限。位移的行为就好像对于位移计数为 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 是 无符号整数 类型的位宽。粗略地说,这些无符号整数操作在溢出时丢弃高位,程序可以依赖“回绕(wrap around)”行为。
对于有符号整数,运算 +
、-
、*
、/
和 <<
可以合法溢出,并且产生的值存在,并由有符号整数的表示、运算及其操作数确定性地定义。溢出不会导致 运行时 panic。编译器不得在假定不会发生溢出的前提下优化代码。例如,它不能假定 x < x + 1
总是为真。
浮点运算符
对于浮点数和复数,+x
与 x
相同,而 -x
是 x
的负数。浮点数或复数除以零的结果在 IEEE 754 标准之外未指定;是否发生 运行时 panic 取决于实现。
实现可以将多个浮点操作组合成一个融合操作,可能跨越多个语句,并产生与单独执行和舍入指令所获得的值不同的结果。显式的浮点类型 转换会四舍五入到目标类型的精度,从而阻止了会丢弃该舍入的融合。
例如,一些架构提供了“乘加融合”(FMA) 指令,可以在不舍入中间结果 x*y
的情况下计算 x*y + z
。这些示例展示了 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 标准所定义的那样。
- 复数值是可比较的。两个复数值
u
和v
相等,如果real(u) == real(v)
并且imag(u) == imag(v)
。 - 字符串值是可比较和可排序的,按照字节进行词典顺序比较。
- 指针值是可比较的。两个指针值相等,如果它们指向同一个变量或它们的值都是
nil
。指向不同的 零大小 变量的指针可能相等,也可能不相等。 - 通道值是可比较的。两个通道值相等,如果它们是通过同一个
make
调用创建的,或者它们的值都是nil
。 - 接口值是可比较的。两个接口值相等,如果它们具有 相同 的动态类型和相等的动态值,或者它们的值都是
nil
。 - 非接口类型
X
的值x
和接口类型T
的值t
可比较,当类型X
的值可比较并且X
实现了T
时。如果t
的动态类型与X
相同并且t
的动态值与x
相等,则它们相等。 - 结构体值可比较,如果它们的所有字段都可比较。两个结构体值相等,如果它们对应的非 空白标识符 字段相等。
- 数组值可比较,如果数组元素类型的值可比较。两个数组值相等,如果它们对应的元素相等。
具有相同动态类型的两个接口值的比较会导致 运行时 panic,如果该类型的值不可比较。这种行为不仅适用于直接的接口值比较,也适用于比较接口值数组或具有接口值字段的结构体时。
切片、map 和函数值是不可比较的。然而,作为特殊情况,切片、map 或函数值可以与预声明标识符 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
的求值会导致 运行时 panic,那么对 &x
的求值也会。
对于指针类型 *T
的操作数 x
,指针解引用 *x
表示 x
指向的类型为 T
的 变量。如果 x
是 nil
,尝试求值 *x
将导致 运行时 panic。
&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
,如果 x
可由类型 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
是一个整数或字节切片或 rune 切片,并且T
是字符串类型。 -
x
是字符串,并且T
是字节切片或 rune 切片。 -
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
常量表达式
常量表达式只能包含 常量 操作数,并在编译时求值。
无类型布尔常量、数值常量和字符串常量可以分别在任何合法使用布尔类型、数值类型或字符串类型操作数的地方用作操作数。
常量 比较 总是产生一个无类型布尔常量。如果常量 位移表达式 的左操作数是无类型常量,则结果是整数常量;否则是与左操作数相同类型的常量,左操作数必须是 整数类型。
对无类型常量的任何其他操作都会产生相同类型的无类型常量;即布尔常量、整数常量、浮点常量、复数常量或字符串常量。如果二元运算(位移除外)的无类型操作数类型不同,则结果是操作数在此列表中出现较晚的类型:integer, rune, floating-point, complex。例如,无类型整数常量除以无类型复数常量会产生一个无类型复数常量。
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
实现限制:编译器在计算无类型浮点或复数常量表达式时可能会使用舍入;请参见 常量 一节中的实现限制。这种舍入可能导致浮点常量表达式在整数上下文中无效,即使使用无限精度计算时它将是整数,反之亦然。
求值顺序
在包级别,初始化依赖项 决定了 变量声明 中各个初始化表达式的求值顺序。否则,在求值表达式、赋值或 return 语句 的 操作数 时,所有函数调用、方法调用和通信操作都按照词法上的从左到右顺序求值。
例如,在(函数本地的)赋值中:
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" 语句不使用 range 子句。
- 一个 "switch" 语句,其中:
- 没有指向该 "switch" 语句的 "break" 语句,
- 存在 default 分支,并且
- 每个 case 的语句列表(包括 default)以终止语句结尾,或者以可能带有标签的 "fallthrough" 语句 结尾。
- 一个 "select" 语句,其中:
- 没有指向该 "select" 语句的 "break" 语句,并且
- 每个 case 的语句列表(如果存在 default,则包括 default)以终止语句结尾。
- 标记了终止语句的 带标签语句。
所有其他语句都不是终止语句。
一个 语句列表 以终止语句结尾,如果该列表非空,且其最后一个非空语句是终止语句。
空语句
空语句不做任何事。
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
自增/自减语句
"++" 和 "--" 语句将其操作数自增或自减无类型 常量 1
。与赋值一样,操作数必须 可寻址 或是一个 map 索引表达式。
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" 内部的 "cases" 进行比较,以确定执行哪个分支。
SwitchStmt = ExprSwitchStmt | TypeSwitchStmt .
有两种形式:表达式 switch 和类型 switch。在表达式 switch 中,cases 包含用于与 switch 表达式的值进行比较的表达式。在类型 switch 中,cases 包含用于与特殊标注的 switch 表达式的类型进行比较的类型。switch 表达式在 switch 语句中仅被求值一次。
表达式 Switch
在表达式 switch 中,switch 表达式被求值,然后 cases 表达式(不一定是常量)按从左到右、从上到下的顺序求值;第一个等于 switch 表达式的 case 会触发执行其关联的语句;其他 cases 则被跳过。如果没有 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
一样;每个 case 表达式 x
都将与 t
的那个值进行相等性测试。
在 case 或 default 子句中,最后一个非空语句可以是一个(可能带标签的)"fallthrough" 语句,表示控制流应从当前子句的末尾转移到下一个子句的第一个语句。否则,控制流会跳转到 "switch" 语句的末尾。"fallthrough" 语句可以出现在表达式 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 }
然后 cases 将实际类型 T
与表达式 x
的动态类型进行匹配。与类型断言一样,x
必须是接口类型,并且 case 中列出的每个非接口类型 T
都必须实现 x
的类型。类型 switch 中 cases 中列出的类型必须都不同。
TypeSwitchStmt = "switch" [ SimpleStmt ";" ] TypeSwitchGuard "{" { TypeCaseClause } "}" . TypeSwitchGuard = [ identifier ":=" ] PrimaryExpr "." "(" "type" ")" . TypeCaseClause = TypeSwitchCase ":" StatementList . TypeSwitchCase = "case" TypeList | "default" . TypeList = Type { "," Type } .
TypeSwitchGuard 可以包含短变量声明。当使用该形式时,变量在每个子句的隐式块中 TypeSwitchCase 的末尾声明。在恰好列出一个类型的子句中,该变量具有该类型;否则,该变量具有 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 guard 前面可以有一个简单语句,该语句在 guard 求值之前执行。
类型 switch 中不允许使用 "fallthrough" 语句。
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" 子句右边的表达式称为range 表达式,它可以是数组、指向数组的指针、切片、字符串、映射或允许接收操作的通道。与赋值一样,如果存在,左边的操作数必须是可寻址的或映射索引表达式;它们表示迭代变量。如果 range 表达式是通道,则最多允许一个迭代变量,否则最多允许两个。如果最后一个迭代变量是空白标识符,则 range 子句等价于没有该标识符的相同子句。
range 表达式 x
在循环开始前求值一次,有一个例外:如果最多存在一个迭代变量且 len(x)
是常量,则 range 表达式不求值。
左边的函数调用在每次迭代中求值一次。对于每次迭代,如果存在相应的迭代变量,迭代值按如下方式产生
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 开始。如果最多存在一个迭代变量,range 循环产生从 0 到len(a)-1
的迭代值,并且不索引到数组或切片本身。对于nil
切片,迭代次数为 0。 - 对于字符串值,"range" 子句从字节索引 0 开始遍历字符串中的 Unicode 码点。在连续的迭代中,索引值将是字符串中连续 UTF-8 编码码点的第一个字节的索引,第二个值,类型为
rune
,将是对应码点的值。如果迭代遇到无效的 UTF-8 序列,第二个值将是0xFFFD
,即 Unicode 替换字符,并且下一次迭代将在字符串中前进一个字节。 - 映射的迭代顺序未指定,并且不保证在一次迭代到下一次迭代之间保持相同。如果在迭代期间删除了尚未到达的映射条目,则不会产生相应的迭代值。如果在迭代期间创建了映射条目,该条目可能在迭代期间产生,也可能被跳过。这种选择对于每个创建的条目以及从一次迭代到下一次迭代都可能不同。如果映射为
nil
,迭代次数为 0。 - 对于通道,产生的迭代值是在通道上发送的连续值,直到通道被关闭。如果通道为
nil
,则 range 表达式会永远阻塞。
迭代值被赋给相应的迭代变量,就像在赋值语句中一样。
迭代变量可以通过使用一种短变量声明形式(:=
)由 "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" 语句,但 cases 都指向通信操作。
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" 语句的执行分几个步骤进行
- 对于语句中的所有 cases,接收操作的通道操作数以及发送语句的通道和右手边表达式在进入 "select" 语句时,按源码顺序恰好求值一次。结果是一组要从中接收或向其发送的通道,以及要发送的相应值。该求值中的任何副作用都会发生,无论选择(如果选择)哪个通信操作继续执行。带有短变量声明或赋值的 RecvStmt 左手边的表达式尚未求值。
- 如果一个或多个通信可以进行,则通过均匀的伪随机选择来选择其中一个可以进行的操作。否则,如果存在 default case,则选择该 case。如果不存在 default case,"select" 语句会阻塞,直到至少一个通信可以进行。
- 除非选中的 case 是 default case,否则执行相应的通信操作。
- 如果选中的 case 是带有短变量声明或赋值的 RecvStmt,则对左手边表达式求值并赋上接收到的值(或多个值)。
- 执行选中 case 的语句列表。
由于在 nil
通道上的通信永远无法进行,因此只有 nil
通道且没有 default case 的 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
Return 语句
函数 F
中的 "return" 语句终止 F
的执行,并可选地提供一个或多个结果值。任何由 F
延迟执行的函数都会在 F
返回到其调用者之前执行。
ReturnStmt = "return" [ ExpressionList ] .
在没有结果类型的函数中,"return" 语句不得指定任何结果值。
func noResult() { return }
有三种方式从带有结果类型的函数返回值
- "return" 语句中可以明确列出返回值或多个值。每个表达式必须是单值的,并且可赋值给函数结果类型的相应元素。
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() }
- 如果函数的结果类型为其结果参数指定了名称,则表达式列表可以为空。结果参数充当普通的局部变量,函数可以根据需要给它们赋值。"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 }
无论如何声明,所有结果值在函数入口时都被初始化为其类型的零值。指定结果的 "return" 语句在任何延迟函数执行之前设置结果参数。
实现限制:如果返回位置的作用域中存在与结果参数同名的不同实体(常量、类型或变量),编译器可能会禁止在 "return" 语句中使用空表达式列表。
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" 语句在其 post 语句处开始最内层"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" 语句不得导致任何在 goto 点时尚未进入作用域的变量进入作用域。例如,此示例
goto L // BAD v := 3 L:
是错误的,因为跳转到标签 L
跳过了 v
的创建。
一个块外部的 "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 语句、到达了其函数体的末尾,或者相应的 goroutine 正在发生 panic。
DeferStmt = "defer" Expression .
该表达式必须是函数或方法调用;它不能带括号。内置函数的调用受表达式语句的限制。
每当 "defer" 语句执行时,函数值和调用参数会照常求值并重新保存,但实际函数不会被调用。相反,延迟函数在所在的函数返回之前立即调用,调用的顺序与它们被延迟的顺序相反。也就是说,如果所在的函数通过显式return 语句返回,延迟函数在结果参数被该 return 语句设置之后但在函数返回给其调用者之前执行。如果延迟的函数值求值为 nil
,则在函数被调用时发生panic,而不是在 "defer" 语句执行时。
例如,如果延迟函数是函数字面量,并且所在的函数具有在其字面量作用域内的命名结果参数,则延迟函数可以在结果参数返回之前访问和修改它们。如果延迟函数有任何返回值,它们在函数完成后被丢弃。(另见处理 panic 一节。)
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 }
内置函数
内置函数是预声明的。它们像其他函数一样调用,但其中一些接受类型而不是表达式作为第一个参数。
内置函数没有标准的 Go 类型,因此它们只能出现在调用表达式中;它们不能用作函数值。
Close
对于通道 c
,内置函数 close(c)
记录通道上不会再发送值。如果 c
是只接收通道,则会出错。向已关闭的通道发送或关闭已关闭的通道会导致运行时 panic。关闭 nil 通道也会导致运行时 panic。调用 close
后,以及在所有先前发送的值都被接收后,接收操作将返回通道类型的零值而不会阻塞。多值的接收操作会返回接收到的值以及通道是否已关闭的指示。
长度和容量
内置函数 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)
是常量。如果 s
的类型是数组或指向数组的指针,并且表达式 s
不包含通道接收或(非常量)函数调用,则表达式 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
,在运行时为该类型的变量分配存储空间,并返回一个类型为 *T
指向该变量的值。变量按照初始值一节中的描述进行初始化。
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
都必须是整数类型或无类型常量。常量大小参数必须是非负的,并且可以用 int
类型的值表示;如果它是无类型常量,则赋予类型 int
。如果 n
和 m
都提供且都是常量,则 n
不能大于 m
。如果在运行时 n
为负或大于 m
,则会发生运行时 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
调用 make
并指定映射类型和大小提示 n
会创建一个具有初始空间以容纳 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
两个内置函数 panic
和 recover
用于报告和处理运行时 panic 和程序定义的错误情况。
func panic(interface{}) func recover() interface{}
在执行函数 F
期间,显式调用 panic
或发生运行时 panic 会终止 F
的执行。然后照常执行由 F
延迟的任何函数。接下来,执行由 F
的调用者延迟的任何函数,依此类推,直到执行 goroutine 中顶层函数延迟的任何函数。此时,程序终止并报告错误情况,包括 panic
参数的值。此终止序列称为 panicking。
panic(42) panic("unreachable") panic(Error("cannot parse"))
recover
函数允许程序管理发生 panic 的 goroutine 的行为。假设函数 G
延迟了一个调用 recover
的函数 D
,并且在 G
正在执行的同一 goroutine 中的某个函数发生了 panic。当延迟函数的运行到达 D
时,D
调用 recover
的返回值将是传递给 panic
调用的值。如果 D
正常返回,而没有启动新的 panic
,则 panic 序列停止。在这种情况下,在 G
和调用 panic
之间调用的函数的状态将被丢弃,正常执行恢复。然后运行由 G
在 D
之前延迟的任何函数,并且 G
的执行通过返回给其调用者而终止。
在以下任何条件成立时,recover
的返回值为 nil
-
panic
的参数是nil
; - goroutine 没有发生 panic;
-
recover
不是由延迟函数直接调用的。
下面示例中的 protect
函数调用函数参数 g
,并保护调用者免受 g
引发的运行时 panic 的影响。
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 的解释取决于实现,但它通常是编译包的完整文件名的一部分,并且可能相对于已安装包的存储库。
实现限制:编译器可以将 ImportPaths 限制为仅使用属于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
,如果x
的初始化表达式或函数体(对于函数和方法)包含对y
或依赖于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)
运行时panic
执行错误,例如尝试对数组进行越界索引,会触发一个运行时panic,等同于调用内置函数panic
,其值是实现定义的接口类型runtime.Error
。该类型满足预声明接口类型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
不为零,则会发生运行时panic。
大小和对齐保证
对于数字类型,保证以下大小
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)
是x
的每个字段f
对应的所有unsafe.Alignof(x.f)
值中的最大值,但至少为1。 - 对于数组类型的变量
x
:unsafe.Alignof(x)
与该数组元素类型的变量的对齐相同。
如果结构体或数组类型不包含大小大于零的字段(或元素),则其大小为零。两个不同的零大小变量可能具有相同的内存地址。