Go 程式語言規範
語言版本 go1.25 (2025 年 2 月 25 日)
前言
這是 Go 程式語言的參考手冊。如需更多資訊或其他文件,請參閱 go.dev。
Go 是一門以系統程式設計為目標而設計的通用程式語言。它是強型別的、具有垃圾回收機制,並對並發程式設計提供顯式支援。程式由套件 (packages) 組成,其屬性允許對依賴關係進行高效管理。
其語法緊湊且易於解析,使得整合開發環境等自動化工具能夠輕鬆進行分析。
標記法 (Notation)
語法使用一種擴充巴科斯範式 (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 "}" .
產生式 (Productions) 是由詞彙項和下列運算子構成的表達式,按優先順序從低到高排列:
| alternation
() grouping
[] option (0 or 1 times)
{} repetition (0 to n times)
小寫的產生式名稱用於識別詞法(終端)符記 (tokens)。非終端符號則使用駝峰式大小寫 (CamelCase)。詞法符記用雙引號 "" 或反引號 `` 括起來。
形式 a … b 表示從 a 到 b 的字元集作為替代項。水平省略號 … 在規範的其他地方也用於非正式地表示未進一步指定的各種列舉或程式碼片段。字元 …(相對於三個字元 ...)並非 Go 語言的符記。
形式為 [Go 1.xx] 的連結表示所描述的語言特性(或其某個方面)是在語言版本 1.xx 中更改或新增的,因此建置時至少需要該語言版本。詳情請參閱附錄中的連結章節。
原始程式碼表示
原始程式碼為編碼為 UTF-8 的 Unicode 文字。文字未經規範化 (canonicalized),因此帶有附加符號的單一碼點 (code point) 與由附加符號和字母組成的相同字元是不同的;它們被視為兩個碼點。為簡單起見,本文件將使用未經限定的術語「字元」來指代原始文字中的 Unicode 碼點。
每個碼點都是不同的;例如,大寫字母和小寫字母是不同的字元。
實作限制:為了與其他工具相容,編譯器可能會禁止原始文字中出現 NUL 字元 (U+0000)。
實作限制:為了與其他工具相容,如果 UTF-8 編碼的位元組順序標記 (U+FEFF) 是原始文字中的第一個 Unicode 碼點,編譯器可能會忽略它。位元組順序標記在原始碼的其他任何位置都可能被禁止。
字元
下列術語用於表示特定的 Unicode 字元類別:
newline = /* the Unicode code point U+000A */ . unicode_char = /* an arbitrary Unicode code point except newline */ . unicode_letter = /* a Unicode code point 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" .
詞法元素
Comments (註解)
註解用於程式文件。有兩種形式:
-
行註解 以字元序列
//開頭,並在行尾結束。 -
一般註解 以字元序列
/*開頭,並以隨後出現的第一個字元序列*/結束。
註解不能在 Rune 字面值或 字串字面值內,或在註解內開始。不包含換行符的一般註解作用如同一個空格。任何其他註解作用如同一個換行符。
符記 (Tokens)
符記構成了 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 的值以及代理對的一半 (surrogate halves)。
反斜線後,某些單字元轉義代表特殊值:
\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
字串字面值
字串字面值代表由連接字元序列所獲得的字串常數。有兩種形式:原生字串字面值 (raw string literals) 和解釋型字串字面值 (interpreted string literals)。
原生字串字面值是位於反引號之間的字元序列,例如 `foo`。在引號內,除了反引號外,可以出現任何字元。原生字串字面值的值是引號之間未經解釋(隱式 UTF-8 編碼)的字元所組成的字串;特別地,反斜線沒有特殊含義,且字串可以包含換行符。原生字串字面值內的歸位字元 ('\r') 會從原生字串值中丟棄。
解釋型字串字面值是位於雙引號之間的字元序列,例如 "bar"。在引號內,除了換行符和未轉義的雙引號外,可以出現任何字元。引號之間的文字構成字面值的值,其中反斜線轉義與Rune 字面值中的解釋方式相同(但 \' 是非法的,而 \" 是合法的),且具有相同的限制。三位八進位(\nnn)和兩位十六進位(\xnn)轉義表示結果字串的單個位元組;所有其他轉義代表單個字元的(可能包含多個位元組的)UTF-8 編碼。因此,在字串字面值內部,\377 和 \xFF 代表值為 0xFF=255 的單個位元組,而 ÿ、\u00FF、\U000000FF 和 \xc3\xbf 代表字元 U+00FF 的 UTF-8 編碼的兩個位元組 0xc3 0xbf。
string_lit = raw_string_lit | interpreted_string_lit .
raw_string_lit = "`" { unicode_char | newline } "`" .
interpreted_string_lit = `"` { unicode_value | byte_value } `"` .
`abc` // same as "abc" `\n \n` // same as "\\n\n\\n" "\n" "\"" // same as `"` "Hello, world!\n" "日本語" "\u65e5本\U00008a9e" "\xff\u00FF" "\uD800" // illegal: surrogate half "\U00110000" // illegal: invalid Unicode code point
這些範例都代表同一個字串:
"日本語" // UTF-8 input text `日本語` // UTF-8 input text as a raw literal "\u65e5\u672c\u8a9e" // the explicit Unicode code points "\U000065e5\U0000672c\U00008a9e" // the explicit Unicode code points "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e" // the explicit UTF-8 bytes
如果原始程式碼將一個字元表示為兩個碼點,例如涉及附加符號和字母的組合形式,則將其放入 Rune 字面值中會產生錯誤(因為它不是單一碼點),如果放入字串字面值中,則會顯示為兩個碼點。
常數
存在布林常數、Rune 常數、整數常數、浮點數常數、複數常數和字串常數。Rune、整數、浮點數和複數常數統稱為數值常數。
常數值由Rune、整數、浮點數、虛數或字串字面值、表示常數的識別字、常數表達式、結果為常數的轉換,或將某些內建函式(如 min 或 max)應用於常數參數、將 unsafe.Sizeof 應用於特定值、將 cap 或 len 應用於某些表達式、將 real 和 imag 應用於複數常數,以及將 complex 應用於數值常數的結果值來表示。布林真值由預宣告的常數 true 和 false 表示。預宣告的識別字 iota 表示一個整數常數。
一般來說,複數常數是一種常數表達式,將在該節討論。
數值常數表示任意精度的精確值,且不會溢位。因此,沒有表示 IEEE 754 負零、無窮大和非數值 (NaN) 的常數。
常數可以是具型別的 (typed) 或不具型別的 (untyped)。字面常數、true、false、iota 以及僅包含不具型別常數運算元的某些常數表達式是不具型別的。
常數可以通過常數宣告或轉換顯式地給予型別,或者在變數宣告、賦值語句或作為表達式中的運算元時隱式地給予型別。如果常數值無法表示為相應型別的值,則為錯誤。如果型別是型別參數,常數會被轉換為該型別參數的非常數值。
不具型別常數有一個預設型別 (default type),這是常數在需要具型別值的上下文中被隱式轉換成的型別,例如在沒有明確型別的短變數宣告(如 i := 0)中。不具型別常數的預設型別分別是 bool、rune、int、float64、complex128 或 string,取決於它是布林、Rune、整數、浮點數、複數還是字串常數。
實作限制:雖然語言中的數值常數具有任意精度,但編譯器可以使用有限精度的內部表示法來實作它們。儘管如此,每個實作都必須:
- 以至少 256 位元表示整數常數。
- 以至少 256 位元的尾數和至少 16 位元的帶符號二進位指數來表示浮點數常數(包括複數常數的部分)。
- 如果無法精確表示整數常數,則給出錯誤。
- 如果因溢位而無法表示浮點數或複數常數,則給出錯誤。
- 如果因精度限制而無法表示浮點數或複數常數,則捨入到最接近的可表示常數。
這些要求同時適用於字面常數和評估常數表達式的結果。
變數
變數是用於保存值的儲存位置。允許的值集由變數的型別決定。
變數宣告,或者對於函式參數和結果,函式宣告或函式字面值的簽名,會為命名的變數保留儲存空間。呼叫內建函式 new 或取複合字面值的位址會在執行時期為變數分配儲存空間。此類匿名變數通過(可能是隱式的)指標間接取值來存取。
陣列、切片和結構型別的結構化變數具有可以單獨定址的元素和欄位。每個這樣的元素都像一個變數。
變數的靜態型別 (static type)(或簡稱型別)是在宣告中給定的型別、new 呼叫或複合字面值中提供的型別,或者是結構化變數元素的型別。介面型別的變數還具有不同的動態型別 (dynamic type),這是執行時期分配給變數的值的(非介面)型別(除非該值是預宣告的識別字 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 的整數索引進行定址。給定元素的切片索引可能小於該元素在底層陣列中的索引。
切片一旦初始化,就始終與持有其元素的底層陣列關聯。因此,切片與其陣列及同一陣列的其他切片共用儲存空間;相比之下,不同的陣列始終代表不同的儲存空間。
切片的底層陣列可能會延伸到切片結尾之外。容量 (capacity) 是該延伸範圍的度量:它是切片長度與切片之外陣列長度之和;通過從原始切片切分 (slicing) 出一個新切片,可以建立長度高達該容量的切片。可以使用內建函式 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()
}
宣告了型別但沒有明確欄位名稱的欄位稱為嵌入欄位 (embedded field)。嵌入欄位必須指定為型別名稱 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 為提升 (promoted)。
提升的欄位表現得像結構的普通欄位,只是它們不能在結構的複合字面值中用作欄位名稱。
給定結構型別 S 和型別名稱 T,提升的方法包含在結構的方法集中,如下所示:
- 如果
S包含嵌入欄位T,則S和*S的方法集都包含接收者為T的提升方法。*S的方法集還包含接收者為*T的提升方法。 - 如果
S包含嵌入欄位*T,則S和*S的方法集都包含接收者為T或*T的提升方法。
欄位宣告後可以跟隨一個可選的字串字面值標籤 (tag),它成為相應欄位宣告中所有欄位的屬性。空標籤字串等同於沒有標籤。標籤通過反射介面可見,並參與結構的型別識別,但在其他方面被忽略。
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) 必須全部存在或全部不存在。如果存在,每個名稱代表指定型別的一個項目(參數或結果),並且簽名中所有非空白名稱必須是唯一的。如果不存在,每個型別代表該型別的一個項目。參數和結果列表始終用括號括起來,除非恰好只有一個未命名的結果,此時可以寫成不帶括號的型別。
函式簽名中的最後一個傳入參數可以具有以 ... 為前綴的型別。具有此類參數的函式稱為可變參數函式 (variadic),並且可以為該參數呼叫零個或多個參數。
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() { … }
則它們實作 Locker 介面以及 File 介面。
嵌入介面
在稍微通用一點的形式中,介面 T 可以使用(可能帶有限定符的)介面型別名稱 E 作為介面元素。這稱為在 T 中嵌入 (embedding) 介面 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 的底層型別必須是其自身,且 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 型別的值實作介面。
映射 (Map) 型別
映射是單一型別(稱為元素型別)元素的無序群組,由另一種型別(稱為鍵型別)的唯一鍵集索引。未初始化的映射的值為 nil。
MapType = "map" "[" KeyType "]" ElementType . KeyType = Type .
比較運算子 == 和 != 必須對鍵型別的運算元完全定義;因此鍵型別不能是函式、映射或切片。如果鍵型別是介面型別,這些比較運算子必須對動態鍵值定義;否則將導致執行時期崩潰 (run-time panic)。
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 映射等同於空映射,只是不能添加元素。
通道 (Channel) 型別
通道提供了一種機制,使並發執行的函式能夠通過發送和接收指定元素型別的值來進行通訊。未初始化的通道的值為 nil。
ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .
可選的 <- 運算子指定通道的方向,即發送或接收。如果給出了方向,則通道是定向的 (directional),否則它是雙向的 (bidirectional)。通道可以通過賦值或顯式轉換限制為僅發送或僅接收。
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 接收它們,則值會按發送順序接收。
型別和值的屬性
值的表示
預宣告型別(關於 any 和 error 介面請參見下文)、陣列和結構的值是自包含的:每個此類值都包含其所有資料的完整副本,並且此類型別的變數儲存整個值。例如,陣列變數為陣列的所有元素提供了儲存空間(變數)。相應的零值特定於該值的型別;它們永遠不會是 nil。
非 nil 指標、函式、切片、映射和通道值包含對底層資料的引用,這些資料可能由多個值共享:
介面值可能是自包含的,或者根據介面的動態型別包含對底層資料的引用。預宣告識別字 nil 是其值可以包含引用的型別的零值。
當多個值共享底層資料時,更改一個值可能會更改另一個值。例如,更改切片的元素將更改所有共享該陣列的切片的底層陣列中的該元素。
底層型別
每個型別 T 都有一個底層型別 (underlying type):如果 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{}。
型別識別
兩種型別要麼是相同的 (identical)(「同一個」),要麼是不同的。
命名型別始終與任何其他型別不同。否則,如果兩種型別的底層型別字面值在結構上是等價的,則它們是相同的;也就是說,它們具有相同的字面結構,且對應的元件具有相同的型別。詳細地說:
- 如果兩個陣列型別具有相同的元素型別和相同的陣列長度,則它們是相同的。
- 如果兩個切片型別具有相同的元素型別,則它們是相同的。
- 如果兩個結構型別具有相同的欄位序列,且對應欄位對具有相同的名稱、相同的型別和相同的標籤,並且兩者皆為嵌入或兩者皆不為嵌入,則它們是相同的。來自不同套件的未匯出欄位名稱始終不同。
- 如果兩個指標型別具有相同的基底型別,則它們是相同的。
- 如果兩個函式型別具有相同數量的參數和結果值,對應的參數和結果型別相同,並且兩者都是可變參數或兩者都不是,則它們是相同的。參數和結果名稱不需要匹配。
- 如果兩個介面型別定義了相同的型別集,則它們是相同的。
- 如果兩個映射型別具有相同的鍵型別和元素型別,則它們是相同的。
- 如果兩個通道型別具有相同的元素型別和相同的方向,則它們是相同的。
- 如果兩個實例化型別的定義型別和所有型別參數都相同,則它們是相同的。
給定以下宣告:
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 } 是不同的,因為前者是實例化的定義型別,而後者是型別字面值(但它們仍然是可賦值的)。
可賦值性 (Assignability)
如果滿足下列條件之一,型別為 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。
可表示性 (Representability)
如果滿足下列條件之一,常數 x 可由型別 T 的值表示(其中 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
方法集 (Method sets)
型別的方法集決定了可以在該型別的運算元上呼叫的方法。每個型別都有一個與之相關聯的(可能為空)方法集:
- 定義型別
T的方法集由所有以接收者型別T宣告的方法組成。 - 指向定義型別
T(其中T既不是指標也不是介面)的指標的方法集,是所有以接收者*T或T宣告的方法集。 - 介面型別的方法集是介面型別集中每個型別的方法集的交集(產生的方法集通常只是介面中宣告的方法集)。
進一步的規則適用於包含嵌入欄位的結構(以及結構的指標),如結構型別一節所述。任何其他型別都有一個空方法集。
區塊 (Blocks)
區塊是花括號內可能為空的宣告和語句序列。
Block = "{" StatementList "}" .
StatementList = { Statement ";" } .
除了原始程式碼中的顯式區塊外,還有隱式區塊:
- 宇宙區塊 (universe block) 包含所有 Go 原始文字。
- 每個套件都有一個套件區塊,包含該套件的所有 Go 原始文字。
- 每個檔案都有一個檔案區塊,包含該檔案中的所有 Go 原始文字。
- 每個 "if"、"for" 和 "switch" 語句都被視為處於其自己的隱式區塊中。
- "switch" 或 "select" 語句中的每個子句都充當一個隱式區塊。
區塊是巢狀的並影響作用域。
宣告和作用域
宣告將一個非空白識別字綁定到常數、型別、型別參數、變數、函式、標籤或套件。程式中的每個識別字都必須宣告。在同一個區塊中不能宣告兩次識別字,且在檔案區塊和套件區塊中都不能宣告相同的識別字。
空白識別字可以用作宣告中的任何其他識別字,但它不引入綁定,因此不被宣告。在套件區塊中,識別字 init 只能用於 init 函式宣告,並且像空白識別字一樣,它不引入新的綁定。
Declaration = ConstDecl | TypeDecl | VarDecl . TopLevelDecl = Declaration | FunctionDecl | MethodDecl .
宣告識別字的作用域 (scope) 是原始文字中該識別字表示指定常數、型別、變數、函式、標籤或套件的範圍。
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
匯出識別字
識別字可以被匯出 (exported),以允許從另一個套件存取它。如果滿足下列兩個條件,則識別字被匯出:
所有其他識別字都不會匯出。
識別字的唯一性
給定一組識別字,如果一個識別字與組內其他所有識別字都不同,則稱其為唯一的。如果兩個識別字拼寫不同,或者它們出現在不同的套件中且未被匯出,則它們是不同的。否則,它們是相同的。
常數宣告
常數宣告將識別字列表(常數名稱)綁定到常數表達式列表的值。識別字的數量必須等於表達式的數量,左側的第 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 [ TypeParameters ] "=" Type .
在該識別字的作用域內,它充當給定型別的別名。
type ( nodeList = []*Node // nodeList and []*Node are identical types Polar = polar // Polar and polar denote identical types )
如果別名宣告指定了型別參數 [Go 1.24],則型別名稱表示一個泛型別名。泛型別名在使用時必須實例化。
type set[P comparable] = map[P]bool
在別名宣告中,給定型別不能是型別參數。
type A[P any] = P // illegal: P is a type parameter
型別定義
型別定義建立一個新的、不同的型別,該型別具有與給定型別相同的底層型別和運算,並將一個識別字(即型別名稱)綁定到它。
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 .
列表中所有非空白名稱必須是唯一的。每個名稱宣告一個型別參數,這是一個新的且不同的命名型別,作為宣告中(尚未知的)型別的佔位符。在泛型函式或型別的實例化時,型別參數會被型別參數 (type argument) 取代。
[P any]
[S interface{ ~[]byte|string }]
[S ~[]E, E any]
[P Constraint[int]]
[_ any]
就像每個普通函式參數都有參數型別一樣,每個型別參數都有一個對應的(元)型別,稱為其型別約束。
當泛型型別的型別參數列表宣告單個型別參數 P 以及約束 C,使得文字 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 表示所有嚴格可比較 (strictly 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
與常規變數宣告不同,短變數宣告可以重新宣告 (redeclare) 變數,前提是它們最初在同一個區塊(如果區塊是函式體,則為參數列表)中以相同的型別宣告,並且至少有一個非空白變數是新的。因此,重新宣告只能出現在多變數短宣告中。重新宣告不引入新變數;它只是將新值分配給原始變數。:= 左側的非空白變數名稱必須是唯一的。
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
方法宣告
方法是具有接收者 (receiver) 的函式。方法宣告將一個識別字(即方法名稱)綁定到一個方法,並將該方法與接收者的基底型別關聯。
MethodDecl = "func" Receiver MethodName Signature [ FunctionBody ] . Receiver = Parameters .
接收者通過方法名稱前的額外參數部分指定。該參數部分必須宣告一個單一的非可變參數,即接收者。其型別必須是定義型別 T 或定義型別 T 的指標,可能後跟方括號括起來的型別參數名稱列表 [P1, P2, …]。T 稱為接收者基底型別。接收者基底型別不能是指標或介面型別,且它必須在與該方法相同的套件中定義。該方法稱為綁定 (bound) 到其接收者基底型別,並且方法名稱僅在型別 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
如果接收者型別由(指向)別名表示,則該別名不能是泛型的,且它不能直接或通過另一個別名間接表示實例化的泛型型別,且無視指標間接取值。
type GPoint[P any] = Point
type HPoint = *GPoint[int]
type IPair = Pair[int, int]
func (*GPoint[P]) Draw(P) { … } // illegal: alias must not be generic
func (HPoint) Draw(P) { … } // illegal: alias must not denote instantiated type GPoint[int]
func (*IPair) Second() int { … } // illegal: alias must not denote instantiated type Pair[int, int]
表達式
表達式通過將運算子和函式應用於運算元來指定值的計算。
運算元
運算元表示表達式中的基本值。運算元可以是字面值、(可能為限定的)表示常數、變數或函式的非空白識別字,或括在括號中的表達式。
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 是類型參數,否則其底層類型必須是結構、陣列、切片或映射類型(除該類型以 TypeName 給定外,語法會強制執行此約束)。若 LiteralType 是類型參數,其類型集中的所有類型必須具有相同的底層類型,且該類型必須是有效的複合字面量類型。元素和鍵的類型必須可指派給類型 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類型的值表示;如果它是帶類型的,則必須是整數類型。 - 沒有鍵的元素使用前一個元素的索引加 1。如果第一個元素沒有鍵,其索引為零。
取址複合字面量會產生一個指向唯一變數的指標,該變數以字面量的值初始化。
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
陣列字面量的長度是字面量類型中指定的長度。如果在字面量中提供的元素少於長度,缺少的元素會被設為陣列元素類型的零值。提供索引值超出陣列索引範圍的元素是錯誤的。... 符號指定的陣列長度等於最大元素索引加 1。
buffer := [10]string{} // len(buffer) == 10
intSet := [6]int{1, 2, 3, 5} // len(intSet) == 6
days := [...]string{"Sat", "Sun"} // len(days) == 2
切片字面量描述了完整的底層陣列字面量。因此,切片字面量的長度和容量等於最大元素索引加 1。切片字面量具有以下形式
[]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 中的深度加 1。
選擇器適用以下規則
- 對於類型為
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.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]
的主要運算式,表示陣列、指向陣列的指標、切片、字串或映射 a 中由 x 索引的元素。值 x 分別稱為索引或映射鍵。以下規則適用:
如果 a 既不是映射也不是類型參數
- 索引
x必須是無類型常數,或者其類型必須是整數或僅包含整數類型的類型參數類型集 - 常數索引必須是非負數,且可由
int類型的值表示 - 無類型的常數索引會被賦予
int類型 - 如果
0 <= x < len(a),則索引x為範圍內,否則為超出範圍
對於陣列類型 A 的 a
對於指向陣列類型的指標 a
a[x]是(*a)[x]的簡寫
對於切片類型 S 的 a
- 如果
x在執行階段超出範圍,則會發生執行階段恐慌 a[x]是索引x處的切片元素,且a[x]的類型是S的元素類型
對於字串類型的 a
對於映射類型 M 的 a
x的類型必須可指派給M的鍵類型- 如果映射包含鍵為
x的條目,則a[x]是鍵為x的映射元素,且a[x]的類型是M的元素類型 - 如果映射為
nil或不包含此類條目,則a[x]為M元素類型的零值
對於類型參數類型 P 的 a
- 索引運算式
a[x]對於P類型集中的所有類型值必須有效。 P類型集中所有類型的元素類型必須相同。在此上下文中,字串類型的元素類型為byte。- 如果類型集中存在映射類型,則該類型集中的所有類型都必須是映射類型,且各自的鍵類型必須全部相同。
a[x]是索引x處的陣列、切片或字串元素,或者是P所實例化的類型參數中鍵為x的映射元素,且a[x]的類型是該(相同)元素類型的類型。- 如果
P的類型集包含字串類型,則a[x]不可被指派。
否則 a[x] 為非法。
映射 a(類型為 map[K]V)的索引運算式,用於指派語句或以下特殊形式的初始化:
v, ok = a[x] v, ok := a[x] var v, ok = a[x]
會產生一個額外的無類型布林值。如果映射中存在鍵 x,則 ok 的值為 true,否則為 false。
對 nil 映射的元素進行指派會導致執行階段恐慌。
切片運算式
切片運算式從字串、陣列、指標陣列或切片運算元建構子字串或切片。有兩種變體:指定下界和上界的簡單形式,以及額外指定容量上限的完整形式。
如果運算元類型是類型參數,除非其類型集包含字串類型,否則類型集中的所有類型必須具有相同的底層類型,且該切片運算式對於該類型的運算元必須有效。如果類型集包含字串類型,它也可能包含底層類型為 []byte 的位元組切片。在這種情況下,該切片運算式對於 string 類型的運算元必須有效。
簡單切片運算式
對於字串、陣列、指向陣列的指標或切片 a,其主要運算式
a[low : high]
建構一個子字串或切片。索引 low 和 high 選擇運算元 a 中哪些元素出現在結果中。結果的索引從 0 開始,長度等於 high - low。對陣列 a 進行切片後
a := [5]int{1, 2, 3, 4, 5}
s := a[1:4]
切片 s 的類型為 []int,長度為 3,容量為 4,其元素為
s[0] == 2 s[1] == 3 s[2] == 4
為方便起見,可以省略任一索引。缺失的 low 索引預設為零;缺失的 high 索引預設為被切片運算元的長度
a[2:] // same as a[2 : len(a)] a[:3] // same as a[0 : 3] a[:] // same as a[0 : len(a)]
如果 a 是指向陣列的指標,則 a[low : high] 是 (*a)[low : high] 的簡寫。
對於陣列或字串,如果 0 <= low <= high <= len(a),則索引為範圍內,否則為超出範圍。對於切片,上限索引為切片容量 cap(a) 而非長度。常數索引必須為非負數,且可由 int 類型的值表示;對於陣列或常數字串,常數索引也必須在範圍內。如果兩個索引均為常數,它們必須滿足 low <= high。如果在執行階段索引超出範圍,則會發生執行階段恐慌。
除無類型字串外,如果被切片運算元是字串或切片,則切片運算的結果是一個與運算元類型相同的非常數值。對於無類型字串運算元,結果是一個 string 類型的非常數值。如果被切片運算元是陣列,則該陣列必須是可定址的,且切片運算的結果是一個與陣列具有相同元素類型的切片。
如果有效切片運算式的被切片運算元是 nil 切片,則結果是 nil 切片。否則,如果結果是切片,它會與運算元共享其底層陣列。
var a [10]int s1 := a[3:7] // underlying array of s1 is array a; &s1[2] == &a[5] s2 := s1[1:4] // underlying array of s2 is underlying array of s1 which is array a; &s2[1] == &a[5] s2[1] = 42 // s2[1] == s1[2] == a[5] == 42; they all refer to the same underlying array element var s []int s3 := s[:0] // s3 == nil
完整切片運算式
對於陣列、指向陣列的指標或切片 a(非字串),其主要運算式
a[low : high : max]
建構一個與簡單切片運算式 a[low : high] 類型相同、長度相同且元素相同的切片。此外,它透過將容量設為 max - low 來控制結果切片的容量。僅第一個索引可省略;預設為 0。對陣列 a 進行切片後
a := [5]int{1, 2, 3, 4, 5}
t := a[1:3:5]
切片 t 的類型為 []int,長度為 2,容量為 4,其元素為
t[0] == 2 t[1] == 3
與簡單切片運算式一樣,如果 a 是指向陣列的指標,則 a[low : high : max] 是 (*a)[low : high : max] 的簡寫。如果被切片運算元是陣列,則該陣列必須是可定址的。
如果 0 <= low <= high <= max <= cap(a),則索引為範圍內,否則為超出範圍。常數索引必須為非負數,且可由 int 類型的值表示;對於陣列,常數索引也必須在範圍內。如果多個索引為常數,則存在的常數必須相對於彼此在範圍內。如果在執行階段索引超出範圍,則會發生執行階段恐慌。
類型斷言
對於介面類型(非類型參數)的運算式 x,以及類型 T,其主要運算式
x.(T)
斷言 x 不為 nil,且儲存在 x 中的值為類型 T。符號 x.(T) 稱為類型斷言。
更精確地說,如果 T 不是介面類型,x.(T) 斷言 x 的動態類型與類型 T 相同。在這種情況下,T 必須實作 x 的(介面)類型;否則類型斷言無效,因為 x 不可能儲存類型為 T 的值。如果 T 是介面類型,x.(T) 斷言 x 的動態類型實作了介面 T。
如果類型斷言成立,運算式的值即為儲存在 x 中的值,且其類型為 T。如果類型斷言為假,則會發生執行階段恐慌。換句話說,儘管 x 的動態類型僅在執行階段已知,但在正確的程式中,x.(T) 的類型已知為 T。
var x interface{} = 7 // x has dynamic type int and value 7
i := x.(int) // i has type int and value 7
type I interface { m() }
func f(y I) {
s := y.(string) // illegal: string does not implement I (missing method m)
r := y.(io.Reader) // r has type io.Reader and the dynamic type of y must implement both I and io.Reader
…
}
用於指派語句或以下特殊形式初始化的類型斷言
v, ok = x.(T)
v, ok := x.(T)
var v, ok = x.(T)
var v, ok interface{} = x.(T) // dynamic types of v and ok are T and bool
會產生一個額外的無類型布林值。如果斷言成立,ok 的值為 true。否則為 false,且 v 的值為類型 T 的零值。在此情況下不會發生執行階段恐慌。
呼叫
給定類型為 函式類型 F 的運算式 f,
f(a1, a2, … an)
以參數 a1, a2, … an 呼叫 f。除了一種特殊情況外,參數必須是可指派給 F 參數類型的單值運算式,並在呼叫函式之前求值。該運算式的類型是 F 的結果類型。方法呼叫類似,但方法本身被指定為接收者類型值上的選擇器。
math.Atan2(x, y) // function call var pt *Point pt.Scale(3.5) // method call with receiver pt
如果 f 表示泛型函式,則必須先將其實例化,然後才能呼叫或作為函式值使用。
如果 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)
變數 s(類型為 Slice)必須可指派給函式參數類型 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的類型集中的所有類型具有相同的底層類型U,且P具有已知的類型參數A,則U和A必須寬鬆統一。 - 同樣地,如果
C的類型集中的所有類型都是具有相同元素類型且無衝突通道方向的通道類型,且P具有已知的類型參數A,則C的類型集中最嚴格的通道類型與A必須寬鬆統一。 - 如果
P沒有已知的類型參數,且C恰好包含一個不是底層(tilde)類型的類型術語T,則統一將映射P ➞ T加入映射中。 - 如果
C沒有上述的類型U,且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 根據 f 的類型參數表示為 float32 或 float64。因此,如果 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" == "🌎" - 將字串類型值轉換為位元組切片類型會產生一個非 nil 切片,其連續元素即為字串的位元組。結果切片的容量取決於實作,可能大於切片長度。
[]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 求值為 false)、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」語句,
- 存在 default case,且
- 每個 case 中的語句列表(包括 default)以終止語句或可能標記的 「fallthrough」語句結尾。
- 「select」語句,其中
- 沒有參考該「select」語句的「break」語句,且
- 每個 case 中的語句列表(包括 default,若存在)以終止語句結尾。
- 標記終止語句的標記語句。
所有其他語句皆不是終止語句。
如果語句列表不為空且其最終的非空語句是終止語句,則該列表以終止語句結尾。
空語句
空語句不執行任何操作。
EmptyStmt = .
標記語句
標記語句可以是 goto、break 或 continue 語句的目標。
LabeledStmt = Label ":" Statement . Label = identifier .
Error: log.Panic("error encountered")
表達式語句
除了特定的內建函式外,函式和方法呼叫以及接收運算可以出現在語句上下文中。此類語句可以加上括號。
ExpressionStmt = Expression .
以下內建函式不允許出現在語句上下文中
append cap complex imag len make new real unsafe.Add unsafe.Alignof unsafe.Offsetof unsafe.Sizeof unsafe.Slice 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= 建構是一個單一 Token。在指派運算中,左右表達式列表必須各包含一個單值表達式,且左側表達式不得為空白識別碼。
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}
在賦值時,每個值必須能賦值給該運算元所屬的型別,但有以下特殊情況:
- 任何型別的值都可以賦值給空白識別字(blank identifier)。
- 若將未具型別(untyped)的常數賦值給介面型別的變數或空白識別字,該常數會先被隱式轉換為其預設型別。
- 若將未具型別的布林值賦值給介面型別的變數或空白識別字,它會先被隱式轉換為
bool型別。
當值被賦值給變數時,僅有儲存在該變數中的資料會被替換。若該值包含一個參考,賦值動作會複製該參考,但不會複製被參考的資料(例如切片的底層陣列)。
var s1 = []int{1, 2, 3}
var s2 = s1 // s2 stores the slice descriptor of s1
s1 = s1[:1] // s1's length is 1 but it still shares its underlying array with s2
s2[0] = 42 // setting s2[0] changes s1[0] as well
fmt.Println(s1, s2) // prints [42] [42 2 3]
var m1 = make(map[string]int)
var m2 = m1 // m2 stores the map descriptor of m1
m1["foo"] = 42 // setting m1["foo"] changes m2["foo"] as well
fmt.Println(m2["foo"]) // prints 42
If 陳述式
"If" 陳述式根據布林運算式的值,指定兩個分支的條件執行。若運算式結果為 true,則執行 "if" 分支;否則,若存在 "else" 分支,則執行該分支。
IfStmt = "if" [ SimpleStmt ";" ] Expression Block [ "else" ( IfStmt | Block ) ] .
if x > max {
x = max
}
運算式前方可加上一個簡單陳述式(simple statement),該陳述式會在運算式求值前執行。
if x := f(); x < y {
return x
} else if x > z {
return z
} else {
return y
}
Switch 陳述式
"Switch" 陳述式提供多向執行。透過比較運算式或型別與 "switch" 內的 "cases" 來決定執行哪個分支。
SwitchStmt = ExprSwitchStmt | TypeSwitchStmt .
它有兩種形式:運算式 switch 和型別 switch。在運算式 switch 中,case 包含與 switch 運算式值進行比較的運算式。在型別 switch 中,case 包含與特殊標註的 switch 運算式型別進行比較的型別。在一個 switch 陳述式中,switch 運算式只會被求值一次。
運算式 Switch
在運算式 switch 中,會先對 switch 運算式求值,並由左至右、由上至下對 case 運算式(不需為常數)求值;第一個與 switch 運算式相等的 case 將觸發相關陳述式的執行;其他 cases 則會被略過。若沒有 case 符合且存在 "default" case,則執行該 case 的陳述式。每個 "switch" 陳述式至多只能有一個 default case,且它可以出現在任何位置。省略 switch 運算式等同於布林值 true。
ExprSwitchStmt = "switch" [ SimpleStmt ";" ] [ Expression ] "{" { ExprCaseClause } "}" .
ExprCaseClause = ExprSwitchCase ":" StatementList .
ExprSwitchCase = "case" ExpressionList | "default" .
若 switch 運算式的結果為未具型別常數,則會先隱式轉換為其預設型別。預宣告的未具型別值 nil 不能用作 switch 運算式。switch 運算式的型別必須是可比較(comparable)的。
若 case 運算式未具型別,則會先隱式轉換為 switch 運算式的型別。對於每個(可能已轉換的)case 運算式 x 與 switch 運算式的值 t,x == t 必須是有效的比較。
換句話說,switch 運算式被視為用來宣告並初始化一個沒有明確型別的暫存變數 t;接著將每個 case 運算式 x 與該 t 的值進行相等性測試。
在 case 或 default 子句中,最後一個非空陳述式可以是(可能帶有標籤的)"fallthrough" 陳述式,用以表示控制權應從該子句末尾流向下一子句的第一個陳述式。否則,控制權將流向 "switch" 陳述式的末尾。"fallthrough" 陳述式可以出現在運算式 switch 中除了最後一個子句以外的任何子句的最後位置。
switch 運算式前方可加上一個簡單陳述式,該陳述式會在運算式求值前執行。
switch tag {
default: s3()
case 0, 1, 2, 3: s1()
case 4, 5, 6, 7: s2()
}
switch x := f(); { // missing switch expression means "true"
case x < 0: return -x
default: return x
}
switch {
case x < y: f1()
case x < z: f2()
case x == 4: f3()
}
實作限制:編譯器可能會禁止多個 case 運算式求值為相同的常數。例如,目前的編譯器不允許 case 運算式中出現重複的整數、浮點數或字串常數。
型別 Switch
型別 switch 比較的是型別而非值。除此之外,它與運算式 switch 類似。它由一個特殊的 switch 運算式標記,該運算式採用型別斷言(type assertion)的形式,並使用關鍵字 type 而非實際型別。
switch x.(type) {
// cases
}
Cases 會將實際型別 T 與運算式 x 的動態型別進行匹配。如同型別斷言,x 必須是介面型別,但不能是型別參數,且 case 中列出的每個非介面型別 T 必須實作 x 的介面型別。型別 switch 的 cases 中所列出的型別必須全都不同。
TypeSwitchStmt = "switch" [ SimpleStmt ";" ] TypeSwitchGuard "{" { TypeCaseClause } "}" .
TypeSwitchGuard = [ identifier ":=" ] PrimaryExpr "." "(" "type" ")" .
TypeCaseClause = TypeSwitchCase ":" StatementList .
TypeSwitchCase = "case" TypeList | "default" .
TypeSwitchGuard 可能包含一個短變數宣告。當使用該形式時,該變數會在每個子句的隱式區塊末尾宣告。在 case 中僅列出單一型別的子句中,該變數具有該型別;否則,該變數具有 TypeSwitchGuard 中運算式的型別。
Case 可以使用預宣告識別字 nil 取代型別;當 TypeSwitchGuard 中的運算式為 nil 介面值時,會選中該 case。至多只能有一個 nil case。
給定一個 interface{} 型別的運算式 x,以下的型別 switch
switch i := x.(type) {
case nil:
printString("x is nil") // type of i is type of x (interface{})
case int:
printInt(i) // type of i is int
case float64:
printFloat64(i) // type of i is float64
case func(int) float64:
printFunction(i) // type of i is func(int) float64
case bool, string:
printString("type is bool or string") // type of i is type of x (interface{})
default:
printString("don't know the type") // type of i is type of x (interface{})
}
可以改寫為
v := x // x is evaluated exactly once
if v == nil {
i := v // type of i is type of x (interface{})
printString("x is nil")
} else if i, isInt := v.(int); isInt {
printInt(i) // type of i is int
} else if i, isFloat64 := v.(float64); isFloat64 {
printFloat64(i) // type of i is float64
} else if i, isFunc := v.(func(int) float64); isFunc {
printFunction(i) // type of i is func(int) float64
} else {
_, isBool := v.(bool)
_, isString := v.(string)
if isBool || isString {
i := v // type of i is type of x (interface{})
printString("type is bool or string")
} else {
i := v // type of i is type of x (interface{})
printString("don't know the type")
}
}
型別參數或泛型型別可用作 case 中的型別。若在實例化(instantiation)後,該型別與 switch 中的另一個項目重複,則會選擇第一個匹配的 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
型別 switch 的防護子句(guard)前方可加上一個簡單陳述式,該陳述式會在防護子句求值前執行。
"fallthrough" 陳述式不允許在型別 switch 中使用。
For 陳述式
"for" 陳述式指定區塊的重複執行。共有三種形式:迭代可以由單一條件、"for" 子句或 "range" 子句控制。
ForStmt = "for" [ Condition | ForClause | RangeClause ] Block . Condition = Expression .
具有單一條件的 For 陳述式
在最簡單的形式中,"for" 陳述式指定只要布林條件求值為 true,就重複執行區塊。條件在每次迭代前求值。若省略條件,則等同於布林值 true。
for a < b {
a *= 2
}
具有 for 子句的 For 陳述式
帶有 ForClause 的 "for" 陳述式同樣由條件控制,但額外可以指定 init(初始化)和 post(後續)陳述式,例如賦值、遞增或遞減陳述式。Init 陳述式可以是短變數宣告,但 post 陳述式則不可。
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 陳述式
"for" 陳述式帶有 "range" 子句時,會迭代陣列、切片、字串或對應(map)的所有項目,從通道接收的值,從零到上限的整數值 [Go 1.22],或是傳遞給迭代器函式 yield 函式的值 [Go 1.23]。對於每個項目,若存在對應的 迭代變數,則將 迭代值 賦值給它們,然後執行區塊。
RangeClause = [ ExpressionList "=" | IdentifierList ":=" ] "range" Expression .
"range" 子句右側的運算式稱為 range 運算式,它可以是陣列、陣列指標、切片、字串、對應、允許接收操作的通道、整數,或是具有特定簽名的函式(詳見下方)。如同賦值,若左側運算元存在,它們必須是可定址的(addressable)或對應索引運算式;它們代表迭代變數。若 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 取代字元),且下一次迭代將在字串中推進一個位元組。 - 對應(map)的迭代順序未指定,且不保證兩次迭代之間相同。若在迭代期間刪除尚未到達的對應項目,則不會產生該項目對應的迭代值。若在迭代期間建立了對應項目,該項目可能會在迭代期間產生或被跳過。此選擇對於每個建立的項目以及不同迭代之間可能各不相同。若對應為
nil,則迭代次數為 0。 - 對於通道,產生的迭代值是通道上連續發送的值,直到通道被關閉。若通道為
nil,range 運算式將永遠阻塞。 - 對於整數值
n(其中n為整數型別或未具型別的整數常數),會按遞增順序產生 0 到n-1的迭代值。若n為整數型別,則迭代值具有相同的型別。否則,n的型別將如同它被賦值給迭代變數般決定。具體而言:若迭代變數已存在,迭代值的型別即為迭代變數的型別(必須是整數型別)。否則,若迭代變數由 "range" 子句宣告或不存在,迭代值的型別即為n的預設型別。若n<= 0,迴圈不會執行任何迭代。 - 對於函式
f,迭代過程為使用一個新的、合成的yield函式作為參數呼叫f。若在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
}
若 range 運算式的型別為型別參數,其型別集中的所有型別必須具有相同的底層型別,且 range 運算式對於該型別必須有效;或者,若型別集包含通道型別,則必須僅包含具有相同元素型別的通道型別,且所有通道型別必須允許接收操作。
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,且它可出現在 cases 列表中的任何位置。
"select" 陳述式的執行依序分為幾個步驟:
- 對於陳述式中的所有 cases,進入 "select" 陳述式時,接收操作的通道運算元以及發送陳述式的通道與右側運算式會按來源順序精確地求值一次。結果是一組用於接收或發送的通道,以及對應要發送的值。該求值過程中的任何副作用都會發生,無論最終選中了哪一個(如果有的話)通訊操作執行。帶有短變數宣告或賦值的 RecvStmt 左側運算式尚不會求值。
- 若有一個或多個通訊可以繼續,則透過統一偽隨機選擇選出其中一個。否則,若存在 default case,則選擇該 case。若沒有 default case,"select" 陳述式會阻塞直到至少有一個通訊可以繼續。
- 除非選中的是 default case,否則將執行對應的通訊操作。
- 若選中的 case 是一個帶有短變數宣告或賦值的 RecvStmt,則左側運算式會進行求值,並賦值接收到的值(或多個值)。
- 執行選中 case 的陳述式列表。
由於 nil 通道上的通訊永遠無法繼續,因此只有 nil 通道且無 default case 的 select 陳述式將永遠阻塞。
var a []int
var c, c1, c2, c3, c4 chan int
var i1, i2 int
select {
case i1 = <-c1:
print("received ", i1, " from c1\n")
case c2 <- i2:
print("sent ", i2, " to c2\n")
case i3, ok := (<-c3): // same as: i3, ok := <-c3
if ok {
print("received ", i3, " from c3\n")
} else {
print("c3 is closed\n")
}
case a[f()] = <-c4:
// same as:
// case t := <-c4
// a[f()] = t
default:
print("no communication\n")
}
for { // send random sequence of bits to c
select {
case c <- 0: // note: no statement, no fallthrough, no folding of cases
case c <- 1:
}
}
select {} // block forever
Return 陳述式
函式 F 中的 "return" 陳述式會終止 F 的執行,並可選擇提供一個或多個結果值。任何由 F 延遲(deferred)的函式會在 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 處,與結果參數同名的另一個實體(常數、型別或變數)位於作用域內,編譯器可能會禁止 "return" 陳述式中使用空的運算式列表。
func f(n int) (res int, err error) {
if _, err := f(n-1); err != nil {
return // invalid return statement: err is shadowed
}
return
}
Break 陳述式
"break" 陳述式終止同一個函式內最內層的 "for"、"switch" 或 "select" 陳述式的執行。
BreakStmt = "break" [ Label ] .
若存在標籤,則必須是外層 "for"、"switch" 或 "select" 陳述式的標籤,這即是將要終止執行的陳述式。
OuterLoop:
for i = 0; i < n; i++ {
for j = 0; j < m; j++ {
switch a[i][j] {
case nil:
state = Error
break OuterLoop
case item:
state = Found
break OuterLoop
}
}
}
Continue 陳述式
"continue" 陳述式透過將控制權推進到迴圈區塊末尾,開始最內層封閉 "for" 迴圈的下一次迭代。"for" 迴圈必須在同一個函式內。
ContinueStmt = "continue" [ Label ] .
若存在標籤,則必須是外層 "for" 陳述式的標籤,這即是將要推進執行的陳述式。
RowLoop:
for y, row := range rows {
for x, data := range row {
if data == endOfRow {
continue RowLoop
}
row[x] = data + bias(x, y)
}
}
Goto 陳述式
"goto" 陳述式將控制權轉移到同一個函式內對應標籤的陳述式。
GotoStmt = "goto" Label .
goto Error
執行 "goto" 陳述式不得導致任何原本不在 goto 處作用域內的變數進入作用域。例如,此範例
goto L // BAD v := 3 L:
是錯誤的,因為跳轉到標籤 L 跳過了 v 的建立。
"goto" 陳述式在區塊外不能跳轉到該區塊內的標籤。例如,此範例
if n%2 == 1 {
goto L1
}
for n > 0 {
f()
n--
L1:
f()
n--
}
是錯誤的,因為標籤 L1 在 "for" 陳述式的區塊內,但 goto 不在。
Fallthrough 陳述式
"fallthrough" 陳述式將控制權轉移到運算式 "switch" 陳述式中下一個 case 子句的第一個陳述式。它只能作為該子句中最後一個非空陳述式使用。
FallthroughStmt = "fallthrough" .
Defer 陳述式
"defer" 陳述式會呼叫一個函式,其執行會被延遲到周圍函式返回的時刻,無論是因為周圍函式執行了 return 陳述式、執行到其 函式主體末尾,還是因為對應的 goroutine 發生恐慌(panicking)。
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 型別的結果切片。值 x 被傳遞給型別為 ...E 的參數,其中 E 是 S 的元素型別,並適用對應的參數傳遞規則。作為特殊情況,append 也接受第一個參數為可賦值給 []byte 型別,且第二個參數為字串型別後接 ...。此形式會追加字串的位元組。
append(s S, x ...E) S // E is the element type of S
若 S 是型別參數,其型別集中的所有型別必須具有相同的底層切片型別 []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,並回傳複製的元素數量。兩個參數必須具有相同的元素型別 E,且必須能賦值給 []E 型別的切片。複製的元素數量為 len(src) 和 len(dst) 的最小值。作為特殊情況,copy 也接受一個可賦值給 []byte 型別的目的參數,以及一個 string 型別的來源參數。此形式會將字串的位元組複製到位元組切片中。
copy(dst, src []T) int copy(dst []byte, src string) int
若一個或兩個參數的型別是型別參數,它們各自型別集中的所有型別必須具有相同的底層切片型別 []E。
範例
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
內建函式 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 的參數型別為型別參數,其型別集中的所有型別必須是對應或切片,且 clear 會執行對應實際型別參數的操作。
若對應或切片為 nil,clear 不執行任何操作。
Close
對於通道 ch,內建函式 close(ch) 記錄該通道不再發送任何值。若 ch 是僅接收通道則為錯誤。對已關閉的通道進行發送或再次關閉會導致執行階段恐慌。關閉 nil 通道也會導致執行階段恐慌。呼叫 close 之後,在所有先前發送的值被接收後,接收操作將回傳通道型別的零值且不阻塞。多值接收操作回傳接收到的值以及一個表示通道是否關閉的標記。
若 close 的參數型別為型別參數,其型別集中的所有型別必須是具有相同元素型別的通道。若其中任何通道是僅接收通道,則為錯誤。
操作複數
三個函式用於組裝與拆解複數。內建函式 complex 從浮點數實部與虛部建構一個複數值,而 real 與 imag 則分別提取複數值的實部與虛部。
complex(realPart, imaginaryPart floatT) complexT real(complexT) floatT imag(complexT) floatT
參數與回傳值的型別是對應的。對於 complex,兩個參數必須為相同的浮點數型別,且回傳型別為具有對應浮點數組成的複數型別:complex64 對應 float32 參數,complex128 對應 float64 參數。若其中一個參數求值為未具型別常數,它會先被隱式轉換為另一個參數的型別。若兩個參數皆為未具型別常數,它們必須是非複數的數字或其虛部必須為零,且該函式的回傳值為未具型別的複數常數。
對於 real 和 imag,參數必須是複數型別,且回傳型別為對應的浮點數型別:float32 對應 complex64 參數,float64 對應 complex128 參數。若參數求值為未具型別常數,它必須是一個數字,且該函式的回傳值為未具型別的浮點數常數。
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
不允許使用型別參數型別的參數。
刪除對應元素
內建函式 delete 從對應 m 中刪除鍵為 k 的元素。值 k 必須能賦值給 m 的鍵型別。
delete(m, k) // remove element m[k] from map m
若 m 的型別為型別參數,其型別集中的所有型別必須是對應,且它們必須擁有相同的鍵型別。
若對應 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)的值。記憶體初始化方式如初始值章節所述。
Call Type T Result make(T, n) slice slice of type T with length n and capacity n make(T, n, m) slice slice of type T with length n and capacity m make(T) map map of type T make(T, n) map map of type T with initial space for approximately n elements make(T) channel unbuffered channel of type T make(T, n) channel buffered channel of type T, buffer size n make(T, n) type parameter see below make(T, n, m) type parameter see below
若第一個參數為型別參數,其型別集中的所有型別必須具有相同的底層型別(必須是切片或對應型別),或者若為通道型別,則必須皆為通道型別,且必須具有相同的元素型別,且通道方向不得衝突。
每個大小參數 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
內建函式 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)
配置(Allocation)
內建函式 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 的參數值。此終止順序稱為 恐慌(panicking)。
panic(42)
panic("unreachable")
panic(Error("cannot parse"))
recover 函式允許程式管理發生恐慌的 goroutine 之行為。假設函式 G 延遲了一個呼叫 recover 的函式 D,且在執行 G 的同一個 goroutine 的某個函式中發生了恐慌。當執行延遲函式到達 D 時,D 呼叫 recover 的回傳值將會是傳遞給 panic 的值。若 D 正常返回而沒有啟動新的 panic,則恐慌順序停止。在這種情況下,G 與 panic 呼叫之間所呼叫函式的狀態會被捨棄,並恢復正常執行。接著會執行 G 在 D 之前延遲的任何函式,並透過返回給 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()
}
啟動(Bootstrapping)
當前的實作提供了幾個在啟動過程中實用的內建函式。這些函式記錄於此以求完整,但不保證會留在語言中。它們不回傳結果。
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 程式是透過連結 套件(packages) 建構而成。一個套件是由一個或多個原始程式檔構成,這些檔案共同宣告了屬於該套件的常數、型別、變數和函式,並在該套件的所有檔案中皆可存取。這些元素可以匯出並在其他套件中使用。
原始程式檔組織
每個原始程式檔皆由一個套件宣告(package clause)開頭以定義其所屬套件,後接一組(可為空)宣告匯入套件的 import 宣告,以便使用其內容,再後接一組(可為空)函式、型別、變數和常數的宣告。
SourceFile = PackageClause ";" { ImportDecl ";" } { TopLevelDecl ";" } .
套件宣告
每個原始程式檔皆以套件宣告開頭,定義該檔案所屬的套件。
PackageClause = "package" PackageName . PackageName = identifier .
PackageName 不得為空白識別字。
package math
一組共享相同 PackageName 的檔案構成了套件的實作。實作可能要求該套件的所有原始程式檔位於同一個目錄中。
Import 宣告
Import 宣告表明包含該宣告的原始程式檔依賴於 被匯入 套件的功能(§程式初始化與執行),並允許存取該套件的匯出識別字。Import 命名一個識別字(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" 識別的檔案中。此表說明了在 import 宣告後,匯入該套件的檔案中如何存取 Sin。
Import declaration Local name of Sin import "lib/math" math.Sin import m "lib/math" m.Sin import . "lib/math" Sin
Import 宣告定義了匯入套件與被匯入套件之間的依賴關係。套件直接或間接匯入自身是非法的,或者直接匯入一個套件而不參考其任何匯出識別字也是不合法的。若僅為了副作用(初始化)而匯入套件,請使用空白識別字作為明確套件名稱。
import _ "lib/math"
套件範例
這是一個實作並發素數篩法的完整 Go 套件。
package main
import "fmt"
// Send the sequence 2, 3, 4, … to channel 'ch'.
func generate(ch chan<- int) {
for i := 2; ; i++ {
ch <- i // Send 'i' to channel 'ch'.
}
}
// Copy the values from channel 'src' to channel 'dst',
// removing those divisible by 'prime'.
func filter(src <-chan int, dst chan<- int, prime int) {
for i := range src { // Loop over values received from 'src'.
if i%prime != 0 {
dst <- i // Send 'i' to channel 'dst'.
}
}
}
// The prime sieve: Daisy-chain filter processes together.
func sieve() {
ch := make(chan int) // Create a new channel.
go generate(ch) // Start generate() as a subprocess.
for {
prime := <-ch
fmt.Print(prime, "\n")
ch1 := make(chan int)
go filter(ch, ch1, prime)
ch = ch1
}
}
func main() {
sieve()
}
程式初始化與執行
零值
當透過宣告或呼叫 new 為變數分配儲存空間,或者透過複合字面量或呼叫 make 建立新值時,若未提供明確初始化,則該變數或值會被賦予預設值。此類變數或值的每個元素皆會設定為其型別的 零值:布林值為 false,數值型別為 0,字串為 "",指標、函式、介面、切片、通道和對應則為 nil。此初始化是遞迴進行的,因此例如陣列的每個元素若未指定值,其結構欄位也會被歸零。
這兩個簡單宣告是等價的
var i int var i int = 0
之後
type T struct { i int; f float64; next *T }
t := new(T)
以下成立
t.i == 0 t.f == 0.0 t.next == nil
同樣地,在以下宣告後也成立
var t T
套件初始化
在套件內部,套件層級變數初始化循序進行,每一步選取 宣告順序 中最早且不依賴未初始化變數的變數。
更精確地說,若套件層級變數尚未初始化,且沒有初始化運算式,或者其初始化運算式沒有依賴於未初始化的變數,則該變數被視為 準備好初始化。初始化過程為重複初始化下一個在宣告順序中最早且已準備好初始化的變數,直到沒有變數可以初始化為止。
若此過程結束時仍有變數未初始化,這些變數即屬於一個或多個初始化循環,程式無效。
由右側單一(多值)運算式初始化且位於變數宣告左側的多個變數會一起初始化:若左側有任何變數被初始化,則所有這些變數會在同一步驟中初始化。
var x = a var a, b = f() // a and b are initialized together, before x is initialized
基於套件初始化的目的,空白變數在宣告中與其他變數處理方式相同。
多個檔案中宣告的變數,其宣告順序取決於檔案傳遞給編譯器的順序:第一個檔案中宣告的變數比第二個檔案中宣告的任何變數先宣告,依此類推。為確保可重現的初始化行為,建議建構系統依據詞法檔名順序將同套件的多個檔案傳遞給編譯器。
依賴分析不依賴變數的實際值,僅依賴原始碼中對它們的詞法 參考,並進行遞迴分析。例如,若變數 x 的初始化運算式參考了一個函式,而該函式的本體參考了變數 y,則 x 依賴於 y。具體而言:
- 變數或函式的參考是指向該變數或函式的識別字。
- 方法
m的參考是一種t.m形式的方法值或方法運算式,其中t的(靜態)型別不是介面型別,且方法m位於t的方法集中。所得函式值t.m是否被呼叫並不重要。 - 變數、函式或方法
x依賴於變數y,若x的初始化運算式或本體(對於函式和方法)包含對y的參考,或是對依賴於y的函式或方法的參考。
例如,給定宣告
var (
a = c + b // == 9
b = f() // == 4
c = f() // == 5
d = 3 // == 5 after initialization has finished
)
func f() int {
d++
return d
}
初始化順序為 d, b, c, a。注意初始化運算式中子運算式的順序無關緊要:在此範例中,a = c + b 和 a = b + c 產生相同的初始化順序。
依賴分析在每個套件內執行;僅考慮參考當前套件中宣告的變數、函式和(非介面)方法的參考。若變數之間存在其他隱藏的資料依賴,則這些變數之間的初始化順序未指定。
例如,給定以下宣告
var x = I(T{}).ab() // x has an undetected, hidden dependency on a and b
var _ = sideEffect() // unrelated to x, a, or b
var a = b
var b = 42
type I interface { ab() []int }
type T struct{}
func (T) ab() []int { return []int{a, b} }
變數 a 會在 b 之後初始化,但 x 是否在 b 之前、b 和 a 之間,還是 a 之後初始化(以及因此 sideEffect() 是在 x 初始化前後呼叫),皆未指定。
變數也可以使用在套件區塊中宣告的 init 函式來初始化,該函式不帶參數且無結果參數。
func init() { … }
每個套件可以定義多個此類函式,甚至是在同一個原始程式檔中。在套件區塊中,init 識別字僅能用於宣告 init 函式,但識別字本身未被宣告。因此,在程式的任何地方都無法參考 init 函式。
整個套件的初始化是透過為所有套件層級變數分配初始值,接著依據原始碼中出現的順序呼叫所有 init 函式來完成,這可能包含傳遞給編譯器的多個檔案。
程式初始化
完整程式的套件會逐一進行初始化。若套件有 import,則被匯入的套件會在初始化該套件本身之前初始化。若多個套件匯入同一個套件,該被匯入的套件只會被初始化一次。透過建構,匯入套件保證不會有循環初始化依賴。更精確地說:
給定所有按匯入路徑排序的套件列表,在每一步中,列表內第一個尚未初始化,且所有被匯入套件(若有)皆已初始化的套件會被初始化。此步驟重複進行直到所有套件皆已初始化。
套件初始化(變數初始化與 init 函式的呼叫)在單一 goroutine 中循序進行,一次處理一個套件。init 函式可以啟動其他 goroutine,這些 goroutine 可以與初始化程式碼並發執行。然而,初始化總是會將 init 函式序列化:直到上一個 init 函式返回前,不會呼叫下一個。
程式執行
完整程式是透過連結單一個未匯入的套件(稱為 main 套件)以及它所傳遞匯入的所有套件來建立。Main 套件必須具有套件名稱 main,並宣告一個不帶參數且不回傳值的函式 main。
func main() { … }
程式執行始於初始化程式,然後呼叫 main 套件中的 main 函式。當該函式呼叫返回時,程式退出。它不會等待其他(非 main)goroutine 完成。
Errors (錯誤)
預宣告型別 error 定義為:
type error interface {
Error() string
}
這是表示錯誤條件的慣用介面,其中 nil 值表示無錯誤。例如,從檔案讀取資料的函式可以定義為:
func Read(f *File, b []byte) (n int, err error)
執行階段恐慌(Run-time panics)
嘗試對陣列進行越界索引等執行錯誤會觸發 執行階段恐慌,等同於使用實作定義的介面型別 runtime.Error 值來呼叫內建函式 panic。該型別滿足預宣告介面型別 error。表示不同執行階段錯誤條件的確切錯誤值未指定。
package runtime
type Error interface {
error
// and perhaps other methods
}
系統考量
unsafe 套件
內建套件 unsafe 對編譯器已知,並可透過匯入路徑 "unsafe" 存取,它提供了包括違反型別系統操作在內的底層程式設計功能。使用 unsafe 的套件必須手動檢查型別安全,且可能不具可移植性。該套件提供了以下介面:
package unsafe type ArbitraryType int // shorthand for an arbitrary Go type; it is not a real type type Pointer *ArbitraryType func Alignof(variable ArbitraryType) uintptr func Offsetof(selector ArbitraryType) uintptr func Sizeof(variable ArbitraryType) uintptr type IntegerType int // shorthand for an integer type; it is not a real type func Add(ptr Pointer, len IntegerType) Pointer func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType func SliceData(slice []ArbitraryType) *ArbitraryType func String(ptr *byte, len IntegerType) string func StringData(str string) *byte
Pointer 是一種指標型別,但 Pointer 值不可被取值(dereferenced)。任何 uintptr 底層型別的指標或值皆可轉換為 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 宣告)的對齊值或大小。
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 型別(的變數)具有 變動大小。否則大小為 常數。若參數(或對於 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)是x每個欄位f的所有unsafe.Alignof(x.f)值中的最大值,且至少為 1。 - 對於陣列型別的變數
x:unsafe.Alignof(x)與該陣列元素型別的變數對齊值相同。
若結構或陣列型別不包含大小大於零的欄位(或元素),則其大小為零。兩個不同的零大小變數在記憶體中可能具有相同的位址。
附錄
語言版本
Go 1 相容性保證確保為 Go 1 規範編寫的程式,在該規範的生命週期內,無需變更即可繼續正確編譯並執行。更廣泛地說,隨著語言調整與功能新增,相容性保證確保適用於特定 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 版本為該語言增加了多型函式與型別("泛型")。具體而言:
- 運算子與標點符號集合包含了新的 token
~。 - 函式和型別宣告可以宣告型別參數。
- 介面型別可以嵌入任意型別(不僅是介面的型別名稱),以及聯集與
~T型別元素。 - 預宣告型別集合包含了新的
any和comparable型別。
Go 1.20
- 若切片與陣列元素型別匹配且陣列不長於切片,切片可以轉換為陣列。
- 內建
unsafe套件包含了新的SliceData、String和StringData函式。 -
可比較型別(如普通介面)即使型別參數並非嚴格可比較,也可以滿足
comparable約束。
Go 1.21
Go 1.22
- 在 "for" 陳述式中,每次迭代都有其各自獨立的迭代變數組,而不是在每次迭代共享相同的變數。
- 帶有 "range" 子句的 "for" 陳述式可以迭代從零到上限的整數值。
Go 1.23
- 帶有 "range" 子句的 "for" 陳述式接受迭代器函式作為 range 運算式。
Go 1.24
型別統一規則
型別統一規則描述了兩個型別是否以及如何統一。具體細節與 Go 實作相關,會影響錯誤訊息的具體情況(例如編譯器是否回報型別推論或其他錯誤),並解釋為何型別推論在某些特殊程式情況下會失敗。但總體而言,在編寫 Go 程式碼時可以忽略這些規則:型別推論旨在大多數情況下「如預期般運作」,而統一規則則是據此進行微調的。
類型統一由匹配模式控制,該模式可以是精確(exact)或寬鬆(loose)。當統一過程遞迴地向下解析複合類型結構時,用於該類型元素的匹配模式(即元素匹配模式)與當前的匹配模式保持一致;但當兩個類型為了可賦值性(≡A)進行統一時例外:在此情況下,頂層採用寬鬆匹配模式,但隨後會變更為精確匹配模式來處理元素類型,這反映了類型無需完全相同即可滿足賦值條件的事實。
若兩個類型皆非已綁定的類型參數,則在滿足以下任一條件時,它們進行「精確」統一:
- 兩個類型完全相同。
- 兩個類型具有相同的結構,且其元素類型進行精確統一。
- 恰有一個類型是未綁定的類型參數,且該類型參數集中的所有類型與另一類型根據
≡A的統一規則(頂層寬鬆統一,元素類型精確統一)進行統一。
若兩個類型皆為已綁定的類型參數,則在以下情況下,它們依照給定的匹配模式進行統一:
- 兩個類型參數完全相同。
- 兩個類型參數中,至多只有一個具有已知的類型引數。在此情況下,類型參數被合併:兩者皆代表同一個類型引數。若兩者皆尚未有已知的類型引數,則未來為其中一個類型參數推導出的類型引數,將會同時應用於兩者。
- 兩個類型參數皆具有已知的類型引數,且這些類型引數依照給定的匹配模式進行統一。
單一已綁定的類型參數 P 與另一個類型 T 在以下情況下依照給定的匹配模式進行統一:
-
P沒有已知的類型引數。在此情況下,T被推導為P的類型引數。 -
P具有已知的類型引數A,且A與T依照給定的匹配模式統一,並滿足以下任一條件:
最後,兩個非已綁定類型參數的類型,若滿足以下條件,則進行「寬鬆」統一(並遵循元素匹配模式):
- 兩個類型進行精確統一。
- 其中一個類型是定義類型,另一個類型是類型字面量(但非介面),且它們的底層類型依照元素匹配模式進行統一。
- 兩個類型皆為介面(且非類型參數),具有相同的類型項(type terms),兩者同時嵌入或皆未嵌入預宣告類型 comparable,對應的方法類型進行精確統一,且其中一個介面的方法集為另一個介面方法集的子集。
- 僅有一個類型是介面(且非類型參數),兩個類型的對應方法依照元素匹配模式進行統一,且介面的方法集為另一個類型方法集的子集。
- 兩個類型具有相同的結構,且其元素類型依照元素匹配模式進行統一。