Go 编程语言规范
语言版本 go1.23 (2024 年 6 月 13 日)
简介
这是 Go 编程语言的参考手册。Go1.18 之前的版本(不含泛型)可以 在此处 找到。有关更多信息和其他文档,请参阅 go.dev。
Go 是一种通用语言,设计时考虑了系统编程。它具有强类型和垃圾回收功能,并对并发编程提供显式支持。程序由包构成,其属性允许有效地管理依赖项。
语法简洁且易于解析,允许自动工具(如集成开发环境)轻松进行分析。
符号
语法使用扩展巴克斯-诺尔范式 (EBNF) 的变体进行指定。
Syntax = { Production } . Production = production_name "=" [ Expression ] "." . Expression = Term { "|" Term } . Term = Factor { Factor } . Factor = 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 语言的标记。
表单 [Go 1.xx] 的链接表示所描述的语言特性(或其某些方面)在语言版本 1.xx 中进行了更改或添加,因此至少需要该语言版本才能构建。有关详细信息,请参阅链接部分中的附录。
源代码表示
源代码是使用UTF-8编码的 Unicode 文本。文本未规范化,因此单个带重音的代码点与通过组合重音和字母构建的相同字符不同;这些被视为两个代码点。为简单起见,本文档将使用未限定的术语字符来指代源文本中的 Unicode 代码点。
每个代码点都是不同的;例如,大写字母和小写字母是不同的字符。
实现限制:为了与其他工具兼容,编译器可能会禁止源文本中的 NUL 字符 (U+0000)。
实现限制:为了与其他工具兼容,如果编译器是源文本中的第一个 Unicode 代码点,则它可能会忽略 UTF-8 编码的字节顺序标记 (U+FEFF)。在源代码的其他任何位置都可能不允许使用字节顺序标记。
字符
以下术语用于表示特定的 Unicode 字符类别
newline = /* the Unicode code point U+000A */ . unicode_char = /* an arbitrary Unicode code point except newline */ . unicode_letter = /* a Unicode code point categorized as "Letter" */ . unicode_digit = /* a Unicode code point categorized 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
运算符和标点符号
以下字符序列表示运算符(包括赋值运算符) 和标点符号 [Go 1.18]
+ & += &= && == != ( ) - | -= |= || < <= [ ] * ^ *= ^= <- > >= { } / << /= <<= ++ = := , ; % >> %= >>= -- ! ... . : &^ &^= ~
整数字面量
整数字面量是表示整数常量的数字序列。可选的前缀设置非十进制基数:0b
或0B
表示二进制,0
、0o
或0O
表示八进制,0x
或0X
表示十六进制 [Go 1.13]。单个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缩放 [Go 1.13]。
为了提高可读性,下划线字符_
可以出现在基数前缀之后或连续数字之间;此类下划线不会更改字面量的值。
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 [Go 1.13]
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 '\k' // illegal: k is not recognized after a backslash '\xa' // illegal: too few hexadecimal digits '\0' // illegal: too few octal digits '\400' // illegal: octal value over 255 '\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、整数、浮点数、虚数或字符串字面量、表示常量的标识符、常量表达式、结果为常量的转换或某些内置函数(如 min
或 max
)应用于常量参数的结果、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 [ TypeArgs ] | TypeLit | "(" Type ")" . TypeName = identifier | QualifiedIdent . TypeArgs = "[" TypeList [ "," ] "]" . TypeList = Type { "," Type } . TypeLit = ArrayType | StructType | PointerType | FunctionType | InterfaceType | SliceType | MapType | ChannelType .
该语言预声明某些类型名称。其他类型通过类型声明或类型参数列表引入。复合类型——数组、结构体、指针、函数、接口、切片、映射和通道类型——可以使用类型字面量构建。
预声明类型、定义类型和类型参数称为命名类型。如果别名声明中给定的类型是命名类型,则别名表示命名类型。
布尔类型
布尔类型表示由预声明的常量 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))
如果包含类型仅为数组或结构体类型,则数组类型T
不能具有类型T
的元素,或包含T
作为直接或间接组件的类型的元素。
// invalid array types type ( T1 [10]T1 // element type of T1 is T1 T2 [10]struct{ f T2 } // T2 contains T2 as component of a struct T3 [10]T4 // T3 contains T3 as component of a struct in T4 T4 struct{ f T3 } // T4 contains T4 as component of array T3 in a struct ) // valid array types type ( T5 [10]*T5 // T5 contains T5 as component of a pointer T6 [10]func() T6 // T6 contains T6 as component of a function type T7 [10]struct{ f []T7 } // T7 contains T7 as component of a slice in a struct )
切片类型
切片是底层数组的连续片段的描述符,并提供对该数组中编号元素序列的访问。切片类型表示其元素类型的所有数组切片的集合。元素的数量称为切片的长度,并且永远不会为负。未初始化的切片的值为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 [ TypeArgs ] . 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"` }
如果包含类型仅为数组或结构体类型,则结构体类型T
不能包含类型T
的字段,或包含T
作为直接或间接组件的类型的字段。
// invalid struct types type ( T1 struct{ T1 } // T1 contains a field of T1 T2 struct{ f [10]T2 } // T2 contains T2 as component of an array T3 struct{ T4 } // T3 contains T3 as component of an array in struct T4 T4 struct{ f [10]T3 } // T4 contains T4 as component of struct T3 in an array ) // valid struct types type ( T5 struct{ f *T5 } // T5 contains T5 as component of a pointer T6 struct{ f func() T6 } // T6 contains T6 as component of a function type T7 struct{ f [10][]T7 } // T7 contains T7 as component of a slice in an array )
指针类型
指针类型表示指向给定类型的所有指针的集合,该类型称为指针的基础类型。未初始化指针的值为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" "{" { InterfaceElem ";" } "}" . InterfaceElem = MethodElem | TypeElem . MethodElem = MethodName Signature . MethodName = identifier . TypeElem = TypeTerm { "|" TypeTerm } . TypeTerm = Type | UnderlyingType . UnderlyingType = "~" Type .
接口类型由接口元素列表指定。接口元素要么是方法,要么是类型元素,其中类型元素是一个或多个类型项的并集。类型项要么是单个类型,要么是单个底层类型。
基本接口
在其最基本的形式中,接口指定了一个(可能是空的)方法列表。此类接口定义的类型集是实现所有这些方法的类型的集合,并且相应的方法集正好由接口指定的方法组成。其类型集可以完全由方法列表定义的接口称为基本接口。
// 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{}
为方便起见,预声明类型any
是空接口的别名。[Go 1.18]
类似地,考虑此接口规范,它出现在类型声明中以定义名为Locker
的接口
type Locker interface { Lock() Unlock() }
如果S1
和S2
也实现了
func (p T) Lock() { … } func (p T) Unlock() { … }
它们除了实现File
接口外,也实现了Locker
接口。
嵌入接口
在稍微更通用的形式中,接口T
可以使用(可能是限定的)接口类型名称E
作为接口元素。这称为在T
中嵌入接口E
[Go 1.14]。T
的类型集是T
的显式声明方法和T
的嵌入接口定义的类型集的交集。换句话说,T
的类型集是实现T
的所有显式声明方法以及E
的所有方法的所有类型的集合 [Go 1.18]。
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
的项,指定底层类型T
,或项的并集t1|t2|…|tn
[Go 1.18]。结合方法规范,这些元素能够精确定义接口的类型集,如下所示
- 空接口的类型集是所有非接口类型的集合。
- 非空接口的类型集是其接口元素的类型集的交集。
- 方法规范的类型集是其方法集包含该方法的所有非接口类型的集合。
- 非接口类型项的类型集是由该类型组成的集合。
- 形式为
~T
的项的类型集是所有底层类型为T
的类型的集合。 - 项
t1|t2|…|tn
的并集的类型集是这些项的类型集的并集。
"所有非接口类型的集合"的量化不仅指当前程序中声明的所有(非接口)类型,还指所有可能程序中所有可能的类型,因此是无限的。类似地,给定实现特定方法的所有非接口类型的集合,这些类型的方法集的交集将正好包含该方法,即使当前程序中的所有类型始终将该方法与另一个方法配对。
根据构造,接口的类型集永远不会包含接口类型。
// An interface representing only the type int. interface { int } // An interface representing all types with underlying type int. interface { ~int } // An interface representing all types with underlying type int that implement the String method. interface { ~int String() string } // An interface representing an empty type set: there is no type that is both an int and a string. interface { int string }
在形式为~T
的项中,T
的底层类型必须是其自身,并且T
不能是接口。
type MyInt int interface { ~[]byte // the underlying type of []byte is itself ~MyInt // illegal: the underlying type of MyInt is not MyInt ~error // illegal: error is an interface }
并集元素表示类型集的并集
// The Float interface represents all floating-point types // (including any named types whose underlying types are // either float32 or float64). type Float interface { ~float32 | ~float64 }
形式为T
或~T
的项中的类型T
不能是类型参数,并且所有非接口项的类型集必须是成对不相交的(类型集的成对交集必须为空)。给定一个类型参数P
interface { P // illegal: P is a type parameter int | ~P // illegal: P is a type parameter ~int | MyInt // illegal: the type sets for ~int and MyInt are not disjoint (~int includes MyInt) float32 | Float // overlapping type sets but Float is an interface }
实现限制:并集(包含多个项)不能包含预声明标识符comparable
或指定方法的接口,也不能嵌入comparable
或指定方法的接口。
不是基本的接口只能用作类型约束,或用作用作约束的其他接口的元素。它们不能作为值或变量的类型,也不能作为其他非接口类型的组件。
var x Float // illegal: Float is not a basic interface var x interface{} = Float(nil) // illegal type Floatish struct { f Float // illegal }
接口类型T
不能嵌入直接或间接包含、包含或嵌入T
的类型元素。
// illegal: Bad may not embed itself type Bad interface { Bad } // illegal: Bad1 may not embed itself using Bad2 type Bad1 interface { Bad2 } type Bad2 interface { Bad1 } // illegal: Bad3 may not embed a union containing Bad3 type Bad3 interface { ~int | ~string | Bad3 } // illegal: Bad4 may not embed an array containing Bad4 as element type type Bad4 interface { [10]Bad4 }
实现接口
类型T
实现接口I
,如果
-
T
不是接口类型,并且是I
的类型集中的一个元素;或者 -
T
是接口类型,并且T
的类型集是I
的类型集的子集。
类型T
的值实现接口,如果T
实现了该接口。
映射类型
映射是一组无序的元素,这些元素属于一种类型(称为元素类型),并由另一类型的唯一键集(称为键类型)索引。未初始化映射的值为nil
。
MapType = "map" "[" KeyType "]" ElementType . KeyType = Type .
比较运算符==
和!=
必须针对键类型的操作数完全定义;因此,键类型不能是函数、映射或切片。如果键类型是接口类型,则必须为动态键值定义这些比较运算符;否则将导致运行时恐慌。
map[string]int map[*T]struct{ x, y float64 } map[string]interface{}
映射元素的数量称为其长度。对于映射m
,可以使用内置函数len
来获取,并且它在执行期间可能会发生变化。可以使用赋值语句在执行期间添加元素,并使用索引表达式检索元素;可以使用delete
和clear
内置函数删除元素。
可以使用内置函数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
关闭通道。接收运算符的多值赋值形式报告接收到的值是否在通道关闭之前发送。
单个通道可以在发送语句、接收操作以及对内置函数cap
和len
的调用中由任意数量的goroutine使用,而无需进一步同步。通道充当先进先出队列。例如,如果一个goroutine在通道上发送值,而第二个goroutine接收这些值,则接收到的值的顺序与发送的顺序相同。
类型和值的属性
底层类型
每个类型T
都有一个底层类型:如果T
是预声明的布尔类型、数值类型或字符串类型,或者类型字面量,则相应的底层类型为T
本身。否则,T
的底层类型是T
在其声明中引用的类型的底层类型。对于作为其类型约束的底层类型的类型参数,该类型始终为接口。
type ( A1 = string A2 = A1 ) type ( B1 string B2 B1 B3 []B1 B4 B3 ) func f[P any](x P) { … }
string
、A1
、A2
、B1
和B2
的底层类型为string
。[]B1
、B3
和B4
的底层类型为[]B1
。P
的底层类型为interface{}
。
核心类型
每个非接口类型T
都有一个核心类型,它与T
的底层类型相同。
如果满足以下条件之一,则接口T
具有核心类型
没有其他接口具有核心类型。
接口的核心类型取决于满足的条件,可以是
- 类型
U
;或者 - 如果
T
仅包含双向通道,则类型为chan E
,或者根据存在的定向通道的方向,类型为chan<- E
或<-chan E
。
具有核心类型的接口示例
type Celsius float32 type Kelvin float32 interface{ int } // int interface{ Celsius|Kelvin } // float32 interface{ ~chan int } // chan int interface{ ~chan int|~chan<- int } // chan<- int interface{ ~[]*data; String() string } // []*data
没有核心类型的接口示例
interface{} // no single underlying type interface{ Celsius|float64 } // no single underlying type interface{ chan int | chan<- string } // channels have different element types interface{ <-chan int | chan<- int } // directional channels have different directions
某些操作(切片表达式、append
和copy
)依赖于核心类型的稍微宽松的形式,该形式接受字节切片和字符串。具体来说,如果正好有两种类型[]byte
和string
是接口T
的类型集中所有类型的底层类型,则T
的核心类型称为bytestring
。
具有bytestring
核心类型的接口示例
interface{ int } // int (same as ordinary core type) interface{ []byte | string } // bytestring interface{ ~[]byte | myString } // bytestring
请注意,bytestring
不是真正的类型;它不能用于声明变量或组合其他类型。它仅用于描述某些从字节序列(可能是字节切片或字符串)读取的操作的行为。
类型同一性
两种类型要么相同,要么不同。
命名类型始终与任何其他类型不同。否则,如果它们的底层类型字面量在结构上等效,则两种类型相同;也就是说,它们具有相同的字面量结构,并且相应的组件具有相同的类型。详细说明
- 如果两种数组类型具有相同的元素类型和相同的数组长度,则它们相同。
- 如果两种切片类型具有相同的元素类型,则它们相同。
- 如果两种结构体类型具有相同的字段序列,并且如果对应的字段具有相同的名称、相同的类型和相同的标签,则它们相同。未导出的来自不同包的字段名称始终不同。
- 如果两种指针类型具有相同的基类型,则它们相同。
- 如果两种函数类型具有相同数量的参数和结果值,对应的参数和结果类型相同,并且两种函数都是可变参数或都不是可变参数,则它们相同。不需要参数和结果名称匹配。
- 如果两种接口类型定义相同的类型集,则它们相同。
- 如果两种映射类型具有相同的键类型和元素类型,则它们相同。
- 如果两种通道类型具有相同的元素类型和相同的方向,则它们相同。
- 如果两种实例化的类型具有相同的定义类型和所有类型参数,则它们相同。
给定声明
type ( A0 = []string A1 = A0 A2 = struct{ a, b int } A3 = int A4 = func(A3, float64) *A0 A5 = func(x int, _ float64) *[]string 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 C0 = B0 D0[P1, P2 any] struct{ x P1; y P2 } E0 = D0[int, string] )
这些类型相同
A0, A1, and []string A2 and struct{ a, b int } A3 and int A4, func(int, float64) *[]string, and A5 B0 and C0 D0[int, string] and E0 []int and []int struct{ a, b *B5 } and struct{ a, b *B5 } 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
不同;P1
和P2
不同,因为它们是不同的类型参数。D0[int, string]
和struct{ x int; y string }
不同,因为前者是实例化的定义类型,而后者是类型字面量(但它们仍然是可赋值的)。
可赋值性
类型V
的值x
可赋值给类型T
的变量(“x
可赋值给T
”),如果满足以下条件之一
-
V
和T
相同。 -
V
和T
具有相同的底层类型,但不是类型参数,并且V
或T
至少有一个不是命名类型。 -
V
和T
是具有相同元素类型的通道类型,V
是双向通道,并且V
或T
至少有一个不是命名类型。 -
T
是接口类型,但不是类型参数,并且x
实现了T
。 -
x
是预声明的标识符nil
,T
是指针、函数、切片、映射、通道或接口类型,但不是类型参数。 -
x
是类型T
的值可以表示的未类型化的常量。
此外,如果x
的类型V
或T
是类型参数,则如果满足以下条件之一,则x
可赋值给类型T
的变量
-
x
是预声明的标识符nil
,T
是类型参数,并且x
可赋值给T
的类型集中的每个类型。 -
V
不是命名类型,T
是类型参数,并且x
可赋值给T
的类型集中的每个类型。 -
V
是类型参数,T
不是命名类型,并且V
的类型集中的每个类型的值都可赋值给T
。
可表示性
如果类型 T
不是类型参数,则常量 x
可以用类型 T
的值表示,当且仅当以下条件之一适用。
-
x
位于由T
确定的值集中。 -
T
是浮点类型,并且x
可以在不溢出的情况下舍入到T
的精度。舍入使用 IEEE 754 向偶数舍入规则,但 IEEE 负零进一步简化为无符号零。请注意,常量值永远不会导致 IEEE 负零、NaN 或无穷大。 -
T
是复数类型,并且x
的组件real(x)
和imag(x)
可以用T
的组件类型(float32
或float64
)的值表示。
如果 T
是类型参数,则如果 x
可以用 T
的类型集中每个类型的值表示,则 x
可以用类型 T
的值表示。
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
方法集
类型的方法集确定可以在该类型的操作数上调用的方法。每种类型都有一个(可能是空的)与其关联的方法集。
- 已定义类型
T
的方法集由所有以接收器类型T
声明的方法组成。 - 指向已定义类型
T
的指针的方法集(其中T
既不是指针也不是接口)是所有以接收器*T
或T
声明的方法的集合。 - 接口类型的方法集是接口类型集中每个类型的方法集的交集(生成的方法集通常只是接口中声明的方法的集合)。
结构体(以及指向结构体的指针)包含嵌入字段时,将应用其他规则,如结构体类型部分所述。任何其他类型都有一个空方法集。
在方法集中,每个方法都必须具有唯一的非空白方法名。
代码块
代码块是在匹配的花括号内的声明和语句的可能为空的序列。
Block = "{" StatementList "}" . StatementList = { Statement ";" } .
除了源代码中的显式代码块外,还有隐式代码块。
- 宇宙块包含所有 Go 源代码文本。
- 每个包都有一个包块,其中包含该包的所有 Go 源代码文本。
- 每个文件都有一个文件块,其中包含该文件中的所有 Go 源代码文本。
- 每个“if”、“for”和“switch”语句都被认为在其自己的隐式代码块中。
- “switch”或“select”语句中的每个子句都充当一个隐式代码块。
代码块嵌套并影响作用域。
声明和作用域
声明将非空白标识符绑定到常量、类型、类型参数、变量、函数、标签或包。程序中的每个标识符都必须声明。在同一代码块中不得重复声明任何标识符,并且不得在文件和包块中同时声明任何标识符。
空白标识符可以像声明中的任何其他标识符一样使用,但它不引入绑定,因此未声明。在包块中,标识符 init
只能用于 init
函数声明,并且像空白标识符一样,它不引入新的绑定。
Declaration = ConstDecl | TypeDecl | VarDecl . TopLevelDecl = Declaration | FunctionDecl | MethodDecl .
作用域是源代码文本的范围,其中标识符表示指定的常量、类型、变量、函数、标签或包。
Go 使用代码块进行词法作用域。
- 预声明标识符的作用域是宇宙块。
- 在顶层(在任何函数之外)声明的表示常量、类型、变量或函数(但不是方法)的标识符的作用域是包块。
- 导入包的包名的作用域是包含导入声明的文件的文件块。
- 表示方法接收器、函数参数或结果变量的标识符的作用域是函数体。
- 表示函数的类型参数或方法接收器声明的类型参数的标识符的作用域从函数名称之后开始,到函数体末尾结束。
- 表示类型的类型参数的标识符的作用域从类型名称之后开始,到 TypeSpec 末尾结束。
- 在函数内部声明的常量或变量标识符的作用域从 ConstSpec 或 VarSpec(对于短变量声明,则为 ShortVarDecl)的末尾开始,到最内层包含代码块的末尾结束。
- 在函数内部声明的类型标识符的作用域从 TypeSpec 中的标识符开始,到最内层包含代码块的末尾结束。
在代码块中声明的标识符可以在内部代码块中重新声明。当内部声明的标识符处于作用域内时,它表示内部声明声明的实体。
包子句不是声明;包名称不会出现在任何作用域中。其目的是识别属于同一包的文件并为导入声明指定默认包名称。
标签作用域
标签由带标签的语句声明,并在“break”、“continue”和“goto”语句中使用。定义永远不会使用的标签是非法的。与其他标识符相反,标签不是代码块作用域,并且不会与不是标签的标识符冲突。标签的作用域是声明它的函数的主体,并且不包括任何嵌套函数的主体。
空白标识符
空白标识符由下划线字符 _
表示。它用作匿名占位符而不是常规(非空白)标识符,并在声明、操作数和赋值语句中具有特殊含义。
预声明标识符
以下标识符在宇宙块中隐式声明 [Go 1.18] [Go 1.21]
Types: any bool byte comparable 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 clear close complex copy delete imag len make max min new panic print println real recover
导出标识符
可以导出标识符以允许从另一个包访问它。如果以下两个条件都满足,则标识符被导出:
- 标识符名称的第一个字符是 Unicode 大写字母(Unicode 字符类别 Lu);以及
- 标识符在包块中声明,或者它是字段名或方法名。
所有其他标识符都不会导出。
标识符的唯一性
给定一组标识符,如果某个标识符与集合中的所有其他标识符都不同,则称该标识符为唯一的。如果两个标识符的拼写不同,或者如果它们出现在不同的包中且未导出,则它们是不同的。否则,它们是相同的。
常量声明
常量声明将标识符列表(常量的名称)绑定到常量表达式列表的值。标识符的数量必须等于表达式的数量,并且左侧的第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 .
别名声明
别名声明将标识符绑定到给定类型 [Go 1.9]。
AliasDecl = identifier "=" Type .
在标识符的作用域内,它充当类型的别名。
type ( nodeList = []*Node // nodeList and []*Node are identical types Polar = polar // Polar and polar denote identical types )
类型定义
类型定义创建一个新的、不同的类型,它与给定类型具有相同的基础类型和操作,并将标识符(类型名称)绑定到它。
TypeDef = identifier [ TypeParameters ] 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 any } 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) }
如果类型定义指定类型参数,则类型名称表示泛型类型。使用泛型类型时必须对其进行实例化。
type List[T any] struct { next *List[T] value T }
在类型定义中,给定类型不能是类型参数。
type T[P any] P // illegal: P is a type parameter func f[T any]() { type L T // illegal: T is a type parameter declared by the enclosing function }
泛型类型也可以与其关联的方法。在这种情况下,方法接收器必须声明与泛型类型定义中存在的类型参数数量相同的类型参数。
// The method Len returns the number of elements in the linked list l. func (l *List[T]) Len() int { … }
类型参数声明
类型参数列表声明泛型函数或类型声明的类型参数。类型参数列表看起来像普通的函数参数列表,除了类型参数名称必须全部存在并且列表用方括号而不是圆括号括起来 [Go 1.18]。
TypeParameters = "[" TypeParamList [ "," ] "]" . TypeParamList = TypeParamDecl { "," TypeParamDecl } . TypeParamDecl = IdentifierList TypeConstraint .
列表中的所有非空白名称必须唯一。每个名称都声明一个类型参数,它是一个新的且不同的命名类型,用作声明中(迄今为止)未知类型的占位符。在泛型函数或类型的实例化时,类型参数将被类型参数替换。
[P any] [S interface{ ~[]byte|string }] [S ~[]E, E any] [P Constraint[int]] [_ any]
就像每个普通函数参数都有一个参数类型一样,每个类型参数都有一个对应的(元)类型,称为其类型约束。
当泛型类型的类型参数列表声明一个带有约束 C
的单个类型参数 P
,并且文本 P C
构成一个有效的表达式时,就会出现解析歧义。
type T[P *C] … type T[P (C)] … type T[P *C|Q] … …
在这些罕见的情况下,类型参数列表与表达式无法区分,类型声明会被解析为数组类型声明。为了解决歧义,请将约束嵌入到接口中或使用尾随逗号。
type T[P interface{*C}] … type T[P *C,] …
类型参数也可以由与泛型类型关联的方法声明的接收者规范声明。
在泛型类型 T
的类型参数列表中,类型约束不能(直接或通过另一个泛型类型的类型参数列表间接)引用 T
。
type T1[P T1[P]] … // illegal: T1 refers to itself type T2[P interface{ T2[int] }] … // illegal: T2 refers to itself type T3[P interface{ m(T3[int])}] … // illegal: T3 refers to itself type T4[P T5[P]] … // illegal: T4 refers to T5 and type T5[P T4[P]] … // T5 refers to T4 type T6[P int] struct{ f *T6[P] } // ok: reference to T6 is not in type parameter list
类型约束
类型约束是一个接口,它定义了相应类型参数的允许类型参数集,并控制该类型参数的值支持的操作 [Go 1.18]。
TypeConstraint = TypeElem .
如果约束是 interface{E}
形式的接口字面量,其中 E
是一个嵌入的类型元素(不是方法),则在类型参数列表中,可以为了方便省略包围的 interface{ … }
。
[T []P] // = [T interface{[]P}] [T ~int] // = [T interface{~int}] [T int|string] // = [T interface{int|string}] type Constraint ~int // illegal: ~int is not in a type parameter list
预声明的接口类型 comparable
表示所有可进行严格比较的非接口类型的集合 [Go 1.18]。
即使不是类型参数的接口是可比较的,它们也不是严格可比较的,因此它们不实现 comparable
。但是,它们满足 comparable
。
int // implements comparable (int is strictly comparable) []byte // does not implement comparable (slices cannot be compared) interface{} // does not implement comparable (see above) interface{ ~int | ~string } // type parameter only: implements comparable (int, string types are strictly comparable) interface{ comparable } // type parameter only: implements comparable (comparable implements itself) interface{ ~int | ~[]byte } // type parameter only: does not implement comparable (slices are not comparable) interface{ ~struct{ any } } // type parameter only: does not implement comparable (field any is not strictly comparable)
comparable
接口以及(直接或间接)嵌入 comparable
的接口只能用作类型约束。它们不能作为值或变量的类型,也不能作为其他非接口类型的组件。
满足类型约束
类型参数 T
满足 类型约束 C
,如果 T
是由 C
定义的类型集中的一个元素;即,如果 T
实现 C
。作为例外,严格可比较的类型约束也可以由可比较(不一定严格可比较)的类型参数满足 [Go 1.20]。更准确地说
类型 T 满足 约束 C
,如果
type argument type constraint // constraint satisfaction int interface{ ~int } // satisfied: int implements interface{ ~int } string comparable // satisfied: string implements comparable (string is strictly comparable) []byte comparable // not satisfied: slices are not comparable any interface{ comparable; int } // not satisfied: any does not implement interface{ int } any comparable // satisfied: any is comparable and implements the basic interface any struct{f any} comparable // satisfied: struct{f any} is comparable and implements the basic interface any any interface{ comparable; m() } // not satisfied: any does not implement the basic interface interface{ m() } interface{ m() } interface{ comparable; m() } // satisfied: interface{ m() } is comparable and implements the basic interface interface{ m() }
由于约束满足规则中的例外情况,比较类型参数类型的操作数可能会在运行时发生恐慌(即使可比较的类型参数始终是严格可比较的)。
变量声明
变量声明创建了一个或多个变量,将相应的标识符绑定到它们,并为每个变量赋予类型和初始值。
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 x, y, x := 1, 2, 3 // illegal: x repeated on left side of :=
简短变量声明只能出现在函数内部。在某些上下文中,例如"if"、"for"或"switch"语句的初始化程序,它们可以用来声明局部临时变量。
函数声明
函数声明将标识符(函数名)绑定到函数。
FunctionDecl = "func" FunctionName [ TypeParameters ] 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 }
如果函数声明指定了类型参数,则函数名表示一个泛型函数。泛型函数必须在被调用或用作值之前进行实例化。
func min[T ~int|~float64](x, y T) T { if x < y { return x } return y }
没有类型参数的函数声明可以省略函数体。此类声明提供了在 Go 外部实现的函数(例如汇编例程)的签名。
func flushICache(begin, end uintptr) // implemented externally
方法声明
方法是具有接收者的函数。方法声明将标识符(方法名)绑定到方法,并将方法与接收者的基础类型关联。
MethodDecl = "func" Receiver MethodName Signature [ FunctionBody ] . Receiver = Parameters .
接收者通过在方法名前面添加一个额外的参数部分来指定。该参数部分必须声明一个唯一的非变长参数,即接收者。其类型必须是已定义的类型 T
或指向已定义类型 T
的指针,后面可能跟着用方括号括起来的一系列类型参数名称 [P1, P2, …]
。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
。
如果接收者基础类型是泛型类型,则接收者规范必须为方法使用声明相应的类型参数。这使得接收者类型参数可供方法使用。在语法上,这种类型参数声明看起来像是接收者基础类型的实例化:类型参数必须是标识接收者基础类型每个类型参数的类型参数的标识符。类型参数名称不需要与其在接收者基础类型定义中的相应参数名称匹配,并且所有非空白参数名称在接收者参数部分和方法签名中都必须唯一。接收者类型参数约束由接收者基础类型定义隐含:相应的类型参数具有相应的约束。
type Pair[A, B any] struct { a A b B } func (p Pair[A, B]) Swap() Pair[B, A] { … } // receiver declares A, B func (p Pair[First, _]) First() First { … } // receiver declares First, corresponds to A in Pair
表达式
表达式通过将运算符和函数应用于操作数来指定值的计算。
操作数
操作数表示表达式中的基本值。操作数可以是字面量、(可能限定的)非空白标识符(表示常量、变量或函数),或者是一个带括号的表达式。
Operand = Literal | OperandName [ TypeArgs ] | "(" 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 [ TypeArgs ] . LiteralValue = "{" [ ElementList [ "," ] ] "}" . ElementList = KeyedElement { "," KeyedElement } . KeyedElement = [ Key ":" ] Element . Key = FieldName | Expression | LiteralValue . FieldName = identifier . Element = Expression | LiteralValue .
LiteralType的核心类型 T
必须是结构体、数组、切片或映射类型(语法强制执行此约束,除非类型作为 TypeName 给出)。元素和键的类型必须可分配到类型 T
的相应字段、元素和键类型;没有额外的转换。键被解释为结构体字面量的字段名称、数组和切片字面量的索引以及映射字面量的键。对于映射字面量,所有元素都必须具有键。指定具有相同字段名称或常量键值的多个元素是错误的。对于非常量映射键,请参阅有关求值顺序的部分。
对于结构体字面量,以下规则适用
- 键必须是在结构体类型中声明的字段名称。
- 不包含任何键的元素列表必须按字段声明的顺序为每个结构体字段列出元素。
- 如果任何元素具有键,则每个元素都必须具有键。
- 包含键的元素列表不需要为每个结构体字段都包含元素。省略的字段将获取该字段的零值。
- 字面量可以省略元素列表;此类字面量将计算为其类型的零值。
- 为属于不同包的结构体的非导出字段指定元素是错误的。
给定声明
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
- 如果字符串
a
也是常量,则常量索引必须在范围内 - 如果
x
在运行时超出范围,则会发生运行时恐慌 a[x]
是索引为x
的非常量字节值,并且a[x]
的类型为byte
- 不能为
a[x]
赋值
对于类型为映射类型 M
的 a
x
的类型必须能够赋值给M
的键类型- 如果映射包含键为
x
的条目,则a[x]
是键为x
的映射元素,并且a[x]
的类型是M
的元素类型 - 如果映射为
nil
或不包含此类条目,则a[x]
为M
的元素类型的零值
对于类型为类型参数类型的 P
的 a
- 索引表达式
a[x]
必须对P
的类型集中所有类型的取值有效。 P
的类型集中所有类型的元素类型必须相同。在此上下文中,字符串类型的元素类型为byte
。- 如果类型集中存在映射类型,则该类型集中的所有类型都必须是映射类型,并且各个键类型必须全部相同。
a[x]
是索引为x
的数组、切片或字符串元素,或使用P
实例化的类型参数的键为x
的映射元素,并且a[x]
的类型是(相同)元素类型的类型。- 如果
P
的类型集包含字符串类型,则不能为a[x]
赋值。
否则 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[low : high]
构造子字符串或切片。a
的核心类型必须是字符串、数组、指向数组的指针、切片或字节字符串。索引 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 var s []int s3 := s[:0] // s3 == nil
完整切片表达式
主要表达式
a[low : high : max]
构造一个与简单切片表达式 a[low : high]
类型相同,长度和元素也相同的切片。此外,它通过将其设置为 max - low
来控制结果切片的容量。只能省略第一个索引;它默认为 0。a
的核心类型必须是数组、指向数组的指针或切片(但不能是字符串)。在切片数组 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
如果 f
表示泛型函数,则必须在调用或用作函数值之前对其进行实例化。
在函数调用中,函数值和参数按通常顺序进行评估。评估完成后,调用的参数按值传递给函数,并开始执行被调用函数。函数返回时,函数的返回值按值传递回调用方。
调用 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
具有相同的值,并具有相同的底层数组。
实例化
泛型函数或类型通过将类型参数替换为类型参数来实例化 [Go 1.18]。实例化分两步进行
- 每个类型参数都被替换为泛型声明中对应的类型参数。此替换发生在整个函数或类型声明中,包括类型参数列表本身以及该列表中的任何类型。
- 替换后,每个类型参数都必须满足对应类型参数的约束(如果需要,则实例化)。否则实例化失败。
实例化类型会产生一个新的非泛型命名类型;实例化函数会产生一个新的非泛型函数。
type parameter list type arguments after substitution [P any] int int satisfies any [S ~[]E, E any] []int, int []int satisfies ~[]int, int satisfies any [P io.Writer] string illegal: string doesn't satisfy io.Writer [P comparable] any any satisfies (but does not implement) comparable
当使用泛型函数时,可以显式提供类型参数,也可以根据函数使用上下文部分或完全推断类型参数。只要可以推断,如果函数是
在所有其他情况下,必须存在(可能是部分的)类型参数列表。如果类型参数列表不存在或不完整,则所有缺少的类型参数必须能够从函数使用上下文推断得出。
// sum returns the sum (concatenation, for strings) of its arguments. func sum[T ~int | ~float64 | ~string](x... T) T { … } x := sum // illegal: the type of x is unknown intSum := sum[int] // intSum has type func(x... int) int a := intSum(2, 3) // a has value 5 of type int b := sum[float64](2.0, 3) // b has value 5.0 of type float64 c := sum(b, -1) // c has value 4.0 of type float64 type sumFunc func(x... string) string var f sumFunc = sum // same as var f sumFunc = sum[string] f = sum // same as f = sum[string]
部分类型参数列表不能为空;至少必须存在第一个参数。该列表是完整类型参数列表的前缀,其余参数留待推断。通俗地说,类型参数可以从“右到左”省略。
func apply[S ~[]E, E any](s S, f func(E) E) S { … } f0 := apply[] // illegal: type argument list cannot be empty f1 := apply[[]int] // type argument for S explicitly provided, type argument for E inferred f2 := apply[[]string, string] // both type arguments explicitly provided var bytes []byte r := apply(bytes, func(byte) byte { … }) // both type arguments inferred from the function arguments
对于泛型类型,始终必须显式提供所有类型参数。
类型推断
如果可以从函数使用所在的上下文(包括函数类型参数的约束)中推断出某些或所有类型参数,则泛型函数的使用可以省略这些参数。如果可以推断出缺少的类型参数并且实例化使用推断出的类型参数成功,则类型推断成功。否则,类型推断失败,程序无效。
类型推断使用类型对之间的类型关系进行推断:例如,函数参数必须可赋值给其相应的函数参数;这在参数类型和参数类型之间建立了一种关系。如果这两种类型中的任何一种都包含类型参数,则类型推断会查找用于替换类型参数的类型参数,以便满足可赋值关系。类似地,类型推断使用以下事实:类型参数必须满足其相应类型参数的约束。
每对匹配的类型对应于一个包含一个或多个类型参数的类型方程,这些类型参数来自一个或多个泛型函数。推断缺少的类型参数意味着求解所得类型方程组以获取相应的类型参数。
例如,给定
// dedup returns a copy of the argument slice with any duplicate entries removed. func dedup[S ~[]E, E comparable](S) S { … } type Slice []int var s Slice s = dedup(s) // same as s = dedup[Slice, int](s)
类型为Slice
的变量s
必须可赋值给函数参数类型S
,程序才能有效。为了降低复杂度,类型推断会忽略赋值的方向性,因此Slice
和S
之间的类型关系可以通过(对称)类型方程Slice ≡A S
(或S ≡A Slice
)来表示,其中≡A
中的A
表示LHS和RHS类型必须根据可赋值规则匹配(有关详细信息,请参阅类型统一部分)。类似地,类型参数S
必须满足其约束~[]E
。这可以表示为S ≡C ~[]E
,其中X ≡C Y
表示“X
满足约束Y
”。这些观察结果导致了一组两个方程
Slice ≡A S (1) S ≡C ~[]E (2)
现在可以针对类型参数S
和E
求解这些方程。从(1)中,编译器可以推断出S
的类型参数为Slice
。类似地,因为Slice
的基础类型为[]int
,并且[]int
必须与约束的[]E
匹配,所以编译器可以推断出E
必须为int
。因此,对于这两个方程,类型推断会推断出
S ➞ Slice E ➞ int
给定一组类型方程,要解决的类型参数是需要实例化的函数的类型参数,并且没有为其提供显式类型参数。这些类型参数称为绑定类型参数。例如,在上面的dedup
示例中,类型参数S
和E
绑定到dedup
。泛型函数调用的参数本身可能是一个泛型函数。该函数的类型参数包含在绑定类型参数集中。函数参数的类型可能包含来自其他函数(例如包含函数调用的泛型函数)的类型参数。这些类型参数也可能出现在类型方程中,但在此上下文中未绑定。类型方程始终仅针对绑定类型参数求解。
类型推断支持泛型函数的调用以及将泛型函数赋值给(显式函数类型)变量。这包括将泛型函数作为参数传递给其他(可能也是泛型)函数,以及将泛型函数作为结果返回。类型推断在一组特定于每种情况的方程上进行操作。方程如下(为简洁起见,省略了类型参数列表)
-
对于函数调用
f(a0, a1, …)
,其中f
或函数参数ai
是泛型函数
每对对应函数参数和参数(ai, pi)
(其中ai
不是无类型常量)都会生成一个方程typeof(pi) ≡A typeof(ai)
。
如果ai
是无类型常量cj
,并且typeof(pi)
是绑定类型参数Pk
,则将对(cj, Pk)
与类型方程分开收集。 -
对于将泛型函数
f
赋值给函数类型的(非泛型)变量v
的赋值v = f
typeof(v) ≡A typeof(f)
. -
对于返回语句
return …, f, …
,其中f
是作为结果返回给函数类型的(非泛型)结果变量r
的泛型函数
typeof(r) ≡A typeof(f)
.
此外,每个类型参数Pk
和相应的类型约束Ck
都会生成类型方程Pk ≡C Ck
。
类型推断优先考虑从类型化操作数获得的类型信息,然后再考虑无类型常量。因此,推断分两个阶段进行
-
使用类型统一针对绑定类型参数求解类型方程。如果统一失败,则类型推断失败。
-
对于每个尚未推断出类型参数且已为其收集了一个或多个对
(cj, Pk)
的绑定类型参数Pk
,请确定所有这些对中常量cj
的常量种类,方法与常量表达式相同。Pk
的类型参数是确定常量种类的默认类型。如果由于常量种类冲突而无法确定常量种类,则类型推断失败。
如果在这两个阶段之后仍未找到所有类型参数,则类型推断失败。
如果这两个阶段都成功,则类型推断会为每个绑定类型参数确定一个类型参数
Pk ➞ Ak
类型参数Ak
可能是复合类型,包含其他绑定类型参数Pk
作为元素类型(甚至可能只是另一个绑定类型参数)。在一个重复简化的过程中,每个类型参数中的绑定类型参数将被相应类型参数的类型参数替换,直到每个类型参数都不包含绑定类型参数。
如果类型参数通过绑定类型参数包含对自身的循环引用,则简化以及类型推断都会失败。否则,类型推断成功。
类型统一
类型推断通过类型统一来解决类型方程。类型统一递归地比较方程的LHS和RHS类型,其中任一类型或两种类型都可能为或包含绑定类型参数,并查找这些类型参数的类型参数,以便LHS和RHS匹配(变得相同或赋值兼容,具体取决于上下文)。为此,类型推断会维护一个从绑定类型参数到推断类型参数的映射;此映射在类型统一期间被查询和更新。最初,绑定类型参数是已知的,但映射为空。在类型统一期间,如果推断出新的类型参数A
,则将从类型参数到参数的相应映射P ➞ A
添加到映射中。相反,当比较类型时,已知类型参数(其映射条目已存在)将取代其对应的类型参数。随着类型推断的进行,映射会越来越多地填充,直到所有方程都被考虑过,或者直到统一失败。如果没有任何统一步骤失败并且映射对每个类型参数都有一个条目,则类型推断成功。
例如,给定包含绑定类型参数P
的类型方程
[10]struct{ elem P, list []P } ≡A [10]struct{ elem string; list []string }
类型推断以空映射开始。统一首先比较LHS和RHS类型的顶层结构。两者都是相同长度的数组;如果元素类型统一,则它们将统一。两种元素类型都是结构体;如果它们具有相同数量的字段且名称相同,并且如果字段类型统一,则它们将统一。P
的类型参数尚不清楚(没有映射条目),因此将P
与string
统一会将映射P ➞ string
添加到映射中。统一list
字段的类型需要统一[]P
和[]string
,因此需要统一P
和string
。由于此时已知P
的类型参数(P
存在映射条目),因此其类型参数string
将取代P
。并且由于string
与string
相同,因此此统一步骤也成功。现在已完成方程LHS和RHS的统一。类型推断成功,因为只有一个类型方程,没有统一步骤失败,并且映射已完全填充。
统一使用精确和宽松统一的组合,具体取决于两种类型是否必须相同、赋值兼容或仅结构相等。相应的类型统一规则在附录中详细说明。
对于X ≡A Y
形式的方程,其中X
和Y
是参与赋值的类型(包括参数传递和返回语句),顶层类型结构可以宽松统一,但元素类型必须精确统一,匹配赋值规则。
对于P ≡C C
形式的方程,其中P
是类型参数,C
是其相应的约束,统一规则会更复杂一些
- 如果
C
具有核心类型core(C)
,并且P
具有已知类型参数A
,则core(C)
和A
必须宽松统一。如果P
没有已知类型参数,并且C
恰好包含一个不是基础(波浪线)类型的类型项T
,则统一会将映射P ➞ T
添加到映射中。 - 如果
C
没有核心类型,并且P
具有已知类型参数A
,则A
必须具有C
的所有方法(如果有),并且相应的方法类型必须精确统一。
在求解来自类型约束的类型方程时,求解一个方程可能会推断出其他类型参数,这反过来可能会使求解依赖于这些类型参数的其他方程成为可能。只要推断出新的类型参数,类型推断就会重复类型统一。
运算符
运算符将操作数组合成表达式。
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 = "+" | "-" | "!" | "^" | "*" | "&" | "<-" .
比较运算符在其他地方讨论。对于其他二元运算符,除非操作涉及移位或无类型常量,否则操作数类型必须相同。对于仅涉及常量的运算,请参阅有关常量表达式的部分。
除了移位运算之外,如果一个操作数是无类型常量,而另一个操作数不是,则该常量将隐式转换为另一个操作数的类型。
移位表达式中的右操作数必须具有整数类型 [Go 1.13] 或为可由类型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 v1 float32 = 1<<s // illegal: 1 has type float32, cannot shift var v2 = string(1<<s) // illegal: 1 is converted to a string, 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 // x 42 + a - b // (42 + a) - b 23 + 3*x[i] // 23 + (3 * x[i]) x <= f() // x <= f() ^a >> b // (^a) >> b f() || g() // f() || g() x == y+1 && <-chanInt > 0 // (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
如果操作数类型是类型参数,则运算符必须应用于该类型集中的每个类型。操作数表示为类型参数实例化的类型参数的类型参数的值,并且操作以该类型参数的精度计算。例如,给定函数
func dotProduct[F ~float32|~float64](v1, v2 []F) F { var s F for i, x := range v1 { y := v2[i] s += x * y } return s }
乘积x * y
和加法s += x * y
分别以float32
或float64
精度计算,具体取决于F
的类型参数。
整数运算符
对于两个整数值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
将常量转换为类型参数会生成该类型的非常量值,该值表示为类型参数实例化所使用的类型参数的值。例如,给定函数
func f[P ~float32|~float64]() { … P(1.1) … }
转换P(1.1)
会生成类型为P
的非常量值,并且值1.1
表示为float32
或float64
,具体取决于f
的类型参数。因此,如果f
使用float32
类型实例化,则表达式P(1.1) + 1.2
的数值将使用与相应的非常量float32
加法相同的精度进行计算。
非常量值x
可以在以下任何情况下转换为类型T
-
x
可以赋值给T
。 - 忽略结构体标签(见下文),
x
的类型和T
不是类型参数,但具有相同的底层类型。 - 忽略结构体标签(见下文),
x
的类型和T
是指针类型,而不是命名类型,并且它们指针的基本类型不是类型参数,但具有相同的底层类型。 -
x
的类型和T
都是整数或浮点类型。 -
x
的类型和T
都是复数类型。 -
x
是整数或字节或字符的切片,而T
是字符串类型。 -
x
是字符串,而T
是字节或字符的切片。 -
x
是切片,T
是数组[Go 1.20]或指向数组的指针[Go 1.17],并且切片和数组类型具有相同的元素类型。
此外,如果T
或x
的类型V
是类型参数,则如果以下条件之一适用,x
也可以转换为类型T
V
和T
都是类型参数,并且V
的类型集中的每个类型的值都可以转换为T
的类型集中的每个类型。- 只有
V
是类型参数,并且V
的类型集中的每个类型的值都可以转换为T
。 - 只有
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)
不会。
在所有涉及浮点或复数值的非常量转换中,如果结果类型无法表示该值,则转换会成功,但结果值取决于实现。
与字符串类型的转换
- 将字节切片转换为字符串类型会生成一个字符串,其连续字节是切片的元素。
string([]byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}) // "hellø" string([]byte{}) // "" string([]byte(nil)) // "" type bytes []byte string(bytes{'h', 'e', 'l', 'l', '\xc3', '\xb8'}) // "hellø" type myByte byte string([]myByte{'w', 'o', 'r', 'l', 'd', '!'}) // "world!" myString([]myByte{'\xf0', '\x9f', '\x8c', '\x8d'}) // "🌍"
- 将字符切片转换为字符串类型会生成一个字符串,该字符串是将各个字符值转换为字符串后的串联结果。
string([]rune{0x767d, 0x9d6c, 0x7fd4}) // "\u767d\u9d6c\u7fd4" == "白鵬翔" string([]rune{}) // "" string([]rune(nil)) // "" type runes []rune string(runes{0x767d, 0x9d6c, 0x7fd4}) // "\u767d\u9d6c\u7fd4" == "白鵬翔" type myRune rune string([]myRune{0x266b, 0x266c}) // "\u266b\u266c" == "♫♬" myString([]myRune{0x1f30e}) // "\U0001f30e" == "🌎"
- 将字符串类型的值转换为字节切片类型会生成一个非空切片,其连续元素是字符串的字节。
[]byte("hellø") // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'} []byte("") // []byte{} bytes("hellø") // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'} []myByte("world!") // []myByte{'w', 'o', 'r', 'l', 'd', '!'} []myByte(myString("🌏")) // []myByte{'\xf0', '\x9f', '\x8c', '\x8f'}
- 将字符串类型的值转换为字符切片类型会生成一个包含字符串的各个 Unicode 代码点的切片。
[]rune(myString("白鵬翔")) // []rune{0x767d, 0x9d6c, 0x7fd4} []rune("") // []rune{} runes("白鵬翔") // []rune{0x767d, 0x9d6c, 0x7fd4} []myRune("♫♬") // []myRune{0x266b, 0x266c} []myRune(myString("🌐")) // []myRune{0x1f310}
- 最后,出于历史原因,可以将整数值转换为字符串类型。这种形式的转换会生成一个字符串,该字符串包含具有给定整数值的 Unicode 代码点的(可能是多字节的)UTF-8 表示形式。超出有效 Unicode 代码点范围的值将转换为
"\uFFFD"
。string('a') // "a" string(65) // "A" string('\xf8') // "\u00f8" == "ø" == "\xc3\xb8" string(-1) // "\ufffd" == "\xef\xbf\xbd" type myString string myString('\u65e5') // "\u65e5" == "日" == "\xe6\x97\xa5"
注意:这种形式的转换最终可能会从语言中删除。go vet
工具将某些整数到字符串的转换标记为潜在错误。应改为使用utf8.AppendRune
或utf8.EncodeRune
等库函数。
从切片到数组或数组指针的转换
将切片转换为数组会生成一个包含切片底层数组元素的数组。类似地,将切片转换为数组指针会生成一个指向切片底层数组的指针。在这两种情况下,如果切片的长度小于数组的长度,则会发生运行时恐慌。
s := make([]byte, 2, 4) a0 := [0]byte(s) a1 := [1]byte(s[1:]) // a1[0] == s[1] a2 := [2]byte(s) // a2[0] == s[0] a4 := [4]byte(s) // panics: len([4]byte) > len(s) 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) // ok for nil slice t t1 := (*[0]string)(t) // t1 == nil t2 := (*[1]string)(t) // panics: len([1]string) > len(t) u := make([]byte, 0) u0 := (*[0]byte)(u) // u0 != nil
常量表达式
常量表达式只能包含常量操作数,并在编译时进行计算。
未类型化的布尔、数字和字符串常量可作为操作数使用,只要使用布尔、数字或字符串类型操作数是合法的即可。
常量比较始终生成未类型化的布尔常量。如果常量移位表达式的左操作数是未类型化的常量,则结果是整数常量;否则,结果是与左操作数类型相同的常量,该常量必须是整数类型。
对未类型化常量的任何其他运算都会导致相同种类的未类型化常量;即布尔、整数、浮点、复数或字符串常量。如果二元运算(移位运算除外)的未类型化操作数种类不同,则结果为操作数中在此列表中出现的较晚的种类:整数、字符、浮点、复数。例如,未类型化整数常量除以未类型化复数常量会产生未类型化复数常量。
const a = 2 + 3.0 // a == 5.0 (untyped floating-point constant) const b = 15 / 4 // b == 3 (untyped integer constant) const c = 15 / 4.0 // c == 3.75 (untyped floating-point constant) const Θ float64 = 3/2 // Θ == 1.0 (type float64, 3/2 is integer division) const Π float64 = 3/2. // Π == 1.5 (type float64, 3/2. is float division) const d = 1 << 3.0 // d == 8 (untyped integer constant) const e = 1.0 << 3 // e == 8 (untyped integer constant) const f = int32(1) << 33 // illegal (constant 8589934592 overflows int32) const g = float64(2) >> 1 // illegal (float64(2) is a typed floating-point constant) const h = "foo" > "bar" // h == true (untyped boolean constant) const j = true // j == true (untyped boolean constant) const k = 'w' + 1 // k == 'x' (untyped rune constant) const l = "hi" // l == "hi" (untyped string constant) const m = string(k) // m == "x" (type string) const Σ = 1 - 0.707i // (untyped complex constant) const Δ = Σ + 2.0e-4 // (untyped complex constant) const Φ = iota*1i - 1/1i // (untyped complex constant)
将内置函数complex
应用于未类型化的整数、字符或浮点常量会生成未类型化的复数常量。
const ic = complex(0, c) // ic == 3.75i (untyped complex constant) const 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(z || h(), i()+x[j()], <-c), k()
函数调用和通信按照以下顺序发生:f()
、h()
(如果z
计算结果为假)、i()
、j()
、<-c
、g()
和k()
。但是,这些事件相对于x
的评估和索引以及y
和z
的评估的顺序未指定,除非在词法上需要。例如,在g
的参数被评估之前,它不能被调用。
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 unsafe.SliceData unsafe.String unsafe.StringData
h(x+y) f.Close() <-ch (<-ch) len("foo") // illegal if len is the built-in function
发送语句
发送语句在通道上发送值。通道表达式的核心类型必须是通道,通道方向必须允许发送操作,并且要发送的值的类型必须可分配给通道的元素类型。
SendStmt = Channel "<-" Expression . Channel = Expression .
通道和值表达式都在通信开始之前进行评估。通信会阻塞,直到发送可以继续。如果接收方已准备好,则在未缓冲的通道上发送可以继续。如果缓冲区中有空间,则在缓冲的通道上发送可以继续。在已关闭的通道上发送会通过导致运行时恐慌来继续。在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 is []int{3, 5, 3}
在赋值中,每个值都必须可分配到其被赋给的操作数的类型,并具有以下特殊情况
If 语句
"If"语句根据布尔表达式的值指定两个分支的条件执行。如果表达式的计算结果为真,则执行"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 .
有两种形式:表达式开关和类型开关。在表达式开关中,情况包含与开关表达式的值进行比较的表达式。在类型开关中,情况包含与特殊注释的开关表达式的类型进行比较的类型。开关表达式在开关语句中恰好评估一次。
表达式开关
在表达式开关中,开关表达式被评估,并且情况表达式(不需要是常量)从左到右、从上到下进行评估;第一个等于开关表达式的表达式将触发关联情况语句的执行;其他情况将被跳过。如果没有情况匹配并且存在"default"情况,则执行其语句。最多只能有一个默认情况,它可以出现在"switch"语句中的任何位置。缺少的开关表达式等效于布尔值true
。
ExprSwitchStmt = "switch" [ SimpleStmt ";" ] [ Expression ] "{" { ExprCaseClause } "}" . ExprCaseClause = ExprSwitchCase ":" StatementList . ExprSwitchCase = "case" ExpressionList | "default" .
如果开关表达式的计算结果为无类型常量,则它首先隐式转换为其默认类型。预声明的无类型值nil
不能用作开关表达式。开关表达式的类型必须可比较。
如果情况表达式是无类型的,则它首先隐式转换为开关表达式的类型。对于每个(可能已转换的)情况表达式x
和开关表达式的值t
,x == t
必须是有效的比较。
换句话说,开关表达式被视为用于声明和初始化临时变量t
而没有显式类型;正是针对该t
的值,每个情况表达式x
都进行相等性测试。
在情况或默认子句中,最后一个非空语句可能是(可能带标签的)"fallthrough"语句,以指示控制应该从该子句的末尾流向下一个子句的第一个语句。否则,控制流向"switch"语句的末尾。除了表达式开关的最后一个子句外,"fallthrough"语句可以出现在所有其他子句的最后一个语句中。
开关表达式前面可以是一个简单语句,该语句在表达式被评估之前执行。
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() }
实现限制:编译器可能会禁止多个情况表达式计算为同一个常量。例如,当前编译器不允许在情况表达式中使用重复的整数、浮点数或字符串常量。
类型开关
类型开关比较类型而不是值。否则,它类似于表达式开关。它由一个特殊开关表达式标记,该表达式的形式为使用关键字type
而不是实际类型的类型断言
switch x.(type) { // cases }
然后,case 将实际类型 T
与表达式 x
的动态类型进行匹配。与类型断言一样,x
必须是 接口类型,但不能是 类型参数,并且 case 中列出的每个非接口类型 T
都必须实现 x
的类型。类型开关的 case 中列出的类型必须全部不同。
TypeSwitchStmt = "switch" [ SimpleStmt ";" ] TypeSwitchGuard "{" { TypeCaseClause } "}" . TypeSwitchGuard = [ identifier ":=" ] PrimaryExpr "." "(" "type" ")" . TypeCaseClause = TypeSwitchCase ":" StatementList . TypeSwitchCase = "case" TypeList | "default" .
TypeSwitchGuard 可以包含一个 简短变量声明。当使用这种形式时,变量在每个子句的 隐式块 中的 TypeSwitchCase 末尾声明。在 case 列表中只包含一个类型的子句中,变量具有该类型;否则,变量具有 TypeSwitchGuard 中表达式的类型。
除了类型之外,case 可以使用预声明的标识符 nil
;当 TypeSwitchGuard 中的表达式为 nil
接口值时,将选择该 case。最多只能有一个 nil
case。
给定一个类型为 interface{}
的表达式 x
,以下类型开关
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") } }
类型参数或泛型类型可以用作 case 中的类型。如果在实例化时,该类型与开关中的另一个条目重复,则选择第一个匹配的 case。
func f[P any](x any) int { switch x.(type) { case P: return 0 case string: return 1 case []P: return 2 case []byte: return 3 default: return 4 } } var v1 = f[string]("foo") // v1 == 0 var v2 = f[byte]([]byte{}) // v2 == 2
类型开关保护语句之前可以是一个简单的语句,该语句会在评估保护语句之前执行。
类型开关中不允许使用“fallthrough”语句。
For 语句
“for”语句指定重复执行一个块。有三种形式:迭代可以通过单个条件、“for”子句或“range”子句控制。
ForStmt = "for" [ Condition | ForClause | RangeClause ] Block . Condition = Expression .
带单个条件的 For 语句
在最简单的形式中,“for”语句指定重复执行一个块,只要布尔条件计算结果为真。每次迭代之前都会评估条件。如果条件不存在,则等效于布尔值 true
。
for a < b { a *= 2 }
带 for
子句的 For 语句
带 ForClause 的“for”语句也受其条件控制,但此外它还可以指定一个init语句和一个post语句,例如赋值语句、增量或减量语句。init 语句可以是 简短变量声明,但 post 语句不能是。
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() }
每次迭代都有其自己的单独声明的变量(或变量)[Go 1.22]。第一次迭代使用的变量由 init 语句声明。每次后续迭代使用的变量在执行 post 语句之前隐式声明,并初始化为该时刻前一次迭代变量的值。
var prints []func() for i := 0; i < 5; i++ { prints = append(prints, func() { println(i) }) i++ } for _, p := range prints { p() }
打印
1 3 5
在 [Go 1.22] 之前,迭代共享一组变量,而不是拥有自己的单独变量。在这种情况下,上面的示例打印
6 6 6
带 range
子句的 For 语句
带“range”子句的“for”语句迭代数组、切片、字符串或映射的所有条目、在通道上接收的值、从零到上限的整数值 [Go 1.22],或传递给迭代器函数的 yield 函数的值 [Go 1.23]。对于每个条目,它将迭代值分配给相应的迭代变量(如果存在),然后执行块。
RangeClause = [ ExpressionList "=" | IdentifierList ":=" ] "range" Expression .
“range”子句右侧的表达式称为range 表达式,其 核心类型必须是数组、指向数组的指针、切片、字符串、映射、允许接收操作的通道、整数或具有特定签名的函数(见下文)。与赋值一样,如果存在,左侧的操作数必须是可寻址的或映射索引表达式;它们表示迭代变量。如果 range 表达式是函数,则迭代变量的最大数量取决于函数签名。如果 range 表达式是通道或整数,则最多允许一个迭代变量;否则最多可以有两个。如果最后一个迭代变量是空白标识符,则 range 子句等效于没有该标识符的相同子句。
range 表达式 x
在循环开始前进行评估,但有一个例外:如果最多存在一个迭代变量并且 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 integer value n integer type, or untyped int value i see below function, 0 values f func(func() bool) function, 1 value f func(func(V) bool) value v V function, 2 values f func(func(K, V) bool) key k K v V
- 对于数组、指向数组的指针或切片值
a
,索引迭代值按升序生成,从元素索引 0 开始。如果最多存在一个迭代变量,则 range 循环会生成从 0 到len(a)-1
的迭代值,并且不会索引到数组或切片本身。对于nil
切片,迭代次数为 0。 - 对于字符串值,“range”子句迭代字符串中从字节索引 0 开始的 Unicode 代码点。在连续的迭代中,索引值将是字符串中连续 UTF-8 编码代码点的第一个字节的索引,第二个值(类型为
rune
)将是相应代码点的值。如果迭代遇到无效的 UTF-8 序列,则第二个值将为0xFFFD
(Unicode 替换字符),并且下一次迭代将在字符串中前进一个字节。 - 映射上的迭代顺序未指定,并且不保证从一次迭代到下一次迭代是相同的。如果在迭代期间删除了尚未到达的映射条目,则不会生成相应的迭代值。如果在迭代期间创建了映射条目,则该条目可能会在迭代期间生成或可能会跳过。对于创建的每个条目以及从一次迭代到下一次迭代,选择可能会有所不同。如果映射为
nil
,则迭代次数为 0。 - 对于通道,生成的迭代值是在通道上发送的连续值,直到通道关闭。如果通道为
nil
,则 range 表达式将永远阻塞。 - 对于整数值
n
,其中n
是整数类型或未类型化的整数常量,将按升序生成 0 到n-1
的迭代值。如果n
是整数类型,则迭代值具有相同的类型。否则,n
的类型将被确定,就好像它被分配给迭代变量一样。具体来说:如果迭代变量是预先存在的,则迭代值的类型是迭代变量的类型,该类型必须是整数类型。否则,如果迭代变量由“range”子句声明或不存在,则迭代值的类型是n
的默认类型。如果n
<= 0,则循环不会运行任何迭代。 - 对于函数
f
,迭代通过调用f
并将其作为参数传递一个新的合成yield
函数来进行。如果在f
返回之前调用了yield
,则yield
的参数将成为执行循环体一次的迭代值。在每次连续的循环迭代之后,yield
返回 true,并且可以再次调用以继续循环。只要循环体不终止,“range”子句将继续以这种方式为每次yield
调用生成迭代值,直到f
返回。如果循环体终止(例如通过break
语句),则yield
返回 false,并且不得再次调用。
迭代变量可以使用 简短变量声明(:=
)的形式由“range”子句声明。在这种情况下,它们的作用域是“for”语句的块,并且每次迭代都有其自己的新变量 [Go 1.22](另请参见带 ForClause 的“for”语句)。变量具有其各自迭代值的类型。
如果迭代变量不是由“range”子句显式声明的,则它们必须是预先存在的。在这种情况下,迭代值将分配给相应的变量,就像在赋值语句中一样。
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 {} // call f(0), f(1), ... f(9) for i := range 10 { // type of i is int (default type for untyped constant 10) f(i) } // invalid: 256 cannot be assigned to uint8 var u uint8 for u = range 256 { } // invalid: 1e3 is a floating-point constant for range 1e3 { } // fibo generates the Fibonacci sequence fibo := func(yield func(x int) bool) { f0, f1 := 0, 1 for yield(f0) { f0, f1 = f1, f0+f1 } } // print the Fibonacci numbers below 1000: for x := range fibo { if x >= 1000 { break } fmt.Printf("%d ", x) } // output: 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 // iteration support for a recursive tree data structure type Tree[K cmp.Ordered, V any] struct { left, right *Tree[K, V] key K value V } func (t *Tree[K, V]) walk(yield func(key K, val V) bool) bool { return t == nil || t.left.walk(yield) && yield(t.key, t.value) && t.right.walk(yield) } func (t *Tree[K, V]) Walk(yield func(key K, val V) bool) { t.walk(yield) } // walk tree t in-order var t Tree[string, int] for k, v := range t.Walk { // process k, v }
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”语句的执行分几个步骤进行
- 对于语句中的所有 case,接收操作的通道操作数以及发送语句的通道和右侧表达式都会在进入“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
延迟的任何函数(延迟函数)在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”语句终止同一函数内最内层“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”语句通过将控制权推进到循环块的末尾来开始最内层封闭“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”语句将控制权转移到同一函数内具有相应标签的语句。
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”语句将控制权转移到表达式“switch”语句中下一个 case 子句的第一个语句。它只能用作此类子句中最后一个非空语句。
FallthroughStmt = "fallthrough" .
延迟语句
“defer”语句调用一个函数,该函数的执行被延迟到周围函数返回的那一刻,无论是由于周围函数执行了return语句,到达了其函数体的末尾,还是由于相应的goroutine正在恐慌。
DeferStmt = "defer" Expression .
表达式必须是函数或方法调用;它不能用括号括起来。内置函数的调用与表达式语句一样受到限制。
每次“defer”语句执行时,对调用的函数值和参数都按通常方式进行计算并重新保存,但不会实际调用该函数。相反,延迟函数在周围函数返回之前立即调用,调用顺序与延迟顺序相反。也就是说,如果周围函数通过显式的return语句返回,则延迟函数在该return语句设置任何结果参数之后但在函数返回到其调用者之前执行。如果延迟函数值计算结果为nil
,则在调用该函数时(而非执行“defer”语句时)执行将恐慌。
例如,如果延迟函数是函数字面量,并且周围函数具有命名结果参数,这些参数在字面量内处于作用域内,则延迟函数可以在返回这些结果参数之前访问和修改它们。如果延迟函数有任何返回值,则在函数完成时将丢弃这些返回值。(另请参阅有关处理恐慌的部分)。
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类型,因此它们只能出现在调用表达式中;它们不能用作函数值。
追加到切片和复制切片
内置函数append
和copy
有助于常见的切片操作。对于这两个函数,结果与参数引用的内存是否重叠无关。
可变参数函数append
将零个或多个值x
追加到切片s
,并返回与s
类型相同的切片。s
的核心类型必须是类型为[]E
的切片。值x
传递给类型为...E
的参数,并应用相应的参数传递规则。作为特殊情况,如果s
的核心类型为[]byte
,则append
还接受第二个参数,其核心类型为bytestring
,后跟...
。此表单将字节切片或字符串的字节追加进去。
append(s S, x ...E) S // core type of S is []E
如果s
的容量不足以容纳其他值,则append
将分配一个新的、足够大的底层数组,以容纳现有的切片元素和其他值。否则,append
将重用底层数组。
s0 := []int{0, 0} s1 := append(s0, 2) // append a single element s1 is []int{0, 0, 2} s2 := append(s1, 3, 5, 7) // append multiple elements s2 is []int{0, 0, 2, 3, 5, 7} s3 := append(s2, s0...) // append a slice s3 is []int{0, 0, 2, 3, 5, 7, 0, 0} s4 := append(s3[3:6], s3[2:]...) // append overlapping slice s4 is []int{3, 5, 7, 2, 3, 5, 7, 0, 0} var t []interface{} t = append(t, 42, 3.1415, "foo") // t is []interface{}{42, 3.1415, "foo"} var b []byte b = append(b, "bar"...) // append string contents b is []byte{'b', 'a', 'r' }
函数copy
将切片元素从源src
复制到目标dst
,并返回复制的元素数。两个参数的核心类型必须是具有相同元素类型的切片。复制的元素数为len(src)
和len(dst)
的最小值。作为特殊情况,如果目标的核心类型为[]byte
,则copy
还接受一个源参数,其核心类型为bytestring
。此表单将字节切片或字符串中的字节复制到字节切片中。
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 is []int{0, 1, 2, 3, 4, 5} n2 := copy(s, s[2:]) // n2 == 4, s is []int{2, 3, 4, 5, 4, 5} n3 := copy(b, "Hello, World!") // n3 == 5, b is []byte("Hello")
清除
内置函数clear
采用map、切片或类型参数类型的参数,并删除或清零所有元素[Go 1.21]。
Call Argument type Result
clear(m) map[K]T deletes all entries, resulting in an
empty map (len(m) == 0)
clear(s) []T sets all elements up to the length of
s
to the zero value of T
clear(t) type parameter see below
如果clear
的参数类型是类型参数,则其类型集中的所有类型都必须是map或切片,并且clear
执行与实际类型参数相对应的操作。
如果map或切片为nil
,则clear
为无操作。
关闭
对于具有核心类型为通道的参数ch
,内置函数close
记录不再向通道发送值。如果ch
是只接收通道,则这是一个错误。向已关闭的通道发送数据或关闭已关闭的通道会导致运行时恐慌。关闭nil通道也会导致运行时恐慌。在调用close
之后,以及在所有先前发送的值都被接收之后,接收操作将返回通道类型的零值,而不会阻塞。多值接收操作返回接收到的值以及通道是否已关闭的指示。
操作复数
三个函数组装和分解复数。内置函数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
不允许使用类型参数类型的参数。
删除map元素
内置函数delete
从mapm
中删除键为k
的元素。值k
必须可赋值到m
的键类型。
delete(m, k) // remove element m[k] from map m
如果m
的类型是类型参数,则该类型集中的所有类型都必须是map,并且它们都必须具有相同的键类型。
如果map m
为nil
或元素m[k]
不存在,则delete
为无操作。
长度和容量
内置函数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 type parameter see below cap(s) [n]T, *[n]T array length (== n) []T slice capacity chan T channel buffer capacity type parameter see below
如果参数类型是类型参数P
,则调用len(e)
(或分别为cap(e)
)对于P
的类型集中的每个类型都必须有效。结果是参数的长度(或容量),其类型对应于用于实例化P
的类型参数。
切片的容量是指底层数组中已分配空间的元素数量。在任何时候,以下关系都成立
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
创建切片、映射和通道
内置函数 make
接受类型 T
,后跟一个可选的类型特定的表达式列表。T
的核心类型必须是切片、映射或通道。它返回类型为 T
的值(而不是 *T
)。内存初始化方式如初始值部分所述。
Call Core type 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
,则会发生运行时恐慌。
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
个映射元素。精确的行为取决于实现。
最小值和最大值
内置函数 min
和 max
分别计算固定数量的有序类型参数的最小值或最大值。必须至少有一个参数 [Go 1.21]。
与运算符相同的类型规则适用:对于有序参数 x
和 y
,如果 x + y
有效,则 min(x, y)
有效,并且 min(x, y)
的类型为 x + y
的类型(max
类似)。如果所有参数都是常量,则结果为常量。
var x, y int m := min(x) // m == x m := min(x, y) // m is the smaller of x and y m := max(x, y, 10) // m is the larger of x and y but at least 10 c := max(1, 2.0, 10) // c == 10.0 (floating-point kind) f := max(0, float32(x)) // type of f is float32 var s []string _ = min(s...) // invalid: slice arguments are not permitted t := max("", "foo", "bar") // t == "foo" (string kind)
对于数值参数,假设所有 NaN 相等,则 min
和 max
是可交换的和关联的。
min(x, y) == min(y, x) min(x, y, z) == min(min(x, y), z) == min(x, min(y, z))
对于浮点参数负零、NaN 和无穷大,适用以下规则。
x y min(x, y) max(x, y) -0.0 0.0 -0.0 0.0 // negative zero is smaller than (non-negative) zero -Inf y -Inf y // negative infinity is smaller than any other number +Inf y y +Inf // positive infinity is larger than any other number NaN y NaN NaN // if any argument is a NaN, the result is a NaN
对于字符串参数,min
的结果是具有最小值(或对于 max
,最大值)的第一个参数,按字节逐个词法比较。
min(x, y) == if x <= y then x else y min(x, y, z) == min(min(x, y), z)
内存分配
内置函数 new
接受类型 T
,在运行时为该类型的变量分配存储空间,并返回类型为 *T
的值,该值指向它。变量初始化方式如初始值部分所述。
new(T)
例如
type S struct { a int; b float64 } new(S)
为类型 S
的变量分配存储空间,对其进行初始化(a=0
,b=0.0
),并返回类型为 *S
的值,其中包含该位置的地址。
处理恐慌
两个内置函数 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
正常返回,而没有启动新的 panic
,则恐慌序列停止。在这种情况下,G
和对 panic
的调用之间调用的函数的状态将被丢弃,并且正常执行将恢复。然后运行 G
之前延迟的任何函数,G
的执行通过返回到其调用者而终止。
当 goroutine 未发生恐慌或 recover
未由延迟函数直接调用时,recover
的返回值为 nil
。相反,如果 goroutine 发生恐慌并且 recover
由延迟函数直接调用,则 recover
的返回值保证不为 nil
。为了确保这一点,使用 nil
接口值(或不定类型 nil
)调用 panic
会导致运行时恐慌。
下面示例中的 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
。当该函数调用返回时,程序退出。它不会等待其他(非main
)goroutine 完成。
错误
预声明类型error
定义为
type error interface { Error() string }
它是表示错误条件的约定接口,其中nil值表示没有错误。例如,从文件读取数据的函数可以定义为
func Read(f *File, b []byte) (n int, err error)
运行时恐慌
诸如尝试索引数组越界之类的执行错误会触发运行时恐慌,相当于调用内置函数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 func SliceData(slice []ArbitraryType) *ArbitraryType func String(ptr *byte, len IntegerType) string func StringData(str string) *byte
Pointer
是一种指针类型,但Pointer
值不能解引用。任何指针或核心类型uintptr
的值都可以转换为核心类型Pointer
的类型,反之亦然。在Pointer
和uintptr
之间转换的效果是实现定义的。
var f float64 bits = *(*uint64)(unsafe.Pointer(&f)) type ptr unsafe.Pointer bits = *(*uint64)(ptr(&f)) func f[P ~*B, B any](p P) uintptr { return uintptr(unsafe.Pointer(p)) } var p ptr = nil
函数Alignof
和Sizeof
接受任何类型的表达式x
,并分别返回假设变量v
的对齐方式或大小,就像通过var v = x
声明v
一样。
函数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
如果T
是类型参数,或者它是包含可变大小元素或字段的数组或结构体类型,则类型T
(的变量)具有可变大小。否则,大小为常量。如果Alignof
、Offsetof
和Sizeof
的参数(或Offsetof
的选择器表达式s.f
中的结构体s
)是常量大小的类型,则对Alignof
、Offsetof
和Sizeof
的调用是类型为uintptr
的编译时常量表达式。
函数Add
将len
添加到ptr
并返回更新后的指针unsafe.Pointer(uintptr(ptr) + uintptr(len))
[Go 1.17]。len
参数必须是整数类型或未类型化的常量。常量len
参数必须可由类型int
的值表示;如果它是未类型化的常量,则其类型为int
。Pointer
的有效用法规则仍然适用。
函数Slice
返回一个切片,其底层数组从ptr
开始,其长度和容量为len
。Slice(ptr, len)
等效于
(*[len]ArbitraryType)(unsafe.Pointer(ptr))[:]
但作为特例,如果ptr
为nil
且len
为零,则Slice
返回nil
[Go 1.17]。
len
参数必须是整数类型或未类型化的常量。常量len
参数必须是非负数,并且可由类型int
的值表示;如果它是未类型化的常量,则其类型为int
。在运行时,如果len
为负数,或者如果ptr
为nil
且len
不为零,则会发生运行时恐慌 [Go 1.17]。
函数SliceData
返回指向slice
参数的底层数组的指针。如果切片的容量cap(slice)
不为零,则该指针为&slice[:1][0]
。如果slice
为nil
,则结果为nil
。否则,它是一个指向未指定内存地址的非nil
指针 [Go 1.20]。
函数String
返回一个string
值,其底层字节从ptr
开始,其长度为len
。ptr
和len
参数与函数Slice
中的相同要求。如果len
为零,则结果为空字符串""
。由于 Go 字符串是不可变的,因此传递给String
的字节之后不得修改。[Go 1.20]
函数StringData
返回指向str
参数的底层字节的指针。对于空字符串,返回值未指定,并且可能是nil
。由于 Go 字符串是不可变的,因此返回的字节StringData
不得修改 [Go 1.20]。
大小和对齐保证
对于数值类型,保证以下大小
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)
与数组元素类型的变量的对齐方式相同。
如果结构体或数组类型不包含大小大于零的字段(或元素),则其大小为零。两个不同的零大小变量可能在内存中具有相同的地址。
附录
语言版本
Go 1 兼容性保证确保根据 Go 1 规范编写的程序将在该规范的整个生命周期内继续编译并正确运行,无需更改。更一般地说,随着对语言进行调整和添加功能,兼容性保证确保一个与特定 Go 语言版本一起使用的 Go 程序将继续与任何后续版本一起使用。
例如,使用前缀0b
表示二进制整数字面量的能力是在 Go 1.13 中引入的,由[Go 1.13]在关于整数字面量的部分中指示。如果编译器使用的隐含或所需语言版本早于 Go 1.13,则包含诸如0b1011
之类的整数字面量的源代码将被拒绝。
下表描述了 Go 1 之后引入的功能所需的最低语言版本。
Go 1.9
- 可以使用别名声明为类型声明别名。
Go 1.13
-
整数字面量 可以使用前缀
0b
、0B
、0o
和0O
分别表示二进制和八进制字面量。 - 十六进制浮点数字面量 可以使用前缀
0x
和0X
来编写。 - 虚数后缀
i
可以用于任何(二进制、十进制、十六进制)整数或浮点数字面量,而不仅仅是十进制字面量。 - 任何数字字面量的数字都可以使用下划线
_
进行分隔(分组)。 - 在移位操作中的移位计数可以是带符号的整数类型。
Go 1.14
- 通过不同的嵌入接口多次嵌入同一个方法不是错误。
Go 1.17
Go 1.18
1.18 版本为语言添加了多态函数和类型(“泛型”)。具体来说
- 运算符和标点符号集包括新的标记
~
。 - 函数和类型声明可以声明类型参数。
- 接口类型可以嵌入任意类型(不仅仅是接口的类型名称),以及联合和
~T
类型元素。 - 预声明的类型集包括新的类型
any
和comparable
。
Go 1.20
- 如果切片和数组元素类型匹配,并且数组长度不超过切片长度,则可以将切片转换为数组。
- 内置的
unsafe
包 包含新的函数SliceData
、String
和StringData
。 -
可比较类型(例如普通接口)可以满足
comparable
约束,即使类型参数不是严格可比较的。
Go 1.21
Go 1.22
- 在"for" 语句中,每次迭代都有自己的一组迭代变量,而不是在每次迭代中共享相同的变量。
- "for" 语句带有"range" 子句可以迭代从零到上限的整数值。
Go 1.23
- "for" 语句带有"range" 子句接受迭代器函数作为范围表达式。
类型统一规则
类型统一规则描述了两种类型是否以及如何统一。精确的细节与 Go 实现相关,影响错误消息的具体内容(例如,编译器是否报告类型推断或其他错误),并可能解释为什么类型推断在不寻常的代码情况下失败。但总的来说,在编写 Go 代码时可以忽略这些规则:类型推断旨在“按预期工作”,并且统一规则也相应地进行了微调。
类型统一由一个匹配模式控制,该模式可以是精确或宽松的。当统一递归地遍历复合类型结构时,用于类型元素的匹配模式(元素匹配模式)与匹配模式相同,除非将两种类型统一用于可赋值性(≡A
):在这种情况下,匹配模式在顶层是宽松的,但随后会更改为元素类型的精确,反映了类型不必相同才能可赋值的事实。
如果不是绑定类型参数的两种类型,如果以下任何条件为真,则它们精确地统一
- 两种类型相同。
- 两种类型具有相同的结构,并且它们的元素类型精确地统一。
- 恰好有一种类型是具有核心类型的未绑定类型参数,并且该核心类型根据
≡A
的统一规则与另一种类型统一(顶层宽松统一,元素类型精确统一)。
如果两种类型都是绑定类型参数,则根据给定的匹配模式统一,如果
- 两个类型参数相同。
- 最多一个类型参数具有已知的类型参数。在这种情况下,类型参数被合并:它们都代表相同的类型参数。如果两个类型参数都还没有已知的类型参数,则将来为一个类型参数推断的类型参数会同时为两个类型参数推断。
- 两个类型参数都具有已知的类型参数,并且类型参数根据给定的匹配模式统一。
单个绑定类型参数 P
和另一个类型 T
根据给定的匹配模式统一,如果
-
P
没有已知的类型参数。在这种情况下,T
被推断为P
的类型参数。 -
P
确实有一个已知的类型参数A
,A
和T
根据给定的匹配模式统一,并且以下条件之一为真
最后,如果不是绑定类型参数的两种类型,如果以下条件为真,则它们宽松地(并根据元素匹配模式)统一
- 两种类型精确地统一。
- 一种类型是定义类型,另一种类型是类型字面量,但不是接口,并且它们的底层类型根据元素匹配模式统一。
- 两种类型都是具有相同类型项的接口(但不是类型参数),都或都不嵌入预声明类型comparable,对应的方法类型精确地统一,并且其中一个接口的方法集是另一个接口的方法集的子集。
- 只有一种类型是接口(但不是类型参数),两种类型的对应方法根据元素匹配模式统一,并且接口的方法集是另一种类型的方法集的子集。
- 两种类型具有相同的结构,并且它们的元素类型根据元素匹配模式统一。