Go 程式語言規範
語言版本 go1.23 (2024 年 6 月 13 日)
引言
這是 Go 程式語言的參考手冊。不含泛型的 Go1.18 之前版本可在 這裡 找到。更多資訊與其他文件,請參閱 go.dev。
Go 是一門通用語言,在設計上以系統程式語言為考量。它是強類型且垃圾回收的,並明確支援並行程式設計。程式由能有效管理依賴項的「套件」建構而成。
語法簡潔且易於解析,可讓整合式開發環境等自動化工具輕鬆分析。
符號表示法
語法使用 變體 的擴充貝克斯-瑙爾範式 (EBNF) 指定
Syntax = { Production } . Production = production_name "=" [ Expression ] "." . Expression = Term { "|" Term } . Term = Factor { Factor } . Factor = production_name | token [ "…" token ] | Group | Option | Repetition . Group = "(" Expression ")" . Option = "[" Expression "]" . Repetition = "{" Expression "}" .
指令由詞彙和以下運算子建構而成,且優先順序逐漸遞增
| alternation () grouping [] option (0 or 1 times) {} repetition (0 to n times)
小寫開頭的指令名稱用於識別詞彙 (終端) 符號。非終端符號使用大小寫組合。詞彙符號以雙引號 ""
或反引號 ``
括起來。
a … b
形式表示從 a
到 b
的字元集合,作為替代詞。水平省略號 …
亦在規範的其他地方非正式表示未進一步指定的各種列舉或程式碼片段。字元 …
(而非三個字元 ...
)並非 Go 語言的符號。
像是 [前往 Go 1.xx] 連結形式表示所述的語言功能(或部分功能)已於語言版本 1.xx 修改或新增,因此最少必須具備該語言版本才能執行組建。詳細資訊請參閱 附錄 中的 已連結區段。
原始程式碼表示法
原始程式碼是以 UTF-8 編碼的 Unicode 文字。文字並非規範化,因此單一加重音的編碼點與由重音和字母組合而成的相同字元有所不同;這些被視為兩個編碼點。為簡化起見,本文件將使用非限定詞彙「字元」來表示原始文字中的 Unicode 編碼點。
各編碼點皆有別;例如,大寫和 p 小寫字母是不同的字元。
實作限制:為了與其他工具相容,編譯器可能會禁止原始文字中出現 NUL 字元(U+0000)。
實作限制:為了與其他工具相容,編譯器可能會略過原始文字中,第一個 Unicode 編碼點為 UTF-8 編碼的位元組順序標記 (U+FEFF)。原始程式碼中的其他部分可能會禁止出現位元組順序標記。
字元
下列術語用於表示特定的 Unicode 字元類別
newline = /* the Unicode code point U+000A */ . unicode_char = /* an arbitrary Unicode code point except newline */ . unicode_letter = /* a Unicode code point categorized as "Letter" */ . unicode_digit = /* a Unicode code point categorized as "Number, decimal digit" */ .
在 Unicode 8.0 標準 的第 4.5 節「一般類別」中定義了一組字元類別。Go 將任何 Lu、Ll、Lt、Lm 或 Lo 字母類別中的字元視為 Unicode 字母,而將數字類別 Nd 中的字元視為 Unicode 數字。
字母和數字
底線字元 _
(U+005F) 被視為小寫字母。
letter = unicode_letter | "_" . decimal_digit = "0" … "9" . binary_digit = "0" | "1" . octal_digit = "0" … "7" . hex_digit = "0" … "9" | "A" … "F" | "a" … "f" .
語法元件
註解
註解可用作程式說明。共有兩個格式
-
行註解:以字元順序
//
開頭,並於行程尾端結束。 -
一般註解:以字元順序
/*
開頭,並於第一個後續字元順序*/
結束。
註解無法從 rune 或 字串文字 中開始,或在註解內開始。不包含換行符號的一般註解會視為空白。其他任何註解會視為換行符號。
詞彙
符記組成 Go 語言的詞彙。有四種類別:識別碼、關鍵字、運算子及標點符號和文字。空白由空格(U+0020)、水平定位標籤(U+0009)、回車(U+000D)和換行(U+000A)組成,除了分隔本來會組合成單一符記的符記外,否則會被忽略。此外,換行或檔案結束可能會觸發插入分號。在將輸入分解為符記時,下一個符記是組成有效符記的最長字元序列。
分號
正式的語法在許多產生式中使用分號 ";"
作為終結子。Go 程式可以使用以下兩個規則省略大多數這些分號
- 在將輸入分解為符記時,如果符記是以下項目,則會在該行最後一個符記之後立即自動在符記串流中插入分號
- 若要讓複雜陳述式佔用單一行,則可以在閉合的
")"
或"}"
之前省略分號。
為了反映慣用語法,本文件中程式碼範例會使用這些規則省略分號。
識別碼
識別碼命名程式實體,例如變數和類型。識別碼是由一個或多個字母和數字組成的序列。識別碼中的第一個字元必須是字母。
identifier = letter { letter | unicode_digit } .
a _x9 ThisVariableIsExported αβ
有些識別碼是預先宣告的識別碼。
關鍵字
下列關鍵字為保留字,不可用作識別碼。
break default func interface select case defer go map struct chan else goto package switch const fallthrough if range type continue for import return var
運算子和標點符號
下列字元序列代表運算子(包括賦值運算子)和標點符號 [Go 1.18]
+ & += &= && == != ( ) - | -= |= || < <= [ ] * ^ *= ^= <- > >= { } / << /= <<= ++ = := , ; % >> %= >>= -- ! ... . : &^ &^= ~
整數文字
整數字面值是一連串代表整數常數的數字。一個選用的字首設定一個非十進位的基底:二進制的0b
或0B
、八進制的0
、0o
或0O
,以及十六進制的0x
或0X
[Go 1.13]。一個單獨的0
被視為十進位的零。在十六進位字面值中,字母a
到f
和A
到F
代表值 10 到 15。
為了可讀性,底線字元_
可以在基底字首後或連續數字間出現;這種底線不會改變字面值的值。
int_lit = decimal_lit | binary_lit | octal_lit | hex_lit . decimal_lit = "0" | ( "1" … "9" ) [ [ "_" ] decimal_digits ] . binary_lit = "0" ( "b" | "B" ) [ "_" ] binary_digits . octal_lit = "0" [ "o" | "O" ] [ "_" ] octal_digits . hex_lit = "0" ( "x" | "X" ) [ "_" ] hex_digits . decimal_digits = decimal_digit { [ "_" ] decimal_digit } . binary_digits = binary_digit { [ "_" ] binary_digit } . octal_digits = octal_digit { [ "_" ] octal_digit } . hex_digits = hex_digit { [ "_" ] hex_digit } .
42 4_2 0600 0_600 0o600 0O600 // second character is capital letter 'O' 0xBadFace 0xBad_Face 0x_67_7a_2f_cc_40_c6 170141183460469231731687303715884105727 170_141183_460469_231731_687303_715884_105727 _42 // an identifier, not an integer literal 42_ // invalid: _ must separate successive digits 4__2 // invalid: only one _ at a time 0_xBadFace // invalid: _ must separate successive digits
浮點數字面值
浮點數字面值是一個浮點數常數的十進位或十六進位表示法。
一個十進位浮點數字面值包含一個整數部分(十進位數字)、一個小數點、一個小數部分(十進位數字),以及一個指數部分(e
或E
後接一個選用的正負號和十進位數字)。整數部分或小數部分可以省略其一;小數點或指數部分可以省略其一。一個指數值 exp 將尾數(整數部分和小數部分)縮放 10exp。
一個十六進位浮點數字面值包含一個0x
或0X
字首、一個整數部分(十六進位數字)、一個基數點、一個小數部分(十六進位數字),以及一個指數部分(p
或P
後接一個選用的正負號和十進位數字)。整數部分或小數部分可以省略其一;基數點也可以省略,但指數部分是必要的。(此語法符合 IEEE 754-2008 §5.12.3 中給出的語法。)一個指數值 exp 將尾數(整數部分和小數部分)縮放 2exp [Go 1.13]。
為了可讀性,底線字元_
可以在基底字首後或連續數字間出現;這種底線不會改變字面值的值。
float_lit = decimal_float_lit | hex_float_lit . decimal_float_lit = decimal_digits "." [ decimal_digits ] [ decimal_exponent ] | decimal_digits decimal_exponent | "." decimal_digits [ decimal_exponent ] . decimal_exponent = ( "e" | "E" ) [ "+" | "-" ] decimal_digits . hex_float_lit = "0" ( "x" | "X" ) hex_mantissa hex_exponent . hex_mantissa = [ "_" ] hex_digits "." [ hex_digits ] | [ "_" ] hex_digits | "." hex_digits . hex_exponent = ( "p" | "P" ) [ "+" | "-" ] decimal_digits .
0. 72.40 072.40 // == 72.40 2.71828 1.e+0 6.67428e-11 1E6 .25 .12345E+5 1_5. // == 15.0 0.15e+0_2 // == 15.0 0x1p-2 // == 0.25 0x2.p10 // == 2048.0 0x1.Fp+0 // == 1.9375 0X.8p-0 // == 0.5 0X_1FFFP-16 // == 0.1249847412109375 0x15e-2 // == 0x15e - 2 (integer subtraction) 0x.p1 // invalid: mantissa has no digits 1p-2 // invalid: p exponent requires hexadecimal mantissa 0x1.5e-2 // invalid: hexadecimal mantissa requires p exponent 1_.5 // invalid: _ must separate successive digits 1._5 // invalid: _ must separate successive digits 1.5_e1 // invalid: _ must separate successive digits 1.5e_1 // invalid: _ must separate successive digits 1.5e1_ // invalid: _ must separate successive digits
虛數字面值
一個虛數字面值代表一個複數常數的虛數部分。它包含一個整數或浮點數字面值後接小寫字母i
。虛數字面值的值是各自整數或浮點數字面值乘以虛數單位i的值 [Go 1.13]。
imaginary_lit = (decimal_digits | int_lit | float_lit) "i" .
為了向後相容性,一個虛數字面值的整數部分如果完全包含十進位數字(並且可能包含底線),則被視為十進位整數,即使它以一個前導0
開始。
0i 0123i // == 123i for backward-compatibility 0o123i // == 0o123 * 1i == 83i 0xabci // == 0xabc * 1i == 2748i 0.i 2.71828i 1.e+0i 6.67428e-11i 1E6i .25i .12345E+5i 0x1p-2i // == 0x1p-2 * 1i == 0.25i
符文字面值
盧恩字元(rune)文字代表 盧恩字元常數,一個識別 UTF-8 編碼點的整數值。盧恩字元文字以一個或多個字元表示,並以單引號括住,如同 'x'
或 '\n'
。在引號內,任何字元都可以出現,除了換行符和未轉義的單引號。單引號字元代表字元本身的 UTF-8 值,而由反斜線開頭的多字元序列則使用不同格式來編碼值。
最簡單的形式代表引號內單一字元;由於 Go 原始文件文字是 UTF-8 編碼的 UTF-8 字元,多個 UTF-8 編碼位元組可以代表單一整數值。例如,字面值 'a'
持有一個單一位元組,表示字面值 a
,UTF-8 U+0061,值 0x61
,而 'ä'
持有兩個位元組(0xc3
0xa4
),表示字面值 a
-分音符,U+00E4,值 0xe4
。
許多反斜線跳脫字元允許任意值作為 ASCII 文字編碼。有四種方法可以將整數值表示成數字常數:\x
後面接著兩個十六進位數字;\u
後面接著四個十六進位數字;\U
後面接著八個十六進位數字,和一個反斜線 \
後面接著三個八進位數字。在每種情況下,字面值的值是由數字在對應進位制中所表示的值。
儘管這些表示法都會產生整數,但它們有不同的有效範圍。八進位跳脫字元必須表示介於 0 到 255(含)之間的值。依結構來看,十六進位跳脫字元滿足此條件。跳脫字元 \u
和 \U
代表 UTF-8 編碼點,因此其中一些值是非法的,特別是那些超過 0x10FFFF
和代理項的部分。
在反斜線之後,某些單字元跳脫字元表示特殊值。
\a U+0007 alert or bell \b U+0008 backspace \f U+000C form feed \n U+000A line feed or newline \r U+000D carriage return \t U+0009 horizontal tab \v U+000B vertical tab \\ U+005C backslash \' U+0027 single quote (valid escape only within rune literals) \" U+0022 double quote (valid escape only within string literals)
在盧恩字元文字中,反斜線後方的一個無法辨識的字元是非法的。
rune_lit = "'" ( unicode_value | byte_value ) "'" . unicode_value = unicode_char | little_u_value | big_u_value | escaped_char . byte_value = octal_byte_value | hex_byte_value . octal_byte_value = `\` octal_digit octal_digit octal_digit . hex_byte_value = `\` "x" hex_digit hex_digit . little_u_value = `\` "u" hex_digit hex_digit hex_digit hex_digit . big_u_value = `\` "U" hex_digit hex_digit hex_digit hex_digit hex_digit hex_digit hex_digit hex_digit . escaped_char = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | "'" | `"` ) .
'a' 'ä' '本' '\t' '\000' '\007' '\377' '\x07' '\xff' '\u12e4' '\U00101234' '\'' // rune literal containing single quote character 'aa' // illegal: too many characters '\k' // illegal: k is not recognized after a backslash '\xa' // illegal: too few hexadecimal digits '\0' // illegal: too few octal digits '\400' // illegal: octal value over 255 '\uDFFF' // illegal: surrogate half '\U00110000' // illegal: invalid Unicode code point
字串文字
字串字面值代表 字串常數,其來自於連接一系列字元的結果。有兩種形式:原始字串文字和已詮釋字串文字。
原始字串文字是反引號之間的字元序列,如同 `foo`
。在引號內,任何字元都可以出現,除了反引號。原始字串字面值的值是由引號之間未經詮釋(隱含 UTF-8 編碼)字元所組成的字串;特別是,反斜線沒有特別意義,而且字串可能會包含換行符。原始字串文字內的回車字元('\r')從原始字串值中捨棄。
解譯字串文字是雙引號中的字元序列,如 "bar"
。在引號內,任何字元都可以出現,除了換行字元和未跳脫的雙引號。引號之間的文字組成文字的值,以反斜線跳脫字元,這些字元會被解譯,就像在 符文文字 中(除了 \'
是非法的但 \"
是合法的),並有相同的限制。三碼八進制 (\
nnn) 和兩碼十六進制 (\x
nn) 跳脫代表結果字串的單獨位元組;所有其他跳脫代表個別字元的 UTF-8 編碼(可能的位元組)。因此在字串文字內,\377
和 \xFF
代表單一位元組的值 0xFF
=255,而 ÿ
、 \u00FF
、 \U000000FF
和 \xc3\xbf
代表 U+00FF 字元的 UTF-8 編碼的兩個位元組 0xc3
0xbf
。
string_lit = raw_string_lit | interpreted_string_lit . raw_string_lit = "`" { unicode_char | newline } "`" . interpreted_string_lit = `"` { unicode_value | byte_value } `"` .
`abc` // same as "abc" `\n \n` // same as "\\n\n\\n" "\n" "\"" // same as `"` "Hello, world!\n" "日本語" "\u65e5本\U00008a9e" "\xff\u00FF" "\uD800" // illegal: surrogate half "\U00110000" // illegal: invalid Unicode code point
這些範例都代表相同的字串
"日本語" // UTF-8 input text `日本語` // UTF-8 input text as a raw literal "\u65e5\u672c\u8a9e" // the explicit Unicode code points "\U000065e5\U0000672c\U00008a9e" // the explicit Unicode code points "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e" // the explicit UTF-8 bytes
如果原始碼將字元表示為兩個代碼點,例如包含變音符號和字母的組合形式,將其放在符文文字中時會產生錯誤(它不是單一代碼點),如果放在字串文字中,會顯示為兩個代碼點。
常數
有布林常數、符文常數、整數常數、浮點常數、複數常數和字串常數。符文、整數、浮點和複數常數統稱為數字常數。
常數值由 符文、整數、浮點、虛數 或 字串 文字表示,表示常數的識別碼、常數表示式、結果為常數的 轉換,或針對常數參數套用一些內建函數(如 min
或 max
)、對 特定值 套用 unsafe.Sizeof
、對 一些表示式 套用 cap
或 len
、對複數常數套用 real
和 imag
,以及對數字常數套用 complex
。布林真值由預先宣告的常數 true
和 false
表示。預先宣告的識別碼 iota 表示整數常數。
一般而言,複數常數是 常數表示式 的形式,並在該章節中討論。
數字常數表示任意精度的確切值,且不會發生溢位。因此,沒有常數表示 IEEE 754 負零、無限,和非數值的值。
常數可以為 已輸入 或未輸入。文字常數、true
、false
、iota
,和只包含未輸入常數運算項的特定 常數運算式 為未輸入。
一種常數可以透過 常數宣告 或 轉換明確指定型別,或是在 變數宣告 或 指定陳述 中使用時隱式指定,或在 運算式 中作為運算項。如果無法將常數值 表示 為特定型別的值,則為錯誤。如果型別為型別參數,則將常數轉換為型別參數的非常數值。
未輸入的常數具有預設型別,為在需要輸入值的情況下,常數會隱式轉換成的型別,例如在 i := 0
的 短變數宣告 中,沒有明確的型別。未輸入常數的預設型別分別為 bool
、rune
、int
、float64
、complex128
或 string
,依序取決於它是否為布林值、符號、整數、浮點數、複數或字串常數。
執行限制:雖然數字常數在語言中具有任意精度,但編譯器可以使用具有有限精度之內部表述來執行。也就是說,每個執行都必須
- 以至少 256 位元來表示整數常數。
- 以小數點右側至少 256 位元,和符號二進位指數至少 16 位元,來表示浮點常數,包括複數常數的部分。
- 如果無法精確表示整數常數,則傳回錯誤。
- 如果無法表示浮點數或複數常數,則傳回溢位錯誤。
- 如果無法表示浮點數或複數常數,則依據精度的限制四捨五入到最接近的可表示常數。
這些需求適用於常數運算式文字常數,以及評估 常數運算式 的結果。
變數
變數為用於儲存值的儲存位置。可允許值的集合由變數的型別決定。
變數宣告或函數參數和結果宣告,函數宣告或函數文字的簽章,為已命名的變數預留儲存空間。呼叫建置函數 new
或取得 複合文字 的位址,在執行時期為變數配置儲存空間。此類匿名變數是透過 (可能為隱式的) 指標間接存取 來參照。
陣列、區塊、結構型態的結構化 變數,有可個別 存取 的元素和欄位。每個此類元素就像一個變數。
變數的靜態類型 (或單純的類型) 是其宣告中給定的類型、new
呼叫或複合文字中提供的類型、或結構化變數元素的類型。介面類型變數也有明確的動態類型,這是執行時期指派給變數的值的 (非介面) 類型 (除非此值是預先宣告的識別碼 nil
,其沒有類型)。動態類型可能在執行時期變更,但儲存在介面變數中的值永遠可以 指派 給變數的靜態類型。
var x interface{} // x is nil and has static type interface{} var v *T // v has value nil, static type *T x = 42 // x has value 42 and dynamic type int x = v // x has value (*T)(nil) and dynamic type *T
透過在 表達式 中參照變數,來擷取其值;它是 指派 給該變數的最後一個值。如果變數尚未指派值,其值是其類型的 零值。
類型
類型決定一組值,以及這些值的特定運算和方法。若類型有一個類型名稱,可以使用來表示類型,若類型是泛型,名稱後必須有 類型引數。也可以使用類型文字來指定類型,其會以現有的類型組合成一個類型。
Type = TypeName [ TypeArgs ] | TypeLit | "(" Type ")" . TypeName = identifier | QualifiedIdent . TypeArgs = "[" TypeList [ "," ] "]" . TypeList = Type { "," Type } . TypeLit = ArrayType | StructType | PointerType | FunctionType | InterfaceType | SliceType | MapType | ChannelType .
程式語言 預先宣告 一些類型名稱。其他則是使用 類型宣告 或 類型參數清單 引進的。複合類型—陣列、結構、指標、函數、介面、區塊、映射和管道類型—可以使用類型文字來建置。
預先宣告類型、定義類型和類型參數稱為命名類型。如果別名宣告中給定的類型是命名類型的,則別名表示命名類型的。
布林類型
布林類型 表示由預先宣告常數 true
和 false
表示的布林真值組。預先宣告的布林類型是 bool
;其為 已定義類型。
數值類型
整數、浮點或複數類型分別表示整數、浮點或複數值組。其會一起稱為數值類型。預先宣告、與架構無關的數值類型為
uint8 the set of all unsigned 8-bit integers (0 to 255) uint16 the set of all unsigned 16-bit integers (0 to 65535) uint32 the set of all unsigned 32-bit integers (0 to 4294967295) uint64 the set of all unsigned 64-bit integers (0 to 18446744073709551615) int8 the set of all signed 8-bit integers (-128 to 127) int16 the set of all signed 16-bit integers (-32768 to 32767) int32 the set of all signed 32-bit integers (-2147483648 to 2147483647) int64 the set of all signed 64-bit integers (-9223372036854775808 to 9223372036854775807) float32 the set of all IEEE 754 32-bit floating-point numbers float64 the set of all IEEE 754 64-bit floating-point numbers complex64 the set of all complex numbers with float32 real and imaginary parts complex128 the set of all complex numbers with float64 real and imaginary parts byte alias for uint8 rune alias for int32
n 位元整數的值為寬 n 位元,並使用 二補數運算 來表示。
同時也有一組依實作而定的整數型別大小的前置宣告
uint either 32 or 64 bits int same size as uint uintptr an unsigned integer large enough to store the uninterpreted bits of a pointer value
為了避免可攜性的問題,所有的數值型別都是定義型別,因此彼此不同,除了byte
是uint8
的別名 外,而rune
則是int32
的別名。在不同的數值型別在表達式或指派中混合使用時,您必須進行明確的轉換。例如,int32
和int
並不是同一個型別,即使它們在特定的架構上擁有相同的大小。
字串型別
字串型別代表字串值的集合。字串值是一個(可能為空的)位元組序列。位元組的數量稱為字串的長度並且從不為負。字串是不可變的:一旦建立後,就不可能變更字串的內容。前置宣告的字串型別為string
;它是一個定義型別。
可以使用內建的函式 len
找出字串 s
的長度。如果字串是常數,則其長度為編譯時期常數。可以透過整數索引 0 到 len(s)-1
來存取字串的位元組。擷取此類元素的位址是非法的;如果 s[i]
是字串中第 i
個位元組,則 &s[i]
無效。
陣列型別
陣列是單一型別的編號元素序列,稱為元素型別。元素的數量稱為陣列的長度,且從不為負。
ArrayType = "[" ArrayLength "]" ElementType . ArrayLength = Expression . ElementType = Type .
長度是陣列型別的一部分;它必須評估為非負的常數,其可以由int
型別的數值表示。可以使用內建函式 len
找出陣列 a
的長度。可以使用整數索引 0 到 len(a)-1
來定址元素。陣列型別始終為一維,但可以組合成多維型別。
[32]byte [2*N] struct { x, y int32 } [1000]*float64 [3][5]int [2][2][2]float64 // same as [2]([2]([2]float64))
陣列型別 T
可能不會包含型別為 T
的元素,或包含T
作為組成部分的型別(無論是直接或間接),如果這些包含型別僅為陣列或結構型別。
// invalid array types type ( T1 [10]T1 // element type of T1 is T1 T2 [10]struct{ f T2 } // T2 contains T2 as component of a struct T3 [10]T4 // T3 contains T3 as component of a struct in T4 T4 struct{ f T3 } // T4 contains T4 as component of array T3 in a struct ) // valid array types type ( T5 [10]*T5 // T5 contains T5 as component of a pointer T6 [10]func() T6 // T6 contains T6 as component of a function type T7 [10]struct{ f []T7 } // T7 contains T7 as component of a slice in a struct )
切片型別
切片是基礎陣列連續區段的描述符,並提供存取該陣列中編號元素序列的方式。切片型別表示其元素型別的所有陣列切片的集合。元素的數量稱為切片的長度,且從不為負。未初始化切片的值為nil
。
SliceType = "[" "]" ElementType .
可以使用內建函式 len
找出切片 s
的長度;與陣列不同的是,此長度可在執行期間變更。可以使用整數索引 0 到 len(s)-1
來定址元素。指定元素的切片索引可能小於基礎陣列中相同元素的索引。
區塊經過初始化後,始終會與儲存其元素的底層陣列關聯。因此,區塊會與其陣列和相同陣列中的其他區塊共享儲存空間;相對而言,不同的陣列始終代表不同的儲存空間。
作為區塊基礎的陣列可能會延伸至區塊結尾之後。容量是衡量此延伸程度的一種方式:它是區塊長度加上超出區塊的陣列長度的總和;長度低於該容量的區塊可透過從原始區塊切片新的區塊來建立。區塊 a
的容量可以使用內建函式 cap(a)
找出。
給定元素類型 T
的新的初始化區塊值可以使用內建函式 make
建立,這會使用區塊類型和指定長度或容量(選擇性)的參數。使用 make
建立的區塊始終會配置一組新的隱藏陣列供傳回的區塊值參考。也就是說,執行
make([]T, length, capacity)
會產生等同於配置陣列和 切片 的區塊,因此以下兩個表達式相等
make([]int, 50, 100) new([100]int)[0:50]
與陣列類似,區塊永遠都是一維的,但可以複合建立高維度物件。在陣列陣列中,內部陣列根據結構長度總是一致;但在區塊陣列(或陣列區塊)中,內部長度可能動態變更。此外,內部區塊必須個別初始化。
結構類型
結構是一組具名元素,稱為欄位,每個元素都有名稱和類型。欄位名稱可以明確指定(IdentifierList)或隱式指定(EmbeddedField)。在結構內,非 空白 欄位名稱必須 唯一。
StructType = "struct" "{" { FieldDecl ";" } "}" . FieldDecl = (IdentifierList Type | EmbeddedField) [ Tag ] . EmbeddedField = [ "*" ] TypeName [ TypeArgs ] . Tag = string_lit .
// An empty struct. struct {} // A struct with 6 fields. struct { x, y int u float32 _ float32 // padding A *[]int F func() }
宣布有類型但沒有明確欄位名稱的欄位稱為嵌入欄位。必須將嵌入欄位指定為類型名稱 T
或指向非介面類型名稱 *T
的指標,而 T
本身不能是指標類型。不具限定的類型名稱將作為欄位名稱。
// A struct with four embedded fields of types T1, *T2, P.T3 and *P.T4 struct { T1 // field name is T1 *T2 // field name is T2 P.T3 // field name is T3 *P.T4 // field name is T4 x, y int // field names are x and y }
以下宣告違法,因為在結構類型中欄位名稱必須唯一
struct { T // conflicts with embedded field *T and *P.T *T // conflicts with embedded field T and *P.T *P.T // conflicts with embedded field T and *T }
結構 x
中嵌入欄位的欄位或 方法 f
會在 x.f
成為標示該欄位或方法 f
的合法 選擇器 時被稱為晉升。
晉升欄位作用如同結構中的普通欄位,但它們不能用作結構的 複合文字 中的欄位名稱。
給定一個結構類型 S
和一個 已命名類型 T
已提升的方法包含在結構的方法組中如下
- 如果
S
包含一個嵌入式欄位T
,那麼S
和*S
的 方法組 都包含接收器T
的提升方法。*S
的方法組也包含接收器*T
的提升方法。 - 如果
S
包含一個嵌入式欄位*T
,那麼S
和*S
的方法組都包含接收器T
或*T
的提升方法。
欄位宣告後可以跟一個可選字串文字 標籤,它會成為對應欄位宣告的所有欄位的屬性。一個空標籤字串等於沒有標籤。這些標籤會藉由 反射介面 讓它們可見,且參與結構的 類型身分 識別,但在其他情況下都被忽略。
struct { x, y float64 "" // an empty tag string is like an absent tag name string "any string is permitted as a tag" _ [4]byte "ceci n'est pas un champ de structure" } // A struct corresponding to a TimeStamp protocol buffer. // The tag strings define the protocol buffer field numbers; // they follow the convention outlined by the reflect package. struct { microsec uint64 `protobuf:"1"` serverIP6 uint64 `protobuf:"2"` }
結構類型 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 .
在參數或結果清單中,名稱(識別子清單)必須全部存在或全部不存在。如果存在,每個名稱代表指定類型的一個項目(參數或結果),而簽章中所有非 空白 的名稱都必須是 唯一的。如果不存在,每個類型代表該類型的一個項目。參數和結果清單總是包含在括弧中,除了如果只有一個未命名結果時,它可以寫成不包含括弧的類型。
函式簽章中的最後一個輸入參數可以有一個字首為 ...
的類型。一個有這樣參數的函式稱為可變引數,並且可以針對這個參數調用零個或多個參數。
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
中嵌入介面E
[Go 1.14]。T
的類型組是T
明確宣告的方法和T
嵌入式介面的類型組的交集。換言之,T
的類型組就是實作T
所有明確宣告的方法以及E
所有方法的所有型態的組 [Go 1.18]。
type Reader interface { Read(p []byte) (n int, err error) Close() error } type Writer interface { Write(p []byte) (n int, err error) Close() error } // ReadWriter's methods are Read, Write, and Close. type ReadWriter interface { Reader // includes methods of Reader in ReadWriter's method set Writer // includes methods of Writer in ReadWriter's method set }
type ReadCloser interface { Reader // includes methods of Reader in ReadCloser's method set Close() // illegal: signatures of Reader.Close and Close are different }
一般介面
在最通用的形式中,介面元素也可能是任意類型的詞 T
,或指定底層類型 T
的形式 ~T
,或是詞的聯集 t1|t2|…|tn
[Go 1.18],與方法規格一起,這些元素可精確定義介面的類型集合,如下所示:
- 空介面的類型集合是非介面類型集合
- 非空介面的類型集合是其介面元素的類型集合的交集
- 方法規格的類型集合是包含此方法的方法集合的所有非介面類型的集合
- 非介面類型詞的類型集合僅包含該類型的集合
- 形式為
~T
的詞的類型集合是所有底層類型為T
的類型的集合 - 詞聯集
t1|t2|…|tn
的類型集合是這些詞類型集合的聯集
量化「所有非介面類型集合」不僅是指程式碼中宣告的所有(非介面)類型,而是所有可能程式的所有可能類型,因此是無限的。同樣地,給定實作特定方法的所有非介面類型集合,這些類型的交集 將包含此方法,即使程式碼中的所有類型總會將此方法與另一個方法配對
根據結構,介面的類型集合絕不會包含介面類型
// An interface representing only the type int. interface { int } // An interface representing all types with underlying type int. interface { ~int } // An interface representing all types with underlying type int that implement the String method. interface { ~int String() string } // An interface representing an empty type set: there is no type that is both an int and a string. interface { int string }
在形式為 ~T
的詞中,T
的底層類型必須是其本身,而 T
不能是介面
type MyInt int interface { ~[]byte // the underlying type of []byte is itself ~MyInt // illegal: the underlying type of MyInt is not MyInt ~error // illegal: error is an interface }
聯集元素表示類型集合的聯集
// The Float interface represents all floating-point types // (including any named types whose underlying types are // either float32 or float64). type Float interface { ~float32 | ~float64 }
在形式為 T
或 ~T
的詞中,T
類型不能是 類型參數,且所有非介面詞的類型集合必須成對不相交(類型集合的成對交集必須為空集)。給定類型參數 P
interface { P // illegal: P is a type parameter int | ~P // illegal: P is a type parameter ~int | MyInt // illegal: the type sets for ~int and MyInt are not disjoint (~int includes MyInt) float32 | Float // overlapping type sets but Float is an interface }
實作限制:聯集(包含多個詞)不能包含 預設識別碼 comparable
或指定方法的介面,或繫入 comparable
或指定方法的介面
不是 基本 的介面只能用作類型約束,或用作其他用作約束的介面元素。它們不能是值或變數的類型,也不能是其他非介面類型的組成部分
var x Float // illegal: Float is not a basic interface var x interface{} = Float(nil) // illegal type Floatish struct { f Float // illegal }
介面類型 T
不能繫入類型元素,即不包含或繫入 T
,無論是直接或間接。
// illegal: Bad may not embed itself type Bad interface { Bad } // illegal: Bad1 may not embed itself using Bad2 type Bad1 interface { Bad2 } type Bad2 interface { Bad1 } // illegal: Bad3 may not embed a union containing Bad3 type Bad3 interface { ~int | ~string | Bad3 } // illegal: Bad4 may not embed an array containing Bad4 as element type type Bad4 interface { [10]Bad4 }
實作介面
類型 T
實作介面 I
,如果:
-
T
不是介面,且是I
的類型集合的元素;或 -
T
是介面,且T
的類型集合是I
的類型集合的子集
型別 T
實作介面,如果 T
實作這個介面。
Map 型別
Map 是由一組不重複的 _金鑰_ 編號,編號的型別稱為 _金鑰型別_,且編號所對應元素的型別稱為 _元素型別_。沒有初始化的 map 的值為 nil
。
MapType = "map" "[" KeyType "]" ElementType . KeyType = Type .
比較運算子 ==
和 !=
必須能為金鑰型別的運算元定義完整,因此金鑰型別不應為函式、map 或切片。如果金鑰型別為介面型別,則這些比較運算子必須能為動態金鑰值定義;若無法定義則會導致 執行時期恐慌。
map[string]int map[*T]struct{ x, y float64 } map[string]interface{}
Map 元素的數量稱為長度。對 map m
來說,可以使用內建函式 len
找出長度,且長度可以在執行期間改變。可以在執行期間使用 指派 新增元素,並使用 索引運算式 來擷取元素。可以使用內建函式 delete
和 clear
來移除元素。
可以使用以 map 型別和一個容量提示作為引數的內建函式 make
來建立一個新的空 map 值。
make(map[string]int) make(map[string]int, 100)
初始容量不會限制 map 的大小:map 會隨著儲存在其中的項目數量而擴充,除了 nil
map。nil
map 會視為空 map,但無法新增任何元素。
通道型別
通道提供一個機制,讓 並行執行的函式 能透過 傳送 和 接收 指定元素型別的值來進行通訊。未初始化的通道值為 nil
。
ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .
選用 <-
運算子可以指定通道是 _傳送_ 或 _接收_。如果指定方向,通道即為 _單向_,否則為 _雙向_。通道可以僅限傳送或僅限接收,透過 指派 或明確的 轉換 來限制。
chan T // can be used to send and receive values of type T chan<- float64 // can only be used to send float64s <-chan int // can only be used to receive ints
<-
運算子會與最左邊合法的 chan
關聯。
chan<- chan int // same as chan<- (chan int) chan<- <-chan int // same as chan<- (<-chan int) <-chan <-chan int // same as <-chan (<-chan int) chan (<-chan int)
可以使用以通道型別和一個選用容量作為引數的內建函式 make
來建立一個新的已初始化通道值。
make(chan int, 100)
容量(以元素數量表示)會設定通道緩衝的大小。如果容量為零或沒有指定,該通道就沒有緩衝,且僅在有傳送者和接收者同時準備好的情況下通訊才能成功。否則,該通道會使用緩衝,且在緩衝未滿(傳送)或未空(接收)的情況下通訊就能成功且不會發生封鎖。nil
通道永遠不會準備好通訊。
可以使用內建函式 close
來關閉管道。 接收運算式 的多值指定形式會報告在通道關閉之前,傳入的值是否已送出。
一個管道可以使用於 發送敘述、 接收運算 以及對內建函式 cap
和 len
的呼叫,這些行為可以由任意數量的常式執行而不需進一步的同步。管道可做為先進先出的佇列。例如,如果一個常式在管道中傳送值,而另一個常式接收這些值,則接收的值的順序與傳送的順序完全相同。
類型和值的屬性
底層類型
每個類型 T
都有一個底層類型:如果 T
是預先宣告的布林、數字或字串類型,或是一個類型字面,則對應的底層類型就是 T
本身。否則, T
的底層類型就是宣告中 T
所參考的類型之底層類型。對於類型參數來說,即為其類型約束的底層類型,而這種類型約束總是介面。
type ( A1 = string A2 = A1 ) type ( B1 string B2 B1 B3 []B1 B4 B3 ) func f[P any](x P) { … }
string
、 A1
、 A2
、 B1
和 B2
的底層類型是 string
。 []B1
、 B3
和 B4
的底層類型是 []B1
。 P
的底層類型是 interface{}
。
核心類型
每個非介面類型 T
都有核心類型,與 T
的底層類型是相同的。
介面 T
具有核心類型,如果符合以下條件之一
沒有其他介面有核心類型。
介面的核心類型會根據條件達成的情況而有所不同,有下列兩種
- 類型
U
;或者 - 如果
T
僅包含雙向管道,則類型為chan E
,或者根據當下方向性管道類型,類型為chan<- E
或<-chan E
。
根據定義,核心類型從來都不是已定義的類型、類型參數,或介面類型。
具有核心類型的介面範例
type Celsius float32 type Kelvin float32 interface{ int } // int interface{ Celsius|Kelvin } // float32 interface{ ~chan int } // chan int interface{ ~chan int|~chan<- int } // chan<- int interface{ ~[]*data; String() string } // []*data
不具有核心類型的介面範例
interface{} // no single underlying type interface{ Celsius|float64 } // no single underlying type interface{ chan int | chan<- string } // channels have different element types interface{ <-chan int | chan<- int } // directional channels have different directions
某些作業(切片表達式, append
和 copy
)依賴核心型態的稍微寬鬆形式,其中接受位元組切片和字串。具體來說,如果恰好有兩個型態,[]byte
和 string
,它們是介面 T
的型態集合中所有型態的基礎型態,則 T
的核心型態稱為 bytestring
。
具有 bytestring
核心型態的介面範例
interface{ int } // int (same as ordinary core type) interface{ []byte | string } // bytestring interface{ ~[]byte | myString } // bytestring
請注意,bytestring
不是真實型態;它不能用來宣告變數或組成其他型態。它的存在純粹是為了描述某些作業之行為,這些作業會從位元組序列讀取資料,該資料可能是位元組切片或字串。
型態識別
兩種型態不是相同就是不同。
命名型態 永遠不同於任何其他型態。否則,如果兩種型態的基礎型態文字在結構上相同,則它們相同;亦即,它們具有相同的文字結構,且對應的元件具有相同的型態。詳細說明
- 如果兩種陣列型態具有相同的元素型態和相同的陣列長度,則它們相同。
- 如果兩種切片型態具有相同的元素型態,則它們相同。
- 如果兩種結構型態具有相同的欄位順序,並且對應的欄位具有相同的名稱、相同的型態,且相同的標籤,則它們相同。未匯出來自不同套件的欄位名稱永遠不同。
- 如果兩種指標型態具有相同的基礎型態,則它們相同。
- 如果兩種函式型態具有相同的參數數目和回傳值,對應的參數和回傳值型態相同,且兩個函數都可變參數或都不是可變參數,則它們相同。參數和回傳值名稱不需要相符。
- 如果兩種介面型態定義相同的型態集合,則它們相同。
- 如果兩種映射型態具有相同的鍵和元素型態,則它們相同。
- 如果兩種通道型態具有相同的元素型態和相同的方向,則它們相同。
- 如果兩種實例化型態具有相同的已定義型態和所有型態參數,則它們相同。
有了這些宣告
type ( A0 = []string A1 = A0 A2 = struct{ a, b int } A3 = int A4 = func(A3, float64) *A0 A5 = func(x int, _ float64) *[]string B0 A0 B1 []string B2 struct{ a, b int } B3 struct{ a, c int } B4 func(int, float64) *B0 B5 func(x int, y float64) *A1 C0 = B0 D0[P1, P2 any] struct{ x P1; y P2 } E0 = D0[int, string] )
這些型態相同
A0, A1, and []string A2 and struct{ a, b int } A3 and int A4, func(int, float64) *[]string, and A5 B0 and C0 D0[int, string] and E0 []int and []int struct{ a, b *B5 } and struct{ a, b *B5 } func(x int, y float64) *[]string, func(int, float64) (result *[]string), and A5
B0
和 B1
不同,因為它們是由不同的 類型定義 所創建的新類型;func(int, float64) *B0
和 func(x int, y float64) *[]string
不同,因為 B0
不同於 []string
;而 P1
和 P2
不同是因為它們是不同的類型參數。D0[int, string]
和 struct{ x int; y string }
不同,因為前者是一個 實例化的 定義類型,而後者是一個類型字面(但它們仍然 可指派)。
可指派性
類型為 V
的值 x
是 可指派 給一個類型為 T
的 變數(「x
可指派給 T
」),如果適用下列條件之一
-
V
和T
是相同的。 -
V
和T
具有相同的 底層類型,但不是類型參數,且V
或T
至少有一個不是 已命名類型。 -
V
和T
是具有相同元素類型的通道類型,V
是雙向通道,且V
或T
至少有一個不是 已命名類型。 -
T
是介面類型,但不是類型參數,且x
實作T
。 -
x
是預先宣告的識別碼nil
,且T
是指標、函式、切片、映射、通道或介面類型,但不是類型參數。 -
x
是未命名 常數,對一個類型為T
的值 具有可表示性。
此外,如果 x
的類型 V
或 T
是類型參數,則在符合下列條件之一時,x
可指派給一個類型為 T
的變數
-
x
是預先宣告的識別碼nil
,T
是類型參數,且x
可指派給T
的類型組中的每個類型。 -
V
不是 已命名類型,T
是類型參數,且x
可指派給T
的類型組中的每個類型。 -
V
是類型參數,且T
不是已命名類型,且V
的類型組中的每個類型的值都可指派給T
。
可表示性
一個 常數 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
型別集合中每個型別的值來表示時,才能由型別 T
的值表示。
x T x is representable by a value of T because 'a' byte 97 is in the set of byte values 97 rune rune is an alias for int32, and 97 is in the set of 32-bit integers "foo" string "foo" is in the set of string values 1024 int16 1024 is in the set of 16-bit integers 42.0 byte 42 is in the set of unsigned 8-bit integers 1e10 uint64 10000000000 is in the set of unsigned 64-bit integers 2.718281828459045 float32 2.718281828459045 rounds to 2.7182817 which is in the set of float32 values -1e-1000 float64 -1e-1000 rounds to IEEE -0.0 which is further simplified to 0.0 0i int 0 is an integer value (42 + 0i) float32 42.0 (with zero imaginary part) is in the set of float32 values
x T x is not representable by a value of T because 0 bool 0 is not in the set of boolean values 'a' string 'a' is a rune, it is not in the set of string values 1024 byte 1024 is not in the set of unsigned 8-bit integers -1 uint16 -1 is not in the set of unsigned 16-bit integers 1.1 int 1.1 is not an integer value 42i float32 (0 + 42i) is not in the set of float32 values 1e1000 float64 1e1000 overflows to IEEE +Inf after rounding
方法集
型別的方法集會決定可在該型別的 運算元 上 呼叫 的方法。每個型別都有(可能為空)關聯的方法集
- 已 定義的型別
T
的方法集包含以接收者型別T
宣告的所有 方法。 - 指向已定義型別
T
的指標(其中T
不是指標或介面)的方法集是宣告接收者為*T
或T
的所有方法的集合。 - 一個 介面型別 的方法集是介面 型別集合 中每個型別的方法集的交集(結果的方法集通常只是介面中宣告的方法的集合)。
其中包含嵌入式欄位的結構(和指向結構的指標)會套用進一步的規則,如 結構型別 區段所述。任何其他型別的方法集都為空。
在方法集中,每個方法都必須具有 唯一 的非 空白 方法名稱。
區塊
區塊是在相符大括號內一長串的宣告和陳述(可能為空)。
Block = "{" StatementList "}" . StatementList = { Statement ";" } .
除了原始程式碼中的明確區塊,還有隱含的區塊
- 宇宙區塊包含所有 Go 原始文字。
- 每個 套件 都有一個套件區塊,其中包含該套件的所有 Go 原始文字。
- 每個檔案都有一個檔案區塊,其中包含該檔案中的所有 Go 原始文字。
- 每個 "if"、"for" 和 "switch" 陳述都視為在它自己的隱含區塊中。
- 在 "switch" 或 "select" 陳述中的每個子句都作用為一個隱含的區塊。
區塊會巢狀並影響 範圍。
宣告和範圍
宣告將非空白識別碼繫結到常數、類型、類型參數、變數、函式、標籤或 套件。程式中的每個識別碼都必須宣告。任何識別碼都不能在同一個區塊中宣告兩次,而且識別碼不能同時在檔案區塊與套件區塊中宣告。
空白識別碼可以在宣告時像其他識別碼一樣使用,但它不會產生繫結,因此並未宣告。在套件區塊中,識別碼init
只能用於 init
函式 宣告,且與空白識別碼一樣,並不會產生新的繫結。
Declaration = ConstDecl | TypeDecl | VarDecl . TopLevelDecl = Declaration | FunctionDecl | MethodDecl .
宣告識別碼的範圍指的是原始碼文字的範圍,其中識別碼表示特定常數、類型、變數、函式、標籤或套件。
Go使用 區塊 做為詞彙範圍
- 預先宣告識別碼的範圍為universe區塊。
- 在頂層(任何函式之外)宣告的識別碼,表示常數、類型、變數或函式(但不是方法)的範圍為套件區塊。
- 已匯入套件的套件名稱範圍為包含匯入宣告的檔案的檔案區塊。
- 表示方法接收器、函式參數或結果變數的識別碼範圍為函式主體。
- 表示函式類型參數或由方法接收器宣告的識別碼範圍,始於函式名稱之後且結束於函式主體末尾。
- 表示類型類型參數的識別碼範圍,始於類型名稱之後且結束於TypeSpec末尾。
- 在函式內宣告的常數或變數識別碼範圍,始於ConstSpec或VarSpec (短變數宣告的ShortVarDecl)結尾,且結束於最內層包含區塊的末尾。
- 在函式內宣告的類型識別碼範圍,始於TypeSpec中的識別碼且結束於最內層包含區塊的末尾。
在區塊中宣告的識別碼可以在內部區塊中重新宣告。在內部宣告的識別碼範圍內,它表示由內部宣告宣告的實體。
「套件宣告」並非宣告;套件名稱不會出現在任何範圍中。其用途旨在找出屬於同一個 套件 的檔案,以及指定匯入宣告的預設套件名稱。
標籤範圍
標籤由 標籤陳述 宣告,並用於 "break"、"continue" 和 "goto" 陳述中。定義從未使用過的標籤是非法的。與其他識別碼不同的是,標籤不屬於區塊範圍,也不會與非標籤的識別碼產生衝突。標籤的範圍是其宣告的函式的本體,且不包含任何巢狀函式的本體。
空白識別碼
空白識別碼 由底線字元 _
表示。它作為匿名佔位符,代替一般的(非空白)識別碼,並在 宣告、運算元 和 指派陳述 中具有特殊意義。
預先宣告的識別碼
下列識別碼在 universe 區塊 中隱含宣告 [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
外銷識別碼
識別碼可以外銷,以便從其他套件存取。如果識別碼符合下列兩項條件,則視為外銷:
所有其他識別碼並未外銷。
識別碼唯一性
在給定一組識別碼的情況下,如果識別碼與集合中的每個其他識別碼都不同,則稱此識別碼為唯一。如果識別碼拼寫不同,或出現在不同的 套件 中且並未 外銷,則兩個識別碼不同。否則,即視為相同。
常數宣告
常數宣告將一個識別碼清單(常數名稱)繫結到一個 常數運算 清單的值。識別碼數目必須等於運算數目,而左邊的第 n 個識別碼繫結到右邊第 n 個運算的值。
ConstDecl = "const" ( ConstSpec | "(" { ConstSpec ";" } ")" ) . ConstSpec = IdentifierList [ [ Type ] "=" ExpressionList ] . IdentifierList = identifier { "," identifier } . ExpressionList = Expression { "," Expression } .
若存在型別,所有常數將採用指定的型別,且表示式必須可指派至不可為型別參數之型別。若省略型別,常數將採用各對應表示式的個別型別。若表示式值為未分型的常數,宣告的常數仍然保持未分型,且常數識別字表示常數值。例如,若表示式是浮點數文字,常數識別字表示浮點數常數,即使文字的小數部分為零也不例外。
const Pi float64 = 3.14159265358979323846 const zero = 0.0 // untyped floating-point constant const ( size int64 = 1024 eof = -1 // untyped integer constant ) const a, b, c = 3, 4, "foo" // a = 3, b = 4, c = "foo", untyped integer and string constants const u, v float32 = 0, 3 // u = 0.0, v = 3.0
在括號內的 const
宣告清單中,除了第一個 ConstSpec 以外的任何一個都可省略表示式清單。此類空清單等同於文字置換第一個先前的非空表示式清單及其型別(若存在)。因此,省略表示式清單等同於重複先前的清單。識別字數目必須等於先前清單中的表示式數目。此機制與iota
常數產生器搭配使用,可輕易宣告序列值。
const ( Sunday = iota Monday Tuesday Wednesday Thursday Friday Partyday numberOfDays // this constant is not exported )
Iota
在常數宣告中,預先宣告的識別字 iota
表示連續的非分型整數常數。其值為該常數宣告中それぞれのConstSpec索引值,從零開始。可用於建構相關常數的集合。
const ( c0 = iota // c0 == 0 c1 = iota // c1 == 1 c2 = iota // c2 == 2 ) const ( a = 1 << iota // a == 1 (iota == 0) b = 1 << iota // b == 2 (iota == 1) c = 3 // c == 3 (iota == 2, unused) d = 1 << iota // d == 8 (iota == 3) ) const ( u = iota * 42 // u == 0 (untyped integer constant) v float64 = iota * 42 // v == 42.0 (float64 constant) w = iota * 42 // w == 84 (untyped integer constant) ) const x = iota // x == 0 const y = iota // y == 0
依定義,在同一個 ConstSpec 中多次使用的iota
都具有相同的值。
const ( bit0, mask0 = 1 << iota, 1<<iota - 1 // bit0 == 1, mask0 == 0 (iota == 0) bit1, mask1 // bit1 == 2, mask1 == 1 (iota == 1) _, _ // (iota == 2, unused) bit3, mask3 // bit3 == 8, mask3 == 7 (iota == 3) )
這個最後的範例應用到隱式重複最後一個非空表示式清單。
型別宣告
型別宣告將識別字(型別名稱)繫結至型別。型別宣告有兩種形式:別名宣告與型別定義。
TypeDecl = "type" ( TypeSpec | "(" { TypeSpec ";" } ")" ) . TypeSpec = AliasDecl | TypeDef .
別名宣告
別名宣告將識別字繫結至給定的型別 [Go 1.9]。
AliasDecl = identifier "=" Type .
在識別字的範圍內,它用作型別的別名。
type ( nodeList = []*Node // nodeList and []*Node are identical types Polar = polar // Polar and polar denote identical types )
型別定義
型別定義建立新的、相異的型別,具有與給定型別相同的基礎型別與作業,並將識別字(型別名稱)繫結於此型別。
TypeDef = identifier [ TypeParameters ] Type .
新的型別稱為已定義的型別。它與任何其他型別(包括創建它的型別)不同。
type ( Point struct{ x, y float64 } // Point and struct{ x, y float64 } are different types polar Point // polar and Point denote different types ) type TreeNode struct { left, right *TreeNode value any } type Block interface { BlockSize() int Encrypt(src, dst []byte) Decrypt(src, dst []byte) }
已定義的型別可以擁有相關聯的方法。它不會繼承繫結至給定型別的任何方法,但介面型別或複合型別元素的方法組保持不變。
// A Mutex is a data type with two methods, Lock and Unlock. type Mutex struct { /* Mutex fields */ } func (m *Mutex) Lock() { /* Lock implementation */ } func (m *Mutex) Unlock() { /* Unlock implementation */ } // NewMutex has the same composition as Mutex but its method set is empty. type NewMutex Mutex // The method set of PtrMutex's underlying type *Mutex remains unchanged, // but the method set of PtrMutex is empty. type PtrMutex *Mutex // The method set of *PrintableMutex contains the methods // Lock and Unlock bound to its embedded field Mutex. type PrintableMutex struct { Mutex } // MyBlock is an interface type that has the same method set as Block. type MyBlock Block
型別定義可用於定義不同的布林、數值或字串型別,並將方法與這些型別關聯。
type TimeZone int const ( EST TimeZone = -(5 + iota) CST MST PST ) func (tz TimeZone) String() string { return fmt.Sprintf("GMT%+dh", tz) }
若型別定義指定了型別參數,則型別名稱表示泛型型別。泛型型別在使用時必須實例化。
type List[T any] struct { next *List[T] value T }
在類型的定義中給定的類型不能是種類參數。
type T[P any] P // illegal: P is a type parameter func f[T any]() { type L T // illegal: T is a type parameter declared by the enclosing function }
通用類型也可能與某個方法關聯。在此情況下,方法接收器必須宣告與通用類型定義中存在的相同數量之類型參數。
// The method Len returns the number of elements in the linked list l. func (l *List[T]) Len() int { … }
類型參數宣告
類型參數清單宣告泛函式或類型宣告之「類型參數」。此類型參數清單就像一般的函式參數清單,除了類型參數名稱必須全部存在,而且清單是以方括號(而非括號)括起來 [Go 1.18]。
TypeParameters = "[" TypeParamList [ "," ] "]" . TypeParamList = TypeParamDecl { "," TypeParamDecl } . TypeParamDecl = IdentifierList TypeConstraint .
清單中所有非空白名稱一定是唯一的。每一個名稱都宣告一個類型參數,此為新的、不同的命名類型,可做為宣告中尚未知類型的佔位符。類型參數於泛函式或類型的實體化後,會以類型參數取代。
[P any] [S interface{ ~[]byte|string }] [S ~[]E, E any] [P Constraint[int]] [_ any]
正如每個普通函式參數都有自己的參數類型,每個類型參數都有對應的(元)類型,稱為其類型約束。
如果泛型類型的類型參數清單宣告具有約束條件 C
的單一類型參數 P
,會發生解析上的歧義,原因在於文字 P C
形成一個有效的表達式。
type T[P *C] … type T[P (C)] … type T[P *C|Q] … …
在這些罕見的情況下,類型參數清單無法與表達式區分,然後類型宣告會解析成陣列類型宣告。為了消歧義,請將約束條件嵌入介面或使用尾隨逗號。
type T[P interface{*C}] … type T[P *C,] …
類型參數也可以由與泛型類型關聯的方法宣告之接收器規格來宣告。
在泛型類型 T
的類型參數清單中,類型約束不能(直接或透過其他泛型類型的類型參數清單)參考 T
。
type T1[P T1[P]] … // illegal: T1 refers to itself type T2[P interface{ T2[int] }] … // illegal: T2 refers to itself type T3[P interface{ m(T3[int])}] … // illegal: T3 refers to itself type T4[P T5[P]] … // illegal: T4 refers to T5 and type T5[P T4[P]] … // T5 refers to T4 type T6[P int] struct{ f *T6[P] } // ok: reference to T6 is not in type parameter list
類型約束
類型約束是介面,用於定義個別類型參數的容許類型參數集合,並控制該類型參數值的支援運算 [Go 1.18]。
TypeConstraint = TypeElem .
如果約束條件是 interface{E}
形式的介面文字,其中 E
是內嵌的類型元素(而非方法),則為了方便性,可以在類型參數清單中省略外圍的 interface{ … }
。
[T []P] // = [T interface{[]P}] [T ~int] // = [T interface{~int}] [T int|string] // = [T interface{int|string}] type Constraint ~int // illegal: ~int is not in a type parameter list
預先宣告的介面類型comparable
表示所有非介面類型的集合,這些類型是嚴格可比較的 [Go 1.18]。
儘管非型別參數的介面會相容,但並非嚴格相容,因此它們不會實作 comparable
。不過,它們會滿足 comparable
。
int // implements comparable (int is strictly comparable) []byte // does not implement comparable (slices cannot be compared) interface{} // does not implement comparable (see above) interface{ ~int | ~string } // type parameter only: implements comparable (int, string types are strictly comparable) interface{ comparable } // type parameter only: implements comparable (comparable implements itself) interface{ ~int | ~[]byte } // type parameter only: does not implement comparable (slices are not comparable) interface{ ~struct{ any } } // type parameter only: does not implement comparable (field any is not strictly comparable)
以 comparable
和 (直接或間接) 嵌入 comparable
的介面只能用來做為型別限制。它們無法當做值或變數的型別,或者作為其他非介面的型別元件。
滿足型別限制
如果 T
是由 C
定義的型別集合成員,也就是說,如果 T
有實作C
,則型別引數 T
會滿足型別限制 C
。例外情況下,嚴格相容 的型別限制也可以由 相容 的型別引數滿足(不一定嚴格相容)[Go 1.20]。更確切地說
如果
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() }
因為限制滿足規則中的例外情況,比較型別參數型別的運算元可能會在執行階段引發 panic(即使相容的型別參數總是嚴格相容)。
變數宣告
變數宣告會建立一個或多個變數,並將相對應的識別字元與之連結,且賦予它們一個型別和初始值。
VarDecl = "var" ( VarSpec | "(" { VarSpec ";" } ")" ) . VarSpec = IdentifierList ( Type [ "=" ExpressionList ] | "=" ExpressionList ) .
var i int var U, V, W float64 var k = 0 var x, y float32 = -1, -2 var ( i int u, v, s = 2.0, 3.0, "bar" ) var re, im = complexSqrt(-1) var _, found = entries[name] // map lookup; only interested in "found"
如果提供一串運算式,則會根據賦值陳述式的規則,用運算式來初始化變數。否則,每個變數都會初始化為它的零值。
如果存在型別,則會為每個變數指定那個型別。否則,會為每個變數指定賦值中對應初始化值的那種型別。如果那個值是未指定型別的常數,則會先隱式地將其轉換成它預設的型別;如果它是未指定型別的布林值,則會先隱式地將其轉換為 bool
型別。無法使用預先宣告的 nil
值來初始化沒有明確型別的變數。
var d = math.Sin(0.5) // d is float64 var i = 42 // i is int var t, ok = x.(T) // t is T, ok is bool var n = nil // illegal
實作限制:編譯器可能會禁止在函數主體中宣告變數,只要那個變數從未使用過。
簡短變數宣告
簡短變數宣告使用語法
ShortVarDecl = IdentifierList ":=" ExpressionList .
這是正規 變數宣告 的簡寫,具有初始化表達式,但沒有類型。
"var" IdentifierList "=" ExpressionList .
i, j := 0, 10 f := func() int { return 7 } ch := make(chan int) r, w, _ := os.Pipe() // os.Pipe() returns a connected pair of Files and an error, if any _, y, _ := coord(p) // coord() returns three values; only interested in y coordinate
與正規變數宣告不同,簡短變數宣告可能會重新宣告變數,只要它們最初在同一個區塊(或是區塊是函式本體時,參數清單)中以相同的類型提前宣告,且至少有一個非 空白 變數是新的。因此,重新宣告只能出現在多變數簡短宣告中。重新宣告並不會引入新的變數;它只會為原始變數指定新的值。:=
左側的非空白變數名稱必須是 唯一的。
field1, offset := nextField(str, 0) field2, offset := nextField(str, offset) // redeclares offset x, y, x := 1, 2, 3 // illegal: x repeated on left side of :=
簡短變數宣告只能出現在函式中。在一些情況下,例如 "if"、「for」或 "switch" 敘述的初始化器,它們可以用來宣告區域暫時變數。
函式宣告
函式宣告會將一個識別碼(函式名稱)繫結到某個函式。
FunctionDecl = "func" FunctionName [ TypeParameters ] Signature [ FunctionBody ] . FunctionName = identifier . FunctionBody = Block .
如果函式的 簽章宣告結果參數,函式本體的敘述清單必須以 終結陳述 結束。
func IndexRune(s string, r rune) int { for i, c := range s { if c == r { return i } } // invalid: missing return statement }
如果函式宣告指定 類型參數,函式名稱表示「泛函式」。泛函式必須先 實例化 才能呼叫或用作值。
func min[T ~int|~float64](x, y T) T { if x < y { return x } return y }
沒有類型參數的函式宣告可以省略本體。此類宣告會提供函式簽章,由 Go 以外的工具實作,例如組合語言例程。
func flushICache(begin, end uintptr) // implemented externally
方法宣告
方法是具有 函式 和 接收器 的函式。方法宣告會將一個識別碼(方法名稱)繫結到某個方法,並將此方法與接收器的 基本類型 關聯起來。
MethodDecl = "func" Receiver MethodName Signature [ FunctionBody ] . Receiver = Parameters .
透過方法名稱前面的附加參數區段,指定接收器。該參數區段必須宣告單一非可變參數(接收器)。其類型必須是 已定義 的類型 T
,或是已定義類型 T
的指標,後面可能接著列在方括號中的類型參數名稱清單:[P1, P2, …]
。T
稱為接收器 基本類型。接收器基本類型不能為指標或介面類型,而且必須在與方法相同的套件中定義。方法稱為繫結到其接收器基本類型,而方法名稱僅於類型 T
或 *T
的 選擇器 中可見。
非 空白 接收器識別碼在方法簽章中必須 唯一。如果在方法本體中沒有參照接收器的值,它的識別碼可以在宣告中省略。相同的情況也適用於函式和方法的參數。
對於基本類型,繫結到其上的方法的非空白名稱必須唯一。如果基本類型是 結構類型,非空白方法和欄位的名稱必須是不同的。
對於已定義的類型 Point
,宣告如下:
func (p *Point) Length() float64 { return math.Sqrt(p.x * p.x + p.y * p.y) } func (p *Point) Scale(factor float64) { p.x *= factor p.y *= factor }
將方法 Length
和 Scale
,接收器類型為 *Point
,繫結到基本類型 Point
。
如果接收器基本類型是 類型參數,則接收器規格必須宣告對應的類型參數供方法使用。這使得類型參數可用於方法。語法上,這個類型參數宣告看起來像接收器基本類型的 實例化:類型引數必須是表示所宣告類型參數的識別碼,每個接收器基本類型類型參數一個。類型參數名稱不需與其在接收器基本類型定義中對應的參數名稱相符,且所有非空白參數名稱在接收器參數區段和方法簽章中都必須獨一無二。接收器類型參數限制由接收器基本類型定義隱含:對應的類型參數具有對應的限制。
type Pair[A, B any] struct { a A b B } func (p Pair[A, B]) Swap() Pair[B, A] { … } // receiver declares A, B func (p Pair[First, _]) First() First { … } // receiver declares First, corresponds to A in Pair
表達式
表達式透過對運算子及函數套用於運算元上來指定值的運算。
運算元
運算元表示表達式中的基本值。運算元可以是:文字、(可能的 限定)表示 常數、變數 或 函數 的(可能為 空白)非 空白 識別碼或括弧表達式。
Operand = Literal | OperandName [ TypeArgs ] | "(" Expression ")" . Literal = BasicLit | CompositeLit | FunctionLit . BasicLit = int_lit | float_lit | imaginary_lit | rune_lit | string_lit . OperandName = identifier | QualifiedIdent .
表示 類型函數 的運算元名稱後面可能會接著 類型引數 清單;所產生的運算元是 實例化的 函數。
實作限制:如果運算元的類型是 類型參數,且 類型集合 為空,編譯器不需要報告錯誤。實例化 會失敗,且所有實例化都將導致實例化位置的錯誤。
限定識別碼
限定識別碼 是使用套件名稱前綴限定的識別碼。套件名稱和識別碼都不可以是 空白。
QualifiedIdent = PackageName "." identifier .
限定識別碼存取在必須 導入 的不同套件中的識別碼。這個識別碼必須 匯出 並且宣告在那個套件的 套件區段。
math.Sin // denotes the Sin function in package math
複合文字
複合文字會在每次評估時建構新的複合值。它們由文字的類型跟一個大括號括起的元素清單組成。每個元素都可以選擇性使用對應的鍵作為前置詞。
CompositeLit = LiteralType LiteralValue . LiteralType = StructType | ArrayType | "[" "..." "]" ElementType | SliceType | MapType | TypeName [ TypeArgs ] . LiteralValue = "{" [ ElementList [ "," ] ] "}" . ElementList = KeyedElement { "," KeyedElement } . KeyedElement = [ Key ":" ] Element . Key = FieldName | Expression | LiteralValue . FieldName = identifier . Element = Expression | LiteralValue .
文字類型的 核心類型 T
必須是一個結構、陣列、切片或映射類型(當類型用做類型名稱給予時,語法會強制此限制)。元素和鍵的類型必須 可賦值 給類型 T
的特定欄位、元素和鍵類型;沒有其他轉換。鍵會針對結構文字解釋為欄位名稱、陣列和切片文字解釋為指標、以及針對映射文字解釋為鍵。對於映射文字,所有元素都必須具有鍵。指定多個具有相同欄位名稱或常數鍵值的元素會產生錯誤。有關非常數映射鍵,請參閱 評估順序 一節。
對於結構文字,下列規則適用
- 鍵必須是結構類型中已宣告的欄位名稱。
- 不包含任何鍵的元素清單必須列出每個結構欄位的元素,欄位的順序則依據宣告順序。
- 如果任何元素有鍵,則每個元素都必須有鍵。
- 包含鍵的元素清單不必針對每個結構欄位都有元素。遺漏的欄位會獲得該欄位的零值。
- 一個文字可以省略元素清單;此類文字會評估為其類型的零值。
- 為屬於不同套件的結構的非外埠欄位指定元素會產生錯誤。
有了這些宣告
type Point3D struct { x, y, z float64 } type Line struct { p, q Point3D }
可以寫成
origin := Point3D{} // zero value for Point3D line := Line{origin, Point3D{y: -4, z: 12.3}} // zero value for line.q.x
對於陣列和切片文字,下列規則適用
- 每個元素都有相關的整數指標,標示其在陣列中的位置。
- 有鍵元素會使用鍵作為其指標。鍵必須是非負面常數,可 表示 為類型
int
的值;如果鍵有類型,則其類型必須是 整數類型。 - 沒有鍵的元素會使用前一個元素的指標加一。如果第一個元素沒有鍵,則其指標為零。
取複合文字的位址 會產生一個指向特定 變數 的指標,用文字的值做初始化。
var pointer *Point3D = &Point3D{y: 1000}
請注意,切片或映射類型的 零值 與初始但空的相同類型值不同。因此,取空切片或映射複合文字的位址,其效果與使用 new 配置新的切片或映射值不同。
p1 := &[]int{} // p1 points to an initialized, empty slice with value []int{} and length 0 p2 := new([]int) // p2 points to an uninitialized slice with value nil and length 0
陣列文字的長度為文字類型中指定的長度。如果文字中提供的元素少於長度,則會將遺漏的元素設定為陣列元素類型的零值。在陣列的索引範圍之外提供元素索引值,是一個錯誤。表示法 ...
指定陣列長度等於最大元素索引加上 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」、「fro」或「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
(不是 套件名稱),選擇器運算式
x.f
標示值 x
(或有時為 *x
;請見下文)的欄位或方法 f
。識別碼 f
稱為(欄位或方法)選擇器;它不能是 空白識別碼。選擇器運算式的類型是 f
的類型。如果 x
是套件名稱,請參閱 合格識別碼 章節。
選擇器 f
可以表示類型 T
中的欄位或方法 f
,或者它可以指嵌在 T
中的一個 嵌入式欄位 的欄位或方法 f
。用來取得欄位 f
的嵌入式欄位數稱為在 T
中的深度。在 T
中宣告的欄位或方法 f
的深度為 0。在 T
中嵌在的欄位 A
中宣告的欄位或方法 f
的深度為 A
中 f
的深度再加上 1。
下列規則適用於選擇器
- 對於類型為
T
或*T
(其中T
不是指標或介面類型) 的值x
,x.f
表示在T
中深度最淺之處的欄位或方法,而該處存在著此類f
。如果深度最淺的f
不只有 一個,則選擇器表達式就不合法。 - 對於類型為
I
的值x
(其中I
為介面類型),x.f
表示x
的動態值的具有名稱f
的實際方法。如果I
的 方法集合 中沒有名稱為f
的方法,則選擇器表達式就不合法。 - 作為例外,如果
x
的類型是 定義的 指標類型且(*x).f
是表示欄位(而不是方法)的合法選擇器表達式,則x.f
是(*x).f
的簡稱。 - 在所有其他情況中,
x.f
都是不合法的。 - 如果
x
是指標類型且值為nil
,而x.f
表示結構欄位,則指派給或運算x.f
將會導致 執行時期驚慌。 - 如果
x
是介面類型且值為nil
,則 呼叫 或 運算 方法x.f
將會導致 執行時期驚慌。
例如,給定宣告
type T0 struct { x int } func (*T0) M0() type T1 struct { y int } func (T1) M1() type T2 struct { z int T1 *T0 } func (*T2) M2() type Q *T2 var t T2 // with t.T0 != nil var p *T2 // with p != nil and (*p).T0 != nil var q Q = p
可以寫成
t.z // t.z t.y // t.T1.y t.x // (*t.T0).x p.z // (*p).z p.y // (*p).T1.y p.x // (*(*p).T0).x q.x // (*(*q).T0).x (*q).x is a valid field selector p.M0() // ((*p).T0).M0() M0 expects *T0 receiver p.M1() // ((*p).T1).M1() M1 expects T1 receiver p.M2() // p.M2() M2 expects *T2 receiver t.M2() // (&t).M2() M2 expects *T2 receiver, see section on Calls
但下列是無效的
q.M0() // (*q).M0 is valid but not a field selector
方法表達式
如果 M
在類型 T
的 方法集合 中,則 T.M
是可以使用與 M
相同引數作為普通函式呼叫的函式,並在前面加上是由此方法接收者的其他引數。
MethodExpr = ReceiverType "." MethodName . ReceiverType = Type .
考慮一個包含兩個方法的結構類型 T
:Mv
(其接收者類型為 T
)、以及 Mp
(其接收者類型為 *T
)。
type T struct { a int } func (tv T) Mv(a int) int { return 0 } // value receiver func (tp *T) Mp(f float32) float32 { return 1 } // pointer receiver var t T
表達式
T.Mv
產生出一個與 Mv
等同的函式,但其明確的接收者作為第一個引數;其簽章為
func(tv T, a int) int
該函式可以使用明確的接收器正常呼叫,因此以下五個呼叫等效
t.Mv(7) T.Mv(t, 7) (T).Mv(t, 7) f1 := T.Mv; f1(t, 7) f2 := (T).Mv; f2(t, 7)
類似地,表達式
(*T).Mp
產生函式值,表示具有簽章的 Mp
func(tp *T, f float32) float32
對於具有值接收器的函式,可以衍生出具有明確指標接收器的函式,因此
(*T).Mv
產生函式值,表示具有簽章的 Mv
func(tv *T, a int) int
這樣的函式通過接收器轉址,建立一個值作為接收器傳遞給底層函式;函式不會覆寫在函式呼叫中傳遞的位址值。
最後一個情況,值接收器函式為指標接收器函式,這是違法的,因為指標接收器函式不在值類型的函式集合中。
從函式衍生的函式值以函式呼叫語法呼叫;接收器提供為呼叫的第一個引數。也就是說,給定 f := T.Mv
,f
以 f(t, 7)
呼叫,而不是 t.f(7)
。若要建立繫結接收器的函式,請使用 函式字面值 或 函式值。
從介面類型的函式衍生函式值是合法的。產生的函式採用該介面類型的明確接收器。
函式值
如果表達式 x
具有靜態類型 T
,且 M
在類型 T
的 函式集合 中,則 x.M
稱為函式值。函式值 x.M
是一個函式值,可以用作為 x.M
函式呼叫的相同引數呼叫。表達式 x
在函式值評估期間評估並儲存;儲存的副本隨後用作任何呼叫中的接收器,這些呼叫可能會稍後執行。
type S struct { *T } type T int func (t T) M() { print(t) } t := new(T) s := S{T: t} f := t.M // receiver *t is evaluated and stored in f g := s.M // receiver *(s.T) is evaluated and stored in g *t = 42 // does not affect stored receivers in f and g
類型 T
可以是介面或非介面類型。
如同以上 函式表達式 的說明,考慮一個具有兩個函式的結構類型 T
,Mv
(其接收器為類型 T
)和 Mp
(其接收器為類型 *T
)。
type T struct { a int } func (tv T) Mv(a int) int { return 0 } // value receiver func (tp *T) Mp(f float32) float32 { return 1 } // pointer receiver var t T var pt *T func makeT() T
表達式
t.Mv
產生類型
func(int) int
這兩個呼叫等效
t.Mv(7) f := t.Mv; f(7)
類似地,表達式
pt.Mp
產生類型
func(float32) float32
與 選擇器 一樣,使用指標參考非介面函式時,其值接收器會自動取消指標參照:pt.Mv
等效於 (*pt).Mv
。
與 函式呼叫 一樣,使用可定址值參考非介面函式時,其指標接收器會自動擷取該值:t.Mp
等效於 (&t).Mp
。
f := t.Mv; f(7) // like t.Mv(7) f := pt.Mp; f(7) // like pt.Mp(7) f := pt.Mv; f(7) // like (*pt).Mv(7) f := t.Mp; f(7) // like (&t).Mp(7) f := makeT().Mp // invalid: result of makeT() is not addressable
儘管以上範例使用非介面類型,但從介面類型值建立函式值也是合法的。
var i interface { M(int) } = myVal f := i.M; f(7) // like i.M(7)
索引表達式
形式的主要表達式
a[x]
表示由 x 索引,陣列元素、陣列指標、切片、字串或映射 a
的值。此值 x 分別稱為索引值或映射值。適用下列規則
若 a
不是映射或類型參數
- 此索引值
x
必須是未定型常數,或其 核心類型 必須是 整數 - 常數索引值必須是非負值,且 可由類型
int
的值表示 - 已給予未定型的常數索引值類型為
int
- 索引值
x
是在範圍內,如果0 <= x < len(a)
,否則為範圍外
對於 陣列類型 A
的 a
對於 指標到陣列類型 A
的 a
a[x]
是(*a)[x]
的簡寫
對於 切片類型 S
的 a
- 若 x 在執行階段超出範圍,將會發生 執行階段驚慌
a[x]
是索引值x
的切片元素,且a[x]
的類型為S
的元素類型
對於 字串類型 的 a
- 若字串
a
也是常數,則 常數 索引值必須在範圍內 - 若 x 在執行階段超出範圍,將會發生 執行階段驚慌
a[x]
是索引值x
的非常數位元組值,且a[x]
的類型為byte
- 無法指派給
a[x]
對於 映射類型 M
的 a
x
的類型必須 可指派 給M
的金鑰類型- 若映射包含金鑰為
x
的輸入條目,則a[x]
是金鑰為x
的映射元素,且a[x]
的類型為M
的元素類型 - 若映射為
nil
或不包含此輸入條目,則a[x]
是M
的元素類型的 零值
對於 類型參數類型 P
的 a
- 索引值表達式
a[x]
必須對P
類型設定中所有類型的值有效。 P
類型設定中所有類型的元素類型必須相同。在此背景下,字串類型的元素類型為byte
。- 若
P
的類型設定中有映射類型,則該類型設定中的所有類型都必須為映射類型,且各自的金鑰類型必須全部相同。 a[x]
為索引值x
的陣列、序列或字串元素,或是P
所建立的類型實例的「key」值為x
中的地圖元素,而a[x]
的類型為(相同的)元素類型。- 若
P
的類型集合包含字串類型,則a[x]
無法被指定給予。
否則,a[x]
會是違法的。
類型為 map[K]V
的地圖 a
中的索引式表達式用在 指定陳述式 或特殊形式的初始化可以
v, ok = a[x] v, ok := a[x] var v, ok = a[x]
產生額外的未類型化布林值。若地圖中有「key」值 x
,則 ok
的值為 true
,否則為 false
。
指定給予 nil
地圖的元素會造成 執行階段恐慌。
序列表達式
序列表達式從字串、陣列、陣列指標或序列建立一個子字串或序列。有兩種變體:一種是指定下限與上限的簡單形式;一種是同時指定容量上限的完整形式。
簡單序列表達式
主要表達式
a[low : high]
會建立一個子字串或序列。a
的 核心類型 必須是字串、陣列、陣列指標、序列或 bytestring
。索引值 low
和 high
會選擇操作數 a
中哪些元素會出現在結果中。結果的索引值從 0 開始,長度等於 high
- low
。將陣列 a
序列化之後
a := [5]int{1, 2, 3, 4, 5} s := a[1:4]
序列 s
的類型為 []int
、長度為 3、容量為 4、元素為
s[0] == 2 s[1] == 3 s[2] == 4
為方便起見,可以略過任意索引值。未提供的 low
索引值會預設為零;未提供的 high
索引值會預設為分割操作數的長度
a[2:] // same as a[2 : len(a)] a[:3] // same as a[0 : 3] a[:] // same as a[0 : len(a)]
若 a
是陣列指標,則 a[low : high]
的簡寫為 (*a)[low : high]
。
對於陣列或字串,只要 0
<= low
<= high
<= len(a)
,索引值就會處於範圍內,否則則超出範圍。對於序列,上限索引值會是序列容量 cap(a)
,而不是長度。常數 索引值必須是非負的,並且可以使用 int
類型的值來表示;對於陣列或常數字串,常數索引值也必須在範圍內。如果兩個索引值都是常數,則它們必須滿足 low <= high
。如果索引值在執行階段超出範圍,則會發生 執行階段恐慌。
除了 非類型字串 以外,如果被切片運算的運算元是字串或切片,切片運算的結果將會是非常數值,其類型與運算元相同。如果被切片運算的運算元是非類型字串,結果則是 string
類型的非常數值。如果被切片運算的運算元是陣列,它一定必須 可定址,切片運算的結果才會是具有和陣列相同的元素類型的切片。
如果有效切片運算式的被切片運算元是 nil
切片,結果將會是 nil
切片。否則,如果結果是切片,它會和運算元共用其基礎陣列。
var a [10]int s1 := a[3:7] // underlying array of s1 is array a; &s1[2] == &a[5] s2 := s1[1:4] // underlying array of s2 is underlying array of s1 which is array a; &s2[1] == &a[5] s2[1] = 42 // s2[1] == s1[2] == a[5] == 42; they all refer to the same underlying array element var s []int s3 := s[:0] // s3 == nil
完整切片運算式
主要表達式
a[low : high : max]
建構一個類型相同、長度相同,且元素與簡單切片運算式 a[low : high]
相同的切片。此外,它透過將結果切片的容量設定為 max - low
來控制結果切片的容量。只有第一個索引可以略過;預設值為0。a
的 核心類型 必須是陣列、指標陣列或切片(但不是字串)。在切片陣列 a
之後
a := [5]int{1, 2, 3, 4, 5} t := a[1:3:5]
切片 t
具有類型 []int
、長度 2、容量 4,以及元素
t[0] == 2 t[1] == 3
對於簡單切片運算式,如果 a
是指標陣列,a[low : high : max]
就是 (*a)[low : high : max]
的簡寫。如果被切片運算的運算元是陣列,它一定必須 可定址。
如果 0 <= low <= high <= max <= cap(a)
,索引就會在範圍內,否則就是超出範圍。常數 索引必須為非負數,且可以用 int
類型的值 表示;對於陣列,常數索引也必須在範圍內。如果有多個常數索引,存在的常數在相互對照時必須在範圍內。如果索引在執行階段超出範圍,就會發生 執行階段恐慌。
類型斷言
對於 介面類型(而不是 類型參數)的運算式 x
和類型 T
,主運算式
x.(T)
斷言 x
不是 nil
以及儲存在 x
中的值類型為 T
。符號 x.(T)
稱為類型斷言。
更準確地說,如果 T
不是介面類型,x.(T)
斷言 x
的動態類型與 T
類型 相同。在這種情況下,T
必須 實作 x
的(介面)類型;否則,類型斷言無效,因為 x
無法儲存 T
類型的值。如果 T
是介面類型,x.(T)
斷言 x
的動態類型 實作 介面 T
。
如果類型斷言成立,則表達式的值儲存在 `x` 中,其類型為 `T`。如果類型斷言不成立,則會產生〈a href="#Run_time_panics">執行時期錯誤〉。換句話說,即使在執行時期才知道 `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 … }
類型的斷言用於特殊形式的〈a href="#Assignment_statements">賦值陳述〉或初始化,
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
會產生一個額外的未執行 boolean 值。如果斷言成立,`ok` 的值為 `true`。否則為 `false`,而 `v` 的值是類型 `T` 的〈a href="#The_zero_value">零值〉。此情況不會產生〈a href="#Run_time_panics">執行時期錯誤〉。
呼叫
給定一個表達式 `f`,且其具有〈a href="#Core_types">核心類型〉 `F` 的〈a href="#Function_types">函數類型,
f(a1, a2, … an)
以參數 `a1, a2, … an` 來呼叫 `f`。除了特殊情況之外,參數必須是可以指定給 `F` 參數類型,且在呼叫函數之前會先評估為單一值表達式的〈a href="#Assignability">可指定值〉。表達式的類型是 `F` 的結果類型。方法呼叫類似,但方法本身會指定為接收器類型值上的選擇器。對於該方法。
math.Atan2(x, y) // function call var pt *Point pt.Scale(3.5) // method call with receiver pt
如果 `f` 表示通用函數,則必須在呼叫它或將其用作函數值之前先〈a href="#Instantiations">實例化〉它。
在函數呼叫中,函數值和參數將按照〈a href="#Order_of_evaluation">慣常順序〉進行評估。在評估之後,呼叫的參數將按值傳遞給函數,且呼叫的函數開始執行。函數的傳回參數會在函數傳回時按值傳遞回呼叫者。
呼叫 `nil` 函數值會導致〈a href="#Run_time_panics">執行錯誤〉。
特殊情況中,如果函數或方法 `g` 的傳回值數量相等且個別可以指定給另一個函數或方法 `f` 的參數,則 `f(g(parameters_of_g))` 呼叫會在將 `g` 的傳回值依序繫結到 `f` 的參數後呼叫 `f`。`f` 的呼叫不能包含 `g` 的呼叫以外的參數,且 `g` 至少必須有一個傳回值。如果 `f` 有最後一個 `...` 參數,則會指定 `g` 繫結常規參數後剩下的傳回值給它。
func Split(s string, pos int) (string, string) { return s[0:pos], s[pos:] } func Join(s, t string) string { return s + t } if Join(Split(value, len(value)/2)) != value { log.Panic("test fails") }
方法呼叫 `x.m()` 是有效的,如果 `x`(類型)的方法集包含 `m`,且參數清單可以指定給 `m` 的參數清單。如果 `x` 是〈a href="#Address_operators">可指定位址〉且 `&x` 的方法集包含 `m`,`x.m()` 是 `(&x).m()` 的簡寫
var p Point p.Scale(3.5)
沒有不同的方法類型,也沒有方法文字。
將參數傳遞給 `...` 參數
如果函式 f
是 變數,具備類型為 ...T
的最終參數 p
,則在 f
中,p
的類型等於 []T
類型。如果函式 f
被呼叫而沒有 p
的實際參數,就將 nil
傳遞到 p
。否則傳遞的數值是 []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
給定一組類型方程式,類型參數的求解對象是需要實例化的函數類型參數,且未提供明確類型參數。這些類型參數稱為bound類型參數。例如,在上述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
。
在考慮未輸入常數之前,類型推論會優先考慮從輸入操作數取得的類型資料。因此,推論分為兩階段進行
-
使用類型統一針對限定類型參數求解類型方程式。如果統一失敗,則類型推論失敗。
-
針對尚未推論出類型引數的每個邊界類型參數
Pk
,及針對收集到一個或多個與同一類型參數配對的(cj, Pk)
,以相同的方式判定所有這些配對中常數cj
的 常數種類,方式與 常數運算式 相同。Pk
的類型引數是判斷出來的常數種類的 預設類型。如果因為有衝突的常數種類而無法判斷常數種類,類型推論就會失敗。
如果這兩個階段之後仍然沒有找出所有類型引數,類型推論就會失敗。
如果兩個階段都成功結束,類型推論會針對每個邊界類型參數判定一個類型引數
Pk ➞ Ak
類型引數 Ak
可能是複合類型,包含其他邊界類型參數 Pk
作為元素類型(甚至可能只是另一個邊界類型參數)。在重複簡化的過程中,每個類型引數中的邊界類型參數會以這些類型參數的各自類型引數替換,直到每個類型引數都不再有邊界類型參數。
如果類型引數透過邊界類型參數對自己進行循環參照,簡化作業,進而類型推論就會失敗。否則,類型推論就成功結束。
類型統一化
類型推論透過 類型統一化 解決類型方程式。類型統一化遞迴比較方程式的 LHS 和 RHS 類型,其中任一類型或兩種類型都可能是或包含邊界類型參數,並尋找這些類型參數的類型引數,例如 LHS 和 RHS 相符(變得相同或可指派,視情況而定)。為此,類型推論維護一個映射,將邊界類型參數對應到推論出的類型引數;類型統一化過程中會諮詢和更新此映射。一開始,系統知道邊界類型參數,但映射是空的。在類型統一化過程中,如果推論出新的類型引數 A
,會將從類型參數對應到引數的對應 P ➞ A
加入映射中。反之,比較類型時,已知的類型引數(已具有映射條目的類型引數)會取代對應的類型參數。類型推論持續進行,映射會持續載入項目,直到所有方程式都已考量在內,或是統一化失敗為止。如果沒有任何統一化步驟失敗,且映射有每一個類型參數的條目,則類型推論就成功結束。
例如,針對具有邊界類型參數 P
的類型方程式
[10]struct{ elem P, list []P } ≡A [10]struct{ elem string; list []string }
類型推論從一個空的映射開始。統一首先比較 LHS 和 RHS 類型的最頂層結構。兩者都是長度相同的陣列,如果元素類型統一,則它們會統一。兩種元素類型都是結構;如果它們具有相同數量的具有相同名稱的欄位,並且欄位類型統一,則它們會統一。尚不知道 P
的類型引數(沒有映射條目),所以將 P
與 string
統一會將對應 P ➞ string
加入映射。統一 list
欄位的類型需要統一 []P
和 []string
,因此會統一 P
和 string
。 поскольку在这个时刻 P
的类型参数是已知的(有 P
的映射项),因此它的类型参数 string
会替换 P
。并且由于 string
与 string
相同,因此此统一步骤也会成功。现在,等号的 LHS 和 RHS 的统一已完成。类型推论成功,因为只有一个类型等式,没有统一步骤失败,并且映射已完全填充。
統一運用精確與寬鬆的統一組合,視二種型態是否必須相同、可指派相容或僅結構相等而定。各別的類型統一規則會在附錄中詳細說明。
對於 X ≡A Y
型式的等號,其中 X
和 Y
是參與指派(包括參數傳遞和回傳陳述式)的型態,最頂層的型態結構可能會寬鬆統一,但元素型態必須精確統一,以符合指派的規則。
對於 P ≡C C
型式的等號,其中 P
是型態參數,而 C
是其對應的約束,統一規則稍複雜。
- 假設
C
具有核心型態core(C)
,而P
具有已知的型態引數A
,則core(C)
與A
必須寬鬆統一。如果P
沒有已知的型態引數,而C
僅包含一個不是底層(波浪符號)型態的型態項目T
,統一會將對應P ➞ T
加入映射。 - 假設
C
沒有核心型態,而P
具有已知的型態引數A
,則A
必須具有(如果有的話)C
的所有方法,而對應的方法型態必須精確統一。
在從型態約束解決型態方程式時,解決一個方程式可能會推論出附加的型態引數,進而讓依賴於那些型態引數的其他方程式得以解決。型態推論會持續進行型態統一,只要推論出新的型態引數。
運算子
運算子會將運算元組合成表達式。
Expression = UnaryExpr | Expression binary_op Expression . UnaryExpr = PrimaryExpr | unary_op UnaryExpr . binary_op = "||" | "&&" | rel_op | add_op | mul_op . rel_op = "==" | "!=" | "<" | "<=" | ">" | ">=" . add_op = "+" | "-" | "|" | "^" . mul_op = "*" | "/" | "%" | "<<" | ">>" | "&" | "&^" . unary_op = "+" | "-" | "!" | "^" | "*" | "&" | "<-" .
比較會在 其他地方 說明。對其他二元運算子而言,除非運算涉及移位或未設定型態的 常數,否則運算元的型態必須要 相同。對於僅涉及常數的運算,請參閱 常數表達式 章節。
除了移位運算外,如果一個運算元是未設定型態的 常數,而且另一個運算元不是,則常數會自動 轉換 成另一個運算元的型態。
在移位表達式中,右運算元必須具有 整數型態 [Go 1.13] 或是一個未設定型態的常數,而且可以用 uint
型態的值 表示。如果非常數移位表達式的左運算元是不設定型態的常數,則會先將其自動轉換為用移位表達式單獨以左運算元取代後會產生的型態。
var a [1024]byte var s uint = 33 // The results of the following examples are given for 64-bit ints. var i = 1<<s // 1 has type int var j int32 = 1<<s // 1 has type int32; j == 0 var k = uint64(1<<s) // 1 has type uint64; k == 1<<33 var m int = 1.0<<s // 1.0 has type int; m == 1<<33 var n = 1.0<<s == j // 1.0 has type int32; n == true var o = 1<<s == 2<<s // 1 and 2 have type int; o == false var p = 1<<s == 1<<33 // 1 has type int; p == true var u = 1.0<<s // illegal: 1.0 has type float64, cannot shift var u1 = 1.0<<s != 0 // illegal: 1.0 has type float64, cannot shift var u2 = 1<<s != 1.0 // illegal: 1 has type float64, cannot shift var v1 float32 = 1<<s // illegal: 1 has type float32, cannot shift var v2 = string(1<<s) // illegal: 1 is converted to a string, cannot shift var w int64 = 1.0<<33 // 1.0<<33 is a constant shift expression; w == 1<<33 var x = a[1.0<<s] // panics: 1.0 has type int, but 1<<33 overflows array bounds var b = make([]byte, 1.0<<s) // 1.0 has type int; len(b) == 1<<33 // The results of the following examples are given for 32-bit ints, // which means the shifts will overflow. var mm int = 1.0<<s // 1.0 has type int; mm == 0 var oo = 1<<s == 2<<s // 1 and 2 have type int; oo == true var pp = 1<<s == 1<<33 // illegal: 1 has type int, but 1<<33 overflows int var xx = a[1.0<<s] // 1.0 has type int; xx == a[0] var bb = make([]byte, 1.0<<s) // 1.0 has type int; len(bb) == 0
運算子優先順序
一元運算子的優先順序最高。因為 ++
和 --
運算子會形成陳述式,而不是表達式,因此它們會置於運算子層級之外。結果,陳述式 *p++
與 (*p)++
相同。
二元運算子有五個優先順序層級。乘法運算子具有最強的約束力,其次是加法運算子、比較運算子、&&
(邏輯 AND),最後是 ||
(邏輯 OR)。
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 的常數次方,除法可以用右位移替換,而且計算餘數可以用按位 AND 運算替換
x x / 4 x % 4 x >> 2 x & 3 11 2 3 2 3 -11 -2 -3 -3 1
位移運算子依據右運算元指定之位移數位移左運算元,而且必須是非負值。如果在執行時期位移數為負數,會出現 執行時期驚恐。如果左運算式是帶正負號的整數,位移運算子會執行算術位移;如果它是無正負號的整數,則執行邏輯位移。位移數沒有上限。位移會像左運算元以 n
移位 n
次,位移數為 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 標準而定。
- 複數類型可比較。兩個複數值
u
和v
在real(u) == real(v)
和imag(u) == imag(v)
時相等。 - 字串類型可比較且已排序。兩個字串值的比較是依字元依序比較位元組。
- 指標類型可比較。兩個指標值在指向同一變數或兩者值都為
nil
時相等。指向不同 零大小 變數的指標可能相等或不相等。 - 通道類型可比較。兩個通道值在透過對
make
的相同呼叫建立,或兩者值都為nil
時相等。 - 作為類型參數的介面類型可比較。兩個介面值在具有 相同的 動態類型和相等的動態值,或兩者值都為
nil
時相等。 - 非介面類型
X
的值x
和介面類型T
的值t
可以比較,若類型X
可比較,而且X
實作T
。若t
的動態類型和X
相符,且t
的動態值等於x
,代表相等。 - 若所有欄位類型都可比較,結構類型可比較。若對應的非 空白 欄位值相等,兩個結構值相等。欄位依來源順序比較,只要兩個欄位值不同(或已比較所有欄位),就停止比較。
- 若陣列元素類型可比較,陣列類型可比較。若對應元素值相等,兩個陣列值相等。元素依遞增索引順序比較,只要兩個欄位值不同(或已比較所有元素),就停止比較。
- 類型參數在 строго 比較時可比較(請參閱下方)。
若兩個介面值具有相同的動態類型,則比較時會引發 執行時間錯誤,若該類型無法比較。此行為不僅適用於直接介面值比較,也適用於比較介面值陣列或具有介面值欄位的結構。
切片、映射和函數類型不可比較。但是,作為特殊案例,切片、映射或函數值可能會與預先宣告的識別碼 nil
比較。允許 pointer、channel 和 interface 值與 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 )
類型在可以比較、不是介面類型且未組成介面類型時才能进行 严格比较。具體來說
- Boolean、數字、字串、pointer 和 channel 類型严格可比较。
- 若所有欄位類型严格可比较,結構類型严格可比较。
- 若陣列元素類型严格可比较,陣列類型严格可比较。
- 若類型集中所有類型都严格可比较,類型參數严格可比较。
邏輯運算子
邏輯運算子適用於 布林值,並產生與運算元相同類型的結果。若條件需要,會先評估左運算元,再評估右運算元。
&& 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
的條件為如果 x
可以利用 T
的值來表示。特殊範例為整數常數 x
可以利用相同規則明確地轉換為字串類型,如同非常數 x
。
將常數轉換為非 類型參數 的類型可產生一個類型常數。
uint(iota) // iota value of type uint float32(2.718281828) // 2.718281828 of type float32 complex128(1) // 1.0 + 0.0i of type complex128 float32(0.49999999) // 0.5 of type float32 float64(-1e-1000) // 0.0 of type float64 string('x') // "x" of type string string(0x266c) // "♬" of type string myString("foo" + "bar") // "foobar" of type myString string([]byte{'a'}) // not a constant: []byte{'a'} is not a constant (*int)(nil) // not a constant: nil is not a constant, *int is not a boolean, numeric, or string type int(1.2) // illegal: 1.2 cannot be represented as an int string(65.0) // illegal: 65.0 is not an integer constant
將常數轉換為類型參數會產生該類型的非常數值,以該類型引數的值呈現,而類型參數則會 實例化 為該值。例如,以下列函式為例:
func f[P ~float32|~float64]() { … P(1.1) … }
轉換 P(1.1)
會產生類型為 P
的非常數值,其中值 1.1
會以 float32
或 float64
的形式呈現,視 f
的類型引數而定。因此,如果 f
以 float32
類型實例化,表達式 P(1.1) + 1.2
的數值會以與對應的非常數 float32
加法運算精度相同的精度運算。
在下列任一情況中,非常數值 x
均可以轉換為類型 T
-
x
可 指派 給T
。 - 忽略結構標籤(請見下方),
x
的類型與T
不是 類型參數,但擁有 相同的 基礎類型。 - 忽略結構標籤(請見下方),
x
的類型與T
是非 命名類型 的指標類型,且其指標基底類型不是類型參數,但也擁有相同的基礎類型。 -
x
的類型與T
都是整數或浮點數類型。 -
x
的類型與T
都是複合物類型。 -
x
是整數或位元組或符文的切片,而T
是字串類型。 -
x
是字串,而T
是位元組或符文的切片。 -
x
是切片,而T
是陣列 [Go 1.20] 或指向陣列的指標 [Go 1.17],且切片與陣列類型擁有 相同的 元素類型。
另外,如果 T
或 x
的類型 V
是類型參數,x
也可以在符合下列條件之一時轉換為類型 T
V
與T
都是類型參數,且V
類型設定中的每個類型值都可以轉換為T
類型設定中的每個類型。- 僅有
V
是類型參數,且V
類型設定中的每個類型值都可以轉換為T
。 - 僅有
T
是類型參數,且x
可以轉換為T
類型設定中的每個類型。
結構標記在將結構類型轉換為識別用時將會被忽略
type Person struct { Name string Address *struct { Street string City string } } var data *struct { Name string `json:"name"` Address *struct { Street string `json:"street"` City string `json:"city"` } `json:"address"` } var person = (*Person)(data) // ignoring tags, the underlying types are identical
特定的規則套用在數字類型之間(非恆值)轉換,或是轉換時牽涉到字串類型。這些轉換可能會改變 x
的表示方式,並導致執行時間成本。其他轉換只會改變 x
的類型,但不會改變其表示方式。
沒有任何語言機制可以將指標和整數進行轉換。package unsafe
在受限的情境下實作此功能。
數字類型間的轉換
下列規則套用在非恆值數字值轉換
- 在整數類型之間轉換時,如果值為有號整數,則會以隱含無限精確度來擴充符號;否則,將會是零擴充。接著會將其截斷以符合結果類型的尺寸。例如,如果
v := uint16(0x10F0)
,則uint32(int8(v)) == 0xFFFFFFF0
。轉換總是會產生有效的數值;沒有溢出的跡象。 - 在將浮點數轉換為整數時,小數點部分會被捨去(對零進行截斷)。
- 在將整數或浮點數轉換為浮點數類型,或是將複數轉換為另一種複數類型時,結果值會依據目的地類型的精確度進行捨入。例如,
float32
類型的變數x
的值可能會使用超過 IEEE 754 32 位元數值的額外精確度儲存,但 float32(x) 代表將x
的值捨入成 32 位元精確度的結果。類似地,x + 0.1
可能使用超過 32 位元的精確度,但float32(x + 0.1)
則否。
在所有涉及浮點數或複數值的非恆值轉換中,如果結果類型無法代表此值,則轉換成功,但結果值會依賴實作而定。
與字串類型之間的轉換
- 將位元組切片轉換為字串類型會產生一個其連續位元組為切片元素的字串。
string([]byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}) // "hellø" string([]byte{}) // "" string([]byte(nil)) // "" type bytes []byte string(bytes{'h', 'e', 'l', 'l', '\xc3', '\xb8'}) // "hellø" type myByte byte string([]myByte{'w', 'o', 'r', 'l', 'd', '!'}) // "world!" myString([]myByte{'\xf0', '\x9f', '\x8c', '\x8d'}) // "🌍"
- 將符號切片轉換為字串類型會產生一個字串,該字串是由個別符號值(已轉換為字串)所串接而成。
string([]rune{0x767d, 0x9d6c, 0x7fd4}) // "\u767d\u9d6c\u7fd4" == "白鵬翔" string([]rune{}) // "" string([]rune(nil)) // "" type runes []rune string(runes{0x767d, 0x9d6c, 0x7fd4}) // "\u767d\u9d6c\u7fd4" == "白鵬翔" type myRune rune string([]myRune{0x266b, 0x266c}) // "\u266b\u266c" == "♫♬" myString([]myRune{0x1f30e}) // "\U0001f30e" == "🌎"
- 將字串類型的值轉換為位元組切片類型會產生一個非零切片,其個別元素為字串的位元組。
[]byte("hellø") // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'} []byte("") // []byte{} bytes("hellø") // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'} []myByte("world!") // []myByte{'w', 'o', 'r', 'l', 'd', '!'} []myByte(myString("🌏")) // []myByte{'\xf0', '\x9f', '\x8c', '\x8f'}
- 將字串類型的值轉換為符號切片類型會產生一個切片,其中包含字串的個別 Unicode 碼點。
[]rune(myString("白鵬翔")) // []rune{0x767d, 0x9d6c, 0x7fd4} []rune("") // []rune{} runes("白鵬翔") // []rune{0x767d, 0x9d6c, 0x7fd4} []myRune("♫♬") // []myRune{0x266b, 0x266c} []myRune(myString("🌐")) // []myRune{0x1f310}
- 最後,因歷史因素,整數值可能會轉換為字串類型。這種轉換形式會產生一個包含給定整數值相對應的 Unicode 編碼點(可能為多位元組)UTF-8 表達形式的字串。超過有效 Unicode 編碼點範圍的值會轉換為
"\uFFFD"
。string('a') // "a" string(65) // "A" string('\xf8') // "\u00f8" == "ø" == "\xc3\xb8" string(-1) // "\ufffd" == "\xef\xbf\xbd" type myString string myString('\u65e5') // "\u65e5" == "日" == "\xe6\x97\xa5"
注意:這種轉換形式最後可能會從語言移除。go vet
工具標記某些將整數轉換為字串的轉換為潛在錯誤。應該使用像utf8.AppendRune
或utf8.EncodeRune
這種函式庫函數。
區塊轉換為陣列或陣列指標
將區塊轉換為陣列會產生一個陣列,它包含區塊底層陣列的元素。類似地,將區塊轉換為陣列指標會產生一個指標,它指向區塊的底層陣列。無論是哪一種狀況,如果區塊的 長度
小於陣列的長度,就會發生 執行時期恐慌
。
s := make([]byte, 2, 4) a0 := [0]byte(s) a1 := [1]byte(s[1:]) // a1[0] == s[1] a2 := [2]byte(s) // a2[0] == s[0] a4 := [4]byte(s) // panics: len([4]byte) > len(s) s0 := (*[0]byte)(s) // s0 != nil s1 := (*[1]byte)(s[1:]) // &s1[0] == &s[1] s2 := (*[2]byte)(s) // &s2[0] == &s[0] s4 := (*[4]byte)(s) // panics: len([4]byte) > len(s) var t []string t0 := [0]string(t) // ok for nil slice t t1 := (*[0]string)(t) // t1 == nil t2 := (*[1]string)(t) // panics: len([1]string) > len(t) u := make([]byte, 0) u0 := (*[0]byte)(u) // u0 != nil
常數表達式
常數表達式只能包含 常數
函數,而且會在編譯時期評估。
非型別布林、數值和字串常數可用作函數,無論在何處都可以合法使用布林、數值或字串型態的函數。
常數 比較
永遠會產生非型別布林常數。如果常數 位移表達式
的左側函數是非型別常數,結果就會是整數常數;否則就是與左側函數相同的型態常數,這個函數必須具有 整數型態
。
對非型別常數執行的任何其他程式碼操作,都會產生相同種類的非型別常數,也就是布林、整數、浮點數、複數或字串常數。如果二元操作(除了位移)的非型別函數有不同的種類,結果就會與這個函數的種類相同,此種類會在清單中出現在比較後面:整數、符號、浮點數、複數。例如,將非型別整數常數除以非型別複數常數,會產生非型別複數常數。
const a = 2 + 3.0 // a == 5.0 (untyped floating-point constant) const b = 15 / 4 // b == 3 (untyped integer constant) const c = 15 / 4.0 // c == 3.75 (untyped floating-point constant) const Θ float64 = 3/2 // Θ == 1.0 (type float64, 3/2 is integer division) const Π float64 = 3/2. // Π == 1.5 (type float64, 3/2. is float division) const d = 1 << 3.0 // d == 8 (untyped integer constant) const e = 1.0 << 3 // e == 8 (untyped integer constant) const f = int32(1) << 33 // illegal (constant 8589934592 overflows int32) const g = float64(2) >> 1 // illegal (float64(2) is a typed floating-point constant) const h = "foo" > "bar" // h == true (untyped boolean constant) const j = true // j == true (untyped boolean constant) const k = 'w' + 1 // k == 'x' (untyped rune constant) const l = "hi" // l == "hi" (untyped string constant) const m = string(k) // m == "x" (type string) const Σ = 1 - 0.707i // (untyped complex constant) const Δ = Σ + 2.0e-4 // (untyped complex constant) const Φ = iota*1i - 1/1i // (untyped complex constant)
將內建函數 complex
套用到非型別整數、符號或浮點數常數,會產生非型別複數常數。
const ic = complex(0, c) // ic == 3.75i (untyped complex constant) const iΘ = complex(0, Θ) // iΘ == 1i (type complex128)
常數表達式會永遠準確地評估;中間值和常數本身可能需要比語言中的任何預先宣告型態都還要大的精準度。以下是合法宣告
const Huge = 1 << 100 // Huge == 1267650600228229401496703205376 (untyped integer constant) const Four int8 = Huge >> 98 // Four == 4 (type int8)
常量除法或取餘數運算的除數不能為零
3.14 / 0.0 // illegal: division by zero
已類型化 常量的值必須始終能由常量類型中的值準確地表示。以下常量表達式是非法的
uint(-1) // -1 cannot be represented as a uint int(3.14) // 3.14 cannot be represented as an int int64(Huge) // 1267650600228229401496703205376 cannot be represented as an int64 Four * 300 // operand 300 cannot be represented as an int8 (type of Four) Four * 100 // product 400 cannot be represented as an int8 (type of Four)
單項位元運算補數運算子 ^
所使用的遮罩符合非常量的規則:對於未簽名的常量,遮罩為全 1,對於已簽名和未類型化的常量,遮罩為 -1。
^1 // untyped integer constant, equal to -2 uint8(^1) // illegal: same as uint8(-2), -2 cannot be represented as a uint8 ^uint8(1) // typed uint8 constant, same as 0xFF ^ uint8(1) = uint8(0xFE) int8(^1) // same as int8(-2) ^int8(1) // same as -1 ^ int8(1) = -2
實作限制:編譯器在計算未類型化的浮點數或複數常量表達式時可能使用圓整;請參閱 常量 部分的實作限制。此圓整可能導致浮點數常量表達式在整數環境中無效,即使在使用無限精度計算時應該是整數,反之亦然。
評估順序
在封包層級,初始化相依性會決定 變數宣告 中個別初始化表達式的評估順序。否則,在評估表達式、賦值或 return 語句 的 運算元 時,所有函數呼叫、方法呼叫、接收運算 和 二元邏輯運算 都會以由左到右的語法順序評估。
例如,在(函數局部)賦值中
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」陳述」其中
- 沒有相關的「break」陳述參照「switch」陳述,
- 有一個預設情況,而且
- 每個情況(包含預設)中的陳述清單,以終止陳述或可能標籤的 「穿透」 陳述 結束。
- 「select」 陳述」其中
- 沒有相關的「break」 陳述參照「select」陳述,而且
- 每個情況(如果有的話包含預設情況)中的陳述清單以終止陳述結束。
- 「標籤陳述」標記一個終止陳述。
所有其餘陳述皆非終止。
「陳述清單」如果該清單非空而且最後一個非空陳述為終止陳述,則以終止陳述結尾。
空陳述
空陳述不執行任何動作。
EmptyStmt = .
標籤陳述
標籤陳述可能是 goto
、break
或 continue
陳述的目標。
LabeledStmt = Label ":" Statement . Label = identifier .
Error: log.Panic("error encountered")
表達式陳述
除了特定內建函數,函數與方法 呼叫 和 接收作業 可以出現在陳述內容中。此類陳述可以用括號括起來。
ExpressionStmt = Expression .
下列內建函數不允許出現在陳述內容中
append cap complex imag len make new real unsafe.Add unsafe.Alignof unsafe.Offsetof unsafe.Sizeof unsafe.Slice unsafe.SliceData unsafe.String unsafe.StringData
h(x+y) f.Close() <-ch (<-ch) len("foo") // illegal if len is the built-in function
傳送陳述
傳送陳述會在一個通道上傳送一個值。通道表達式的 核心類型 必須為一個 通道,通道路線必須允許傳送操作,而且被傳送的值的類型必須可以 指派 到通道的元素類型。
SendStmt = Channel "<-" Expression . Channel = Expression .
通道和值表達式在通訊開始前會先評估。通訊會阻擋直到傳送可以進行為止。如果有一個接收方準備好,則在未緩衝的通道上傳送可以進行。如果緩衝器有空間,則在緩衝的通道上傳送可以進行。在封閉的通道上傳送會導致 執行期恐慌。在一個 nil
通道上傳送會永遠阻擋。
ch <- 3 // send value 3 to channel ch
IncDec 陳述
「++」和「--」陳述會以非類型化 常數 1
增加或減少其運算元。與指定一樣,運算元必須可以 尋址 或是一個地圖索引表達式。
IncDecStmt = Expression ( "++" | "--" ) .
下列 指定陳述 的語意等效
IncDec statement Assignment x++ x += 1 x-- x -= 1
指定陳述
指定 以 表示式 指定的新值來取代目前儲存在 變數 中的值。一個指定陳述可以為單一變數指定單一值,或為匹配數量變數指定多個值。
Assignment = ExpressionList assign_op ExpressionList . assign_op = [ add_op | mul_op ] "=" .
每個左運算元都必須是 可位址 的、地圖索引的表示式,或是 (僅適用於 =
指定) 空識別碼。運算元可以加上括弧。
x = 1 *p = f() a[i] = 23 (k) = <-ch // same as: k = <-ch
指定運算 x
op=
y
其中 op 為二元 算術運算子,等效於 x
=
x
op (y)
但僅評估 x
一次。op=
建構是一個單一記號。在指定運算中,左運算式和右運算式清單都必須包含一個單一值運算式,而左運算式不得為空識別碼。
a[i] <<= 2 i &^= 1<<n
一個組配指定將多元運算中的個別元素指定至一個變數清單。有兩種形式。在第一個形式中,右運算元是單一多元運算式,例如函式呼叫、通道 或 映射 運算,或 型別斷言。左運算元的運算元數量必須符合值的數量。例如,如果 f
是一個傳回兩個值的函式,
x, y = f()
將第一個值指定至 x
,而將第二個值指定至 y
。在第二個形式中,左運算元的運算元數量必須等於右運算式的運算式數量 (各運算式都必須是單一值的),而右側的第 n 個運算式指定至左側的第 n 個運算元
one, two, three = '一', '二', '三'
使用 空識別碼 可以忽略指定中的右運算元值
_ = x // evaluate x but ignore it x, _ = f() // evaluate f() but ignore second result value
指定分兩個階段進行。首先,在左方和右方的 索引運算式 和 指標間接化 (包括 選擇器 中隱含的指標間接化) 的運算元,以及運算式都以 通常順序 進行 評估。其次,指定由左至右的順序執行。
a, b = b, a // exchange a and b x := []int{1, 2, 3} i := 0 i, x[i] = 1, 2 // set i = 1, x[0] = 2 i = 0 x[i], i = 2, 1 // set x[0] = 2, i = 1 x[0], x[0] = 1, 2 // set x[0] = 1, then x[0] = 2 (so x[0] == 2 at end) x[1], x[3] = 4, 5 // set x[1] = 4, then panic setting x[3] = 5. type Point struct { x, y int } var p *Point x[2], p.x = 6, 7 // set x[2] = 6, then panic setting p.x = 7 i = 2 x = []int{3, 5, 7} for i, x[i] = range x { // set i, x[2] = 0, x[0] break } // after this loop, i == 0 and x is []int{3, 5, 3}
在指定中,每個值都必須 可指定 至所指定之運算元的型別,有以下特殊情況
- 任何型別值都可以指定至空識別碼。
- 如果將非型別常數指定至介面型別的變數或空識別碼,則該常數會先隱含 轉換 為其 預設型別。
- 如果將非型別布林值指定至介面型別的變數或空識別碼,則它會先隱含轉換為
bool
型別。
如果陳述
「如果」陳述式根據布林表達式的值來指定兩個分支的條件式執行。如果表達式評估為 true,則執行「如果」分支,否則,如果存在,則執行「否則」分支。
IfStmt = "if" [ SimpleStmt ";" ] Expression Block [ "else" ( IfStmt | Block ) ] .
if x > max { x = max }
表達式前可以有單純陳述式,表達式評估之前會執行這個陳述式。
if x := f(); x < y { return x } else if x > z { return z } else { return y }
Switch 陳述式
「Switch」陳述式提供多重執行。一個表達式或類型與「switch」中的「案例」進行比較,以決定要執行哪個分支。
SwitchStmt = ExprSwitchStmt | TypeSwitchStmt .
有兩種形式:表達式 switch 和類型 switch。在表達式 switch 中,案例包含用於與 switch 表達式的值進行比較表達式。在類型 switch 中,案例包含用於與特別註解的 switch 表達式類型進行比較類型。在 switch 陳述式中,switch 表達式只評估一次。
表達式 switch
在表達式 switch 中,會評估 switch 表達式,並且不需要常數的 case 表達式會由左向右、由上到下評估;第一個等於 switch 表達式的會觸發執行關聯 case 的陳述式;跳過其他 case。如果沒有任何案例相符,而且有「預設」案例,就會執行它的陳述式。最多只能有一個預設 case,而它可以出現在「switch」陳述式中的任何地方。缺少 switch 表達式等同於布林值 true
。
ExprSwitchStmt = "switch" [ SimpleStmt ";" ] [ Expression ] "{" { ExprCaseClause } "}" . ExprCaseClause = ExprSwitchCase ":" StatementList . ExprSwitchCase = "case" ExpressionList | "default" .
如果 switch 表達式評估為未打型的常數,它會先隱含地 轉換 為它的 預設類型。預先宣告的未打型值 nil
無法用作 switch 表達式。switch 表達式類型必須是 可比較 的。
如果 case 表達式是未打型的,它會先隱含地 轉換 為 switch 表達式的類型。對於每個(可能轉換後的)case 表達式 x
和 switch 表達式的值 t
,x == t
必須是有效的 比較。
換句話說,switch 表達式會像用來宣告和初始化沒有明確類型的暫時變數 t
一樣;每個 case 表達式 x
就是針對它來測試相等的。
在 case 或 default 子句中,最後一個非空陳述式可能是一個(可能標籤)"直落" 陳述式,以表示控制應該從此子句的結尾流向下一個子句的第一個陳述式。否則,控制流會流向 "switch" 陳述式的結尾。"直落" 陳述式會出現在表達式 switch 的所有子句最後一個子句的最後一個陳述式。
Switch 表達式之前可以是一個簡單的陳述式,它會在表達式評估之前執行。
switch tag { default: s3() case 0, 1, 2, 3: s1() case 4, 5, 6, 7: s2() } switch x := f(); { // missing switch expression means "true" case x < 0: return -x default: return x } switch { case x < y: f1() case x < z: f2() case x == 4: f3() }
實作限制:編譯器可能不允許多個 case 表達式評估出同一個常數。例如,目前的編譯器不允許在 case 表達式中重複整數、浮點或字串常數。
型別 switch
型別 switch 比較的是型別,而不是值。它在其他方面類似於表達式 switch。它以一個特殊 switch 表達式標示,形式為一個型別斷言,它使用關鍵字 type
代替實際的型別
switch x.(type) { // cases }
然後,case 會比對實際型別 T
與表達式 x
的動態型別。與型別斷言一樣,x
必須為介面型別,但不能是型別參數,而且在 case 中列出的每一個非介面型別 T
都必須實作 x
的型別。在型別 switch 的 case 中列出的型別都必須不同。
TypeSwitchStmt = "switch" [ SimpleStmt ";" ] TypeSwitchGuard "{" { TypeCaseClause } "}" . TypeSwitchGuard = [ identifier ":=" ] PrimaryExpr "." "(" "type" ")" . TypeCaseClause = TypeSwitchCase ":" StatementList . TypeSwitchCase = "case" TypeList | "default" .
TypeSwitchGuard 可能包含一個 簡短變數宣告。當使用這種形式時,這個變數會在 隱含區塊 中各個子句的 TypeSwitchCase 結尾宣告。在 case 只列出一種類型的子句中,這個變數有那個型別;否則,這個變數有 TypeSwitchGuard 中的表達式型別。
除了型別,case 也可以使用預先宣告的識別碼 nil
;當 TypeSwitchGuard 中的表達式為 nil
介面值時,會選取這個 case。最多只能有一個 null
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 中當成型別。如果在實例化時,發現那個型別與 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 前面可以放一個簡單陳述式,它會在 guard 評估之前執行。
型別 switch 中不允許 "直落" 陳述式。
迴圈陳述式
"for" 陳述式會指定重複執行一個區塊。有下列三種形式:執行可能是由單一條件、一個 "for" 子句或一個 "range" 子句控制。
ForStmt = "for" [ Condition | ForClause | RangeClause ] Block . Condition = Expression .
具有單一條件的 for 陳述式
在最簡單的形式中,「for」語句指定了只要布林條件評估為真實時,就反覆執行區塊。這個條件在每次反覆之前會進行評估。如果沒有這個條件,則它等同於布林值 true
。
for a < b { a *= 2 }
包含 for
句子的 for 語句
包含 ForClause 的「for」語句也由其條件控制,但它另外可以指定 init 和 post 語句,像是賦值、增量或減量語句。init 語句可以是 簡短變數宣告,但 post 語句不行。
ForClause = [ InitStmt ] ";" [ Condition ] ";" [ PostStmt ] . InitStmt = SimpleStmt . PostStmt = SimpleStmt .
for i := 0; i < 10; i++ { f(i) }
如果非空值,在評估第一次反覆的條件之前,init 語句會執行一次;post 語句在執行區塊之後執行(且僅在區塊已執行的情況下執行)。ForClause 的任何元素都可能為空,但 分號 是必要的,除非只有條件。如果沒有條件,則它等同於布林值 true
。
for cond { S() } is the same as for ; cond ; { S() } for { S() } is the same as for true { S() }
每個反覆都有其自己的個別宣告變數(或變數)[Go 1.22]。第一次反覆使用的變數是由 init 語句宣告的。每個後續反覆使用的變數在執行 post 語句之前會隱含地宣告,並初始化為在那個時間點時前一次反覆變數的值。
var prints []func() for i := 0; i < 5; i++ { prints = append(prints, func() { println(i) }) i++ } for _, p := range prints { p() }
列印
1 3 5
[Go 1.22] 之前,反覆共享一組變數,而不是擁有自己的個別變數。在這種情況下,以上的範例會列印
6 6 6
包含 range
句子的 for 語句
包含「range」句子的「for」語句會反覆執行陣列、切片、字串或對應、頻道中接收到的值、從 0 到上限的整數值 [Go 1.22],或傳遞給迭代器函式的 yield 函式的值 [Go 1.23]。對於每個項目,它會將 反覆值 賦值給對應的 反覆變數(如果存在),然後執行區塊。
RangeClause = [ ExpressionList "=" | IdentifierList ":=" ] "range" Expression .
「範圍」子句中的右側表達式稱為範圍表達式,其核心類型必須是陣列、陣列指標、切片、字串、允許接受操作的通道、整數,或是具有特定簽章的函式(見下方)。與賦值情況相同,若存在於左側,運算元必須是可定址的,或是對應索引表達式;它們表示反覆運算變數。若範圍表達式是函式,則最大的反覆運算變數數量取決於函式簽章。若範圍表達式是通道或整數,則最多只允許一個反覆運算變數;否則,最多可以有兩個。如果最後一個反覆運算變數是空白識別碼,範圍子句等同於沒有該識別碼的子句。
範圍表達式x
會在迴圈之前評估,有一個例外:若出現最多一個反覆運算變數,且x
或len(x)
是常數,則範圍表達式不會經過評估。
每次反覆運算時,函式呼叫會評估一次。根據以下方式,針對每一個反覆運算產生反覆運算值(如果個別的反覆運算變數存在的話)
Range expression 1st value 2nd value array or slice a [n]E, *[n]E, or []E index i int a[i] E string s string type index i int see below rune map m map[K]V key k K m[k] V channel c chan E, <-chan E element e E 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開始。如果最多出現一個反覆運算變數,則範圍迴圈會從0產生到len(a)-1
的反覆運算值,且不索引到陣列或切片本身。對於nil
切片,反覆運算的次數是0。 - 對於字串值,「範圍」子句會反覆運算字串中的 Unicode 編碼點,從位元組索引0開始。在後續的反覆運算中,索引值會是字串中後續 UTF-8 編碼編碼點的第一個位元組的索引,第二個值(屬於
rune
類型),會是對應編碼點的值。如果反覆運算遇到無效的 UTF-8 順序,第二個值會是0xFFFD
(Unicode 的替換字元),下一次反覆運算會在字串中前進一個位元組。 - 對映的並未指定反覆運算順序,且無法保證從一次反覆運算到下一次反覆運算會是相同的。如果在反覆運算期間移除尚未達到的對映項目,則對應的反覆運算值不會產生。如果在反覆運算期間建立對映項目,則該項目可能會在反覆運算期間產生,也可能會被略過。這個選擇可能會因建立的每個項目而異,且會因反覆運算而異。如果對映是
nil
,則反覆運算的次數是0。 - 對於頻道,產生的反覆運算值是持續傳送到頻道中的值,直到頻道 關閉 為止。如果頻道為
nil
,範圍運算式將永遠封鎖。 - 對於整數值
n
,其中n
是 整數類型 或非類型化的 整數常量,反覆運算值 0 到n-1
會按遞增順序產生。如果n
是整數類型,反覆運算值具有相同的類型。否則,會根據將n
指定至反覆運算變數的方式來決定其類型。具體來說:如果反覆運算變數已預先存在,反覆運算值類型即為反覆運算變數的類型,該類型必須為整數類型。否則,如果反覆運算變數由「範圍」子句宣告或不存在,反覆運算值類型即為n
的 預設類型。如果n
<= 0,迴圈不會執行任何運算。 - 對於函式
f
,反覆運算會呼叫f
,並將新的合成yield
函式作為其引數。如果在f
傳回前呼叫yield
,yield
的引數將變成反覆運算值,以便執行迴圈主體一次。在每個後續迴圈反覆運算後,yield
會傳回 true,且可以再次呼叫以繼續執行迴圈。只要迴圈主體沒有終止,「範圍」子句便會持續為每個yield
呼叫產生反覆運算值,直到f
傳回。如果迴圈主體終止(例如透過break
陳述式),yield
會傳回 false,且不得再次呼叫。
反覆運算變數可以使用一種形式的 簡短變數宣告 (:=
) 由「範圍」子句宣告。在此情況下,它們的 範圍 是「for」陳述式的區塊,每個反覆運算都有各自的新變數 [Go 1.22](另請參閱 帶有 ForClause 的「for」陳述式)。變數具有其各自反覆運算值的類型。
如果「範圍」子句並未明確宣告反覆運算變數,它們必須已預先存在。在此情況下,反覆運算值會指定給各自的變數,就像在 指定陳述式 中一樣。
var testdata *struct { a *[7]int } for i, _ := range testdata.a { // testdata.a is never evaluated; len(testdata.a) is constant // i ranges from 0 to 6 f(i) } var a [10]string for i, s := range a { // type of i is int // type of s is string // s == a[i] g(i, s) } var key string var val interface{} // element type of m is assignable to val m := map[string]int{"mon":0, "tue":1, "wed":2, "thu":3, "fri":4, "sat":5, "sun":6} for key, val = range m { h(key, val) } // key == last map key encountered in iteration // val == map[key] var ch chan Work = producer() for w := range ch { doWork(w) } // empty a channel for range ch {} // call f(0), f(1), ... f(9) for i := range 10 { // type of i is int (default type for untyped constant 10) f(i) } // invalid: 256 cannot be assigned to uint8 var u uint8 for u = range 256 { } // invalid: 1e3 is a floating-point constant for range 1e3 { } // fibo generates the Fibonacci sequence fibo := func(yield func(x int) bool) { f0, f1 := 0, 1 for yield(f0) { f0, f1 = f1, f0+f1 } } // print the Fibonacci numbers below 1000: for x := range fibo { if x >= 1000 { break } fmt.Printf("%d ", x) } // output: 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 // iteration support for a recursive tree data structure type Tree[K cmp.Ordered, V any] struct { left, right *Tree[K, V] key K value V } func (t *Tree[K, V]) walk(yield func(key K, val V) bool) bool { return t == nil || t.left.walk(yield) && yield(t.key, t.value) && t.right.walk(yield) } func (t *Tree[K, V]) Walk(yield func(key K, val V) bool) { t.walk(yield) } // walk tree t in-order var t Tree[string, int] for k, v := range t.Walk { // process k, v }
Go 陳述式
「go」陳述式會在相同的位址空間中啟動函式呼叫的執行,作為一個獨立的同時代控制執行緒,或稱為 goroutine。
GoStmt = "go" Expression .
運算式必須是函式或方法呼叫;不能加上括號。內建函式的呼叫會受限制,就像 運算式陳述式 一樣。
函數值與參數會在呼叫的 goroutine 中像平常一樣進行評估,但與常規呼叫不同的是,程式執行時不會等待呼叫的函數完成。相反,函數會在新的 goroutine 中獨立地執行。當函數結束時,它的 goroutine 也會結束。如果函數有任何回傳值,則在函數完成時會將這些回傳值捨棄。
go Server() go func(ch chan<- bool) { for { sleep(10); ch <- true }} (c)
Select 敘述
「select」敘述會決定一系列可能的傳送或接收動作中哪一個會進行。它看起來類似於「switch」敘述,但其中的個案都是指通訊動作。
SelectStmt = "select" "{" { CommClause } "}" . CommClause = CommCase ":" StatementList . CommCase = "case" ( SendStmt | RecvStmt ) | "default" . RecvStmt = [ ExpressionList "=" | IdentifierList ":=" ] RecvExpr . RecvExpr = Expression .
含有 RecvStmt 的個案可能會將 RecvExpr 的結果指派給一或兩個變數,這些變數可以使用簡短變數宣告宣告。RecvExpr 必須是接收動作(可能會加上括號)。最多只可以有一個預設個案,它可以出現在個案清單中的任何地方。
「select」敘述的執行會進行下列幾個步驟
- 對於敘述中的所有個案,進入「select」敘述時,會依來源順序評估接收動作的通道操作數與傳送敘述的通道和右側表達式,僅評估一次。結果會產生一組要接收或傳送的通道,以及對應要傳送的值。評估中的任何副作用都會發生,而不管會選擇哪一個(如果有)通訊動作來進行。含有簡短變數宣告或指派的 RecvStmt 左側的表達式尚未評估。
- 如果一個或多個通訊動作可以進行,就會透過均一的偽亂數選擇來選擇其中一個可進行的動作。否則,如果有一個預設個案,就會選擇該個案。如果沒有預設個案,「select」敘述會封鎖,直到至少一個通訊動作可以進行。
- 除非所選個案是預設個案,否則會執行對應的通訊動作。
- 如果所選個案是含有簡短變數宣告或指派的 RecvStmt,就會評估左側表達式,並指派收到的值(或多個值)。
- 會執行所選個案的敘述清單。
由於 nil
通道的通訊永遠無法進行,因此僅含有 nil
通道且沒有預設個案的 select 會永遠封鎖。
var a []int var c, c1, c2, c3, c4 chan int var i1, i2 int select { case i1 = <-c1: print("received ", i1, " from c1\n") case c2 <- i2: print("sent ", i2, " to c2\n") case i3, ok := (<-c3): // same as: i3, ok := <-c3 if ok { print("received ", i3, " from c3\n") } else { print("c3 is closed\n") } case a[f()] = <-c4: // same as: // case t := <-c4 // a[f()] = t default: print("no communication\n") } for { // send random sequence of bits to c select { case c <- 0: // note: no statement, no fallthrough, no folding of cases case c <- 1: } } select {} // block forever
Return 敘述
函數 F
中的「return」敘述會終止 F
的執行,並可以選擇提供一個或多個結果值。任何由 F
推遲的函數都會在 F
回傳給它的呼叫函數之前執行。
ReturnStmt = "return" [ ExpressionList ] .
在沒有結果類型的函數中,「return」敘述不得指定任何結果值。
func noResult() { return }
有三個方法可以從有結果類型的函數回傳值
- 回傳值或多個回傳值可以在「return」陳述式中明確列出。每個表達式必須是單值且可指派給函式結果類型的對應元素。
func simpleF() int { return 2 } func complexF1() (re float64, im float64) { return -7.0, -4.0 }
- 「return」陳述式中的表達式清單可能是對多值函式的一次呼叫。這個運作方式就像從那個函式回傳的每個值都被指定給一個暫時變數並採用各自值的類型,接著是一個「return」陳述式來列出這些變數,在這個時候適用先前範例的規則。
func complexF2() (re float64, im float64) { return complexF1() }
- 如果函式的結果類型指定其結果參數的名稱,表達式清單可以是空的。結果參數會作為一般的局部變數,函式可以根據需要指定它們的值。「return」陳述式會回傳這些變數的值。
func complexF3() (re float64, im float64) { re = 7.0 im = 4.0 return } func (devnull) Write(p []byte) (n int, _ error) { n = len(p) return }
無論這些結果參數如何宣告,所有結果值在函式輸入時都會初始化為它們類型的零值。指定結果的「return」陳述式會在執行任何遞延函式之前設定結果參數。
實作限制:如果在回傳的地方有一個與結果參數相同名稱(常數、類型或變數)的不同實體位於作用域中,編譯器可能會禁止在「return」陳述式中使用空表達式清單。
func f(n int) (res int, err error) { if _, err := f(n-1); err != nil { return // invalid return statement: err is shadowed } return }
中斷陳述式
「break」陳述式會終止在同一個函式中最內層的「for」、「switch」或「select」陳述式。
BreakStmt = "break" [ Label ] .
如果有一個標籤,標籤必須是封裝的「for」、「switch」或「select」陳述式的標籤,而這樣做會終止執行該陳述式。
OuterLoop: for i = 0; i < n; i++ { for j = 0; j < m; j++ { switch a[i][j] { case nil: state = Error break OuterLoop case item: state = Found break OuterLoop } } }
繼續陳述式
「continue」陳述式會開始最內層封裝的「for」迴圈的下一個反覆,方式是將控制權轉移到迴圈區塊的最後。這個「for」迴圈必須在同一個函式中。
ContinueStmt = "continue" [ Label ] .
如果有一個標籤,標籤必須是封裝的「for」陳述式的標籤,而這樣做會繼續執行該陳述式。
RowLoop: for y, row := range rows { for x, data := range row { if data == endOfRow { continue RowLoop } row[x] = data + bias(x, y) } }
Goto 陳述式
「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
不在區塊內。
執行中陳述式
「穿透式」語句會將控制轉移至 expression 「switch」語句 中下一個 case 子句的第一個語句。它只能用於此類子句中最後一個非空語句。
FallthroughStmt = "fallthrough" .
延後執行語句
「defer」語句會呼叫在當前函式回傳的瞬間延後執行的函式,原因可能是當前函式執行了 return 語句、到達 函式主體 的結尾,或因為對應的 goroutine 發生異常。
DeferStmt = "defer" Expression .
運算式必須是函式或方法呼叫;不能加上括號。內建函式的呼叫會受限制,就像 運算式陳述式 一樣。
每次執行「defer」語句時,呼叫的函式值和參數都會 依慣例評估 並重新儲存,但不會呼叫實際的函式。而延後執行的函式會在當前函式回傳的正前呼叫,會按照延後函式的相反順序呼叫。也就是說,如果當前函式透過明確的 return 語句 回傳,則延後函式會 在 該 return 語句設定任何結果參數 之後 執行,但 在 函式回傳給其呼叫函式 之前 執行。如果延後函式的值評估為 nil
,則在呼叫函式時執行 會發生異常,而不是在執行「defer」語句時發生異常。
例如,如果延後函式是 函式字面值 ,而當前函式包含出現在字面值範圍內的 已命名結果參數,則延後函式可以在回傳結果參數之前存取和修改這些參數。如果延後函式有任何回傳值,則函式執行完成後會捨棄這些值。(另請參閱 處理異常 部分。)
lock(l) defer unlock(l) // unlocking happens before surrounding function returns // prints 3 2 1 0 before surrounding function returns for i := 0; i <= 3; i++ { defer fmt.Print(i) } // f returns 42 func f() (result int) { defer func() { // result is accessed after it was set to 6 by the return statement result *= 7 }() return 6 }
內建函式
內建函式會 預先宣告。呼叫這些函式的方式與其他函式沒有不同,不過有些函式會接受類型而非表達式作為第一個參數。
內建函式沒有標準的 Go 類型,因此它只能出現在 呼叫表達式 中;它無法用作函式值。
附加和複製片段
內建函式 append
和 copy
會提供協助處理常見的片段操作。這兩個函式產生的結果與參數所引用的記憶體是否重疊無關。
可變參數函數 append
會對切片 s
附加零個或多個值 x
,並回傳核心類型與 s
相同結果切片。s
的核心類型必須是類型 []E
的切片。值 x
會傳遞給核心類型為 ...E
的參數,並且套用相關參數傳遞規則。特別情況是,如果 s
的核心類型是 []byte
,append
也會接受第二個參數,其核心類型為byte
字串,後接 ...
。這種形式會附加位元組切片或字串的位元組。
append(s S, x ...E) S // core type of S is []E
如果 s
的容量不足以容納其他值,append
會配置一個新的足夠大的底層陣列,以容納現有的切片元素和其他值。否則,append
會重複使用底層陣列。
s0 := []int{0, 0} s1 := append(s0, 2) // append a single element s1 is []int{0, 0, 2} s2 := append(s1, 3, 5, 7) // append multiple elements s2 is []int{0, 0, 2, 3, 5, 7} s3 := append(s2, s0...) // append a slice s3 is []int{0, 0, 2, 3, 5, 7, 0, 0} s4 := append(s3[3:6], s3[2:]...) // append overlapping slice s4 is []int{3, 5, 7, 2, 3, 5, 7, 0, 0} var t []interface{} t = append(t, 42, 3.1415, "foo") // t is []interface{}{42, 3.1415, "foo"} var b []byte b = append(b, "bar"...) // append string contents b is []byte{'b', 'a', 'r' }
函數 copy
會將切片元素從來源 src
複製到目的地 dst
,並回傳已複製的元素數。兩個參數的核心類型都必須是切片,且擁有相同的元素類型。已複製的元素數是 len(src)
和 len(dst)
中的最小值。特別情況是,如果目的地的核心類型是 []byte
,copy
也會接受核心類型為byte
字串的來源參數。這種形式會將位元組切片或字串中的位元組複製到位元組切片中。
copy(dst, src []T) int copy(dst []byte, src string) int
範例
var a = [...]int{0, 1, 2, 3, 4, 5, 6, 7} var s = make([]int, 6) var b = make([]byte, 5) n1 := copy(s, a[0:]) // n1 == 6, s 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
會採用對應類型、切片類型或類型參數類型的參數,並刪除或將所有元素歸零 [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
則會是空操作。
關閉
對於具有 核心類型 為 通道 的引數 ch
,內建函式 close
記錄將不再透過該通道傳送任何值。如果 ch
是僅接收通道,則這會產生錯誤。傳送至封閉的通道或封閉通道會導致 執行時間恐慌。封閉 nil 通道也會導致 執行時間恐慌。在呼叫 close
,且在先前傳送的所有值都已接收後,接收作業會傳回通道類型不區塊化的零值。多值 接收作業 傳回已接收的值和通道是否已封閉的指標。
處理複數
有三個函式組合和拆分複數。內建函式 complex
從浮點實部和虛部建構一個複數值,而 real
和 imag
萃取複數值的實部和虛部。
complex(realPart, imaginaryPart floatT) complexT real(complexT) floatT imag(complexT) floatT
引數和傳回值的類型對應。對於 complex
,兩個引數必須有相同的 浮點類型,而且傳回類型是具有對應浮點組成的 複數類型:對於 float32
引數為 complex64
,而對於 float64
引數為 complex128
。如果任一引數評估為未類型化的常數,它會首先暗中 轉換 為另一個引數的類型。如果兩個引數都評估為未類型化的常數,它們必定是非複數或其虛部必定為零,而且函式的傳回值是一個未類型化的複數常數。
對於 real
和 imag
,引數必須為複數類型,而且傳回類型是對應的浮點類型:對於 complex64
引數為 float32
,而對於 complex128
引數為 float64
。如果引數評估為未類型化的常數,它必定是一個數字,而且函式的傳回值是一個未類型化的浮點常數。
函式 real
和 imag
一起構成 complex
的反函數,因此對於一個複數類型 Z
的值 z
,z == Z(complex(real(z), imag(z)))
。
如果這些函式的運算元都是常數,傳回值是一個常數。
var a = complex(2, -2) // complex128 const b = complex(1.0, -1.4) // untyped complex constant 1 - 1.4i x := float32(math.Cos(math.Pi/2)) // float32 var c64 = complex(5, -x) // complex64 var s int = complex(1, 0) // untyped complex constant 1 + 0i can be converted to int _ = complex(1, 2<<s) // illegal: 2 assumes floating-point type, cannot shift var rl = real(c64) // float32 var im = imag(a) // float64 const c = imag(b) // untyped constant -1.4 _ = imag(3 << s) // illegal: 3 assumes complex type, cannot shift
不允許類型參數類型的引數。
刪除映射元素
內建函式 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
(不是 *T
)的值。初始化記憶體的方式如 初始值 一節所述。
Call Core type Result make(T, n) slice slice of type T with length n and capacity n make(T, n, m) slice slice of type T with length n and capacity m make(T) map map of type T make(T, n) map map of type T with initial space for approximately n elements make(T) channel unbuffered channel of type T make(T, n) channel buffered channel of type T, buffer size n
每個大小引數 n
和 m
都必須為 整數類型,類型組中只能包含整數類型,或是不定型的 常數。常數大小引數必須是非負的,而且可以由 int
類型的值 表示;如果它是不定型的常數,則給予它 int
類型。如果 n
和 m
都已提供而且是常數,則 n
不能大於 m
。對於切片和通道來說,如果 n
為負值或在執行時大於 m
,將會發生 執行時異常。
s := make([]int, 10, 100) // slice with len(s) == 10, cap(s) == 100 s := make([]int, 1e3) // slice with len(s) == cap(s) == 1000 s := make([]int, 1<<63) // illegal: len(s) is not representable by a value of type int s := make([]int, 10, 0) // illegal: len(s) > cap(s) c := make(chan int, 10) // channel with a buffer size of 10 m := make(map[string]int, 100) // map with initial space for approximately 100 elements
使用映射類型和大小提示 n
呼叫 make
會建立一個映射,初始空間可以容納 n
個映射元素。確切的行為由實作決定。
最小值和最大值
內建涵數 min
和 max
計算固定數目、有序類型 引數的最小值或最大值。必須至少有一個引數 [Go 1.21]。
對於 運算子 所套用的相同類型規則將:對於 已排序 參數 x
和 y
,當 x + y
有效時,min(x, y)
有效,而且 min(x, y)
類型即為 x + y
類型(max
同樣如此)。如果所有參數皆為常數,則結果為常數。
var x, y int m := min(x) // m == x m := min(x, y) // m is the smaller of x and y m := max(x, y, 10) // m is the larger of x and y but at least 10 c := max(1, 2.0, 10) // c == 10.0 (floating-point kind) f := max(0, float32(x)) // type of f is float32 var s []string _ = min(s...) // invalid: slice arguments are not permitted t := max("", "foo", "bar") // t == "foo" (string kind)
對於數值參數,假設所有 NaN 都相等,則 min
和 max
具有交換律和結合律
min(x, y) == min(y, x) min(x, y, z) == min(min(x, y), z) == min(x, min(y, z))
對於浮點數參數負零、NaN 和無限,將套用以下規則
x y min(x, y) max(x, y) -0.0 0.0 -0.0 0.0 // negative zero is smaller than (non-negative) zero -Inf y -Inf y // negative infinity is smaller than any other number +Inf y y +Inf // positive infinity is larger than any other number NaN y NaN NaN // if any argument is a NaN, the result is a NaN
對於字串參數,min
的結果為第一個參數中值最小(或對於 max
,值最大)的參數,以位元組按字元順序比較
min(x, y) == if x <= y then x else y min(x, y, z) == min(min(x, y), z)
配置
內建函數 new
會在執行階段取得類型 T
,為該類型的一個 變數 配置儲存空間,並傳回一個指向它的 *T
型別值。該變數的初始化則如 初始值 部分所述。
new(T)
例如
type S struct { a int; b float64 } new(S)
為類型 S
的變數配置儲存空間,初始化它(a=0
、b=0.0
),並傳回一個包含該位置位址的 *S
型別值。
處理恐慌
兩個內建函數,panic
和 recover
,有助於回報和處理 執行階段恐慌 和程式定義錯誤狀況。
func panic(interface{}) func recover() interface{}
在執行函數 F
時,panic
的明確呼叫或 執行階段恐慌 會終止 F
的執行。由 F
延後 的任何函數之後會照常執行。接下來,由 F
的呼叫者執行中的任何延後函數也會執行,依此類推,一直到執行中的 goroutine 中頂層函數延後的所有內容。在該點上,程式將終止,並且會回報錯誤狀況,包括 panic
的參數值。此終止序列稱為恐慌。
panic(42) panic("unreachable") panic(Error("cannot parse"))
recover
函數允許程式管理回報錯誤的 goroutine 行為。假設一個函數 G
遞延一個呼叫 recover
的函數 D
,且在 G
執行的同一個 goroutine 上的一個函數中發生回報錯誤。當遞延函數的執行到達 D
時,D
呼叫 recover
的回傳值會是傳遞給 panic
呼叫的值。如果 D
正常回傳,沒有發出新的 panic
,回報錯誤的序列會停止。在這種情況下,在 G
和呼叫 panic
之間呼叫的函數狀態會被捨棄,且正常執行會繼續。任何 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() }
開機自舉
目前的實作提供幾個在開機自舉期間有用的內建函數。這些函數列於此處以確保完整性,但無法保證會繼續存在於該語言中。它們不會回傳結果。
Function Behavior print prints all arguments; formatting of arguments is implementation-specific println like print but prints spaces between arguments and a newline at the end
實作限制:print
和 println
不一定接受任意引數類型,但必須支援布林值、數字和字串 類型 的列印。
套件
Go 程式是透過連結在一起的 套件 來建立的。一個套件反過來由一個或多個原始檔所組成,這些原始檔會宣告共屬於這個套件的常數、類型、變數和函數,且在同一個套件的所有原始檔中都能存取。這些元素可能 外載 並用在其他套件中。
原始檔組織
每個原始檔都包含一個套件宣告項,用來定義它所屬的套件,後接一組可能為空的匯入宣告項,以宣告它要使用的套件內容,再後接一組可能為空的函數、類型、變數和常數宣告項。
SourceFile = PackageClause ";" { ImportDecl ";" } { TopLevelDecl ";" } .
套件宣告項
每個原始檔都以套件宣告項開始,並定義原始檔所屬的套件。
PackageClause = "package" PackageName . PackageName = identifier .
PackageName 不得為 空白識別碼。
package math
共用相同 PackageName 的一組原始檔會形成一個套件的實作。一個實作可能需要一個套件的所有原始檔都在同一個目錄中。
匯入宣告項
一項匯入宣告指出包含宣告的來源檔案,取決於匯入的套件的功能(§程式初始化和執行),並啟用存取該套件的匯出的識別碼。匯入會命名一個識別碼(PackageName),用於存取和一個 ImportPath,用於指定要匯入的套件。
ImportDecl = "import" ( ImportSpec | "(" { ImportSpec ";" } ")" ) . ImportSpec = [ "." | PackageName ] ImportPath . ImportPath = string_lit .
PackageName 在限定識別碼中用於存取匯入來源檔案內套件的匯出識別碼。它在檔案區塊中宣告。如果省略 PackageName,它會預設為在匯入套件的套件子句中指定的識別碼。如果出現明確的句點(.
)而不是名稱,則在那個套件的套件區塊中宣告的所有套件匯出的識別碼都將在匯入來源檔案的檔案區塊中宣告,且必須在沒有限定詞的情況下存取。
ImportPath 的詮釋取決於實作方式,但它通常是已編譯套件的完整檔名的子字串,且可能是相對於安裝的套件的儲存庫。
實作限制:編譯器可能將 ImportPath 限制為僅使用屬於Unicode的 L、M、N、P 和 S 一般類別(沒有空格的圖形字元)的非空白字串,也可能排除字元 !"#$%&'()*,:;<=>?[\]^`{|}
以及 Unicode 替換字元 U+FFFD。
考慮一個包含套件子句 套件數學
的已編譯套件,套件會匯出函式 Sin
,並安裝在由 "lib/math"
識別的檔案中的已編譯套件。此表格說明在各種匯入宣告類型後,是如何在匯入套件的檔案中存取 Sin
。
Import declaration Local name of Sin import "lib/math" math.Sin import m "lib/math" m.Sin import . "lib/math" Sin
匯入宣告會宣告匯入和匯入的套件之間的依賴關係。一個套件不可匯入它自身(直接或間接),或直接匯入一個套件而沒有參照它的任何匯出識別碼。要只為了副作用(初始化)匯入一個套件,請使用空白識別碼作為明確的套件名稱。
import _ "lib/math"
範例套件
以下是實作並行的質數篩選器的完整 Go 套件。
package main import "fmt" // Send the sequence 2, 3, 4, … to channel 'ch'. func generate(ch chan<- int) { for i := 2; ; i++ { ch <- i // Send 'i' to channel 'ch'. } } // Copy the values from channel 'src' to channel 'dst', // removing those divisible by 'prime'. func filter(src <-chan int, dst chan<- int, prime int) { for i := range src { // Loop over values received from 'src'. if i%prime != 0 { dst <- i // Send 'i' to channel 'dst'. } } } // The prime sieve: Daisy-chain filter processes together. func sieve() { ch := make(chan int) // Create a new channel. go generate(ch) // Start generate() as a subprocess. for { prime := <-ch fmt.Print(prime, "\n") ch1 := make(chan int) go filter(ch, ch1, prime) ch = ch1 } } func main() { sieve() }
程式初始化和執行
零值
當透過宣告或呼叫 new
為變數配置儲存空間,或透過複合文字或呼叫 make
建立新值,且沒有提供明確的初始化時,變數或值會獲得預設值。這樣一個變數或值的每個元素都會設定為其類型的零值:布林值為 false
,數字類型為 0
,字串為 ""
,指標、函式、介面、區段、通道和對應為 nil
。此初始化會遞迴進行,因此例如如果沒有指定值,結構陣列的每個元素其欄位都會設為 0。
這兩個簡單的宣告是等效的
var i int var i int = 0
在
type T struct { i int; f float64; next *T } t := new(T)
以下情況中成立
t.i == 0 t.f == 0.0 t.next == nil
包初始化之後也是一樣
var t T
包初始化
在一個包裡,包層級變數初始化會逐步進行,每個步驟都會選擇在宣告順序中最先且沒有相依於未初始化變數的變數。
更精確地說,一個包層級變數被視為準備好要初始化,如果它還沒有被初始化,而且沒有初始化表達式,或它的初始化表達式沒有相依於未初始化變數的相依性。初始化會重複執行,每次執行都把宣告順序中最先且準備好要初始化的包層級變數初始化,直到沒有任何變數準備好要初始化為止。
如果這個程序結束時還有任何變數未初始化,表示那些變數是其中一個或多個初始化循環的一部分,而且程式無效。
在單一(多值)表達式初始化的變數宣告的左手邊的變數會一起初始化:如果左手邊的任何變數被初始化,所有那些變數都會在同一步驟中被初始化。
var x = a var a, b = f() // a and b are initialized together, before x is initialized
在包初始化的場合中,空白變數就像宣告中的其他變數一樣。
在多個檔案中宣告的變數的宣告順序是由檔案呈現給編譯器的順序決定的:在第一個檔案宣告的變數會在宣告在第二個檔案中宣告的任何變數之前被宣告,等等。為了確保可再現的初始化行為,建議建立系統以字典檔名順序將屬於同一個包的多個檔案提供給編譯器。
相依性分析並非依賴於變數的實際值,而僅依賴於變數在原始程式碼中對它們的字彙參照,分析為遞移的。例如,如果一個變數 x
的初始化表達式參照了它的主體參照變數 y
的一個函式,則 x
相依於 y
。特別地
- 對變數或函式的參照是由一個識別符號表示的變數或函數。
- 對方法
m
的參照是一個t.m
形式的方法值或方法表達式 ,其中t
的(靜態)型態不是介面型態,而且方法m
在t
的方法集。無所謂所產生的函式值t.m
是否會被呼叫。 - 一個變數、函式或方法
x
相依於一個變數y
如果x
的初始化表達式或主體(對函式和方法)包含一個對y
或一個相依於y
的函式或方法的參照。
例如,給定宣告
var ( a = c + b // == 9 b = f() // == 4 c = f() // == 5 d = 3 // == 5 after initialization has finished ) func f() int { d++ return d }
初始化順序為 d
、b
、c
及 a
。請注意,初始化表達式中子表達式的順序無關緊要:a = c + b
及 a = b + c
在這個範例中會產生相同的初始化順序。
依存性分析在每個套件中執行;僅考慮參照當前套件中宣告的變數、函式和(非介面)方法的參照。如果變數之間存在其他隱藏資料依存性,這些變數之間的初始化順序不會指定。
例如,給定下列宣告
var x = I(T{}).ab() // x has an undetected, hidden dependency on a and b var _ = sideEffect() // unrelated to x, a, or b var a = b var b = 42 type I interface { ab() []int } type T struct{} func (T) ab() []int { return []int{a, b} }
變數 a
會在 b
之後初始化,但是否 x
的初始化在 b
之前、在 b
與 a
之間,或在 a
之後,以及函式 sideEffect()
的呼叫時間(在 x
初始化之前或之後),都沒有指定。
變數也可以使用在套件區塊中宣告的名稱為 init
的函式初始化,不需要參數且沒有傳回的參數。
func init() { … }
每個套件可能定義許多這樣的函式,即使在同一個來源檔案中。在套件區塊中,init
識別碼只能用於宣告 init
函式,但識別碼本身並未 宣告。因此 init
函式不能在程式中任何地方參照。
整個套件的初始化會為所有套件層級的變數指派初始值,接著呼叫 init
函式,順序為函式在提供給編譯器的來源中出現的順序,可能在多個檔案中。
程式初始化
完整程式的套件以逐步方式初始化,一次一個套件。如果一個套件有匯入,在初始化套件本身之前,會初始化匯入的套件。如果多個套件匯入同一個套件,匯入的套件只會初始化一次。根據建構,匯入套件保證無法有循環的初始化依存性。更精確來說
給定已由匯入路徑排序的所有套件的清單,每個步驟中,清單中第一個未初始化的套件(如果有),而所有被匯入套件(如果有)都已初始化,會 初始化。這個步驟會重複執行直到所有套件都已初始化。
套件初始化(變數初始化和呼叫 init
函式)會在一個 goroutine 中單一執行,循序進行,一次一個套件。init
函式可以啟動其他 goroutine,這些 goroutine 可以與初始化程式碼同時執行。然而,初始化總是依循 init
函式的順序:它不會在先前一個函式傳回之前呼叫下一個函式。
程式執行
完整的程式會透過將單一、未匯入,稱為 主套件 的套件,與其匯入的所有套件(循序)連結,來建立。主套件的套件名稱必須為 main
,並宣告一個不接受任何參數且不傳回任何值的 main
函式。
func main() { … }
程式執行會由 初始化程式 開始,然後呼叫 main
套件中的 main
函式。當函式呼叫傳回時,程式會結束。它不會等待其他(非 main
)goroutine 完成。
錯誤
預先宣告類型error
定義如下
type error interface { Error() string }
這是表示錯誤狀況的慣例介面,其中 nil 值表示沒有錯誤。例如,可以定義一個從文件中讀取資料的函數
func Read(f *File, b []byte) (n int, err error)
執行時期錯誤
執行錯誤(例如嘗試對陣列索引超出範圍)會引發執行時期錯誤,這等於呼叫內建函數 panic
,值為實作定義的介面類型runtime.Error
。該類型滿足預先宣告的介面類型 error
。表示不同執行時期錯誤狀況的確切錯誤值未指定。
package runtime type Error interface { error // and perhaps other methods }
系統考量
套件unsafe
已知編譯器且可透過 匯入路徑 "unsafe"
存取的內建套件unsafe
,提供可用於低階程式設計的程式庫,包括違反類型系統的運算。使用 unsafe
的套件必須針對類型安全性進行手動驗證,且可能無法移植。套件提供下列介面
package unsafe type ArbitraryType int // shorthand for an arbitrary Go type; it is not a real type type Pointer *ArbitraryType func Alignof(variable ArbitraryType) uintptr func Offsetof(selector ArbitraryType) uintptr func Sizeof(variable ArbitraryType) uintptr type IntegerType int // shorthand for an integer type; it is not a real type func Add(ptr Pointer, len IntegerType) Pointer func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType func SliceData(slice []ArbitraryType) *ArbitraryType func String(ptr *byte, len IntegerType) string func StringData(str string) *byte
Pointer
是 指標類型,但 Pointer
值不可能是 取消參照。任何指標或 核心類型 uintptr
的值,皆可 轉換 為核心類型 Pointer
的類型,反之亦然。在 Pointer
與 uintptr
間轉換的效果取決於實作。
var f float64 bits = *(*uint64)(unsafe.Pointer(&f)) type ptr unsafe.Pointer bits = *(*uint64)(ptr(&f)) func f[P ~*B, B any](p P) uintptr { return uintptr(unsafe.Pointer(p)) } var p ptr = nil
函數 Alignof
和 Sizeof
會接收任何類型的運算式 x
,並分別傳回假設變數 v
透過 var v = x
宣告時的變數對齊方式或大小。
函數 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
A(T
函數的變數)的大小會因爲 T
是函數參數、陣列或結構而有所不同,另外還包含元素或各種大小的欄位。否則大小是保持一致的。對 Alignof
、Offsetof
和 Sizeof
的呼叫是 uintptr
型態的編譯時期常數表達式,前提是其引數(或對 Offsetof
的選擇器表達式 s.f
中的結構 s
)的型態有大小的常數變數。
函數 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
為 0,Slice
將會傳回 nil
[Go 1.17]。
len
引數必須是整數型態或未分組的常數。常數 len
引數必須是非負整數,並能以 int
型態的數值表示;如果它是未分組常數,將設定型態為 int
。於執行階段,如果 len
為負數,或 ptr
為 nil
且 len
不為 0,將發生執行階段畫面 [Go 1.17]。
函數 SliceData
傳回一個指標,指向區塊引數的底層陣列。如果區塊的總長 cap(slice)
不為 0,這個指標便是 &slice[:1][0]
。如果區塊為 nil
,結果則是 nil
。否則它會是 nil
,指向未指定記憶體地址的指標 [Go 1.20]。
函數 String
傳回一個 string
值,其底層位元組開始於 ptr
,長度為 len
。函數 Slice
中會對 ptr
和 len
引數套用相同的要求。如果 len
為 0,結果會是空字串 ""
。由於 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)
與陣列元素類型的變數對齊方式相同。
如果結構或陣列類型不包含大小大於 0 的欄位(或元素),則大小為 0。兩個不同的零大小變數在記憶體中可能具有相同位址。
附錄
語言版本
Go 1 兼容性保證 確保根據 Go 1 規格編寫的程式,在該規格的使用期間,將可以持續編譯並正確執行,而不需要進行任何變更。更普遍來說,由於語言中進行了調整並加入了功能,因此兼容性保證確保使用特定 Go 語言版本運作的 Go 程式,可以繼續使用任何後續版本。
例如,使用前綴 0b
表示二進位整數文字的功能是在 Go 1.13 中引入的,在 整數文字 區段中以 [Go 1.13] 表示。如果編譯器使用的隱含或必要語言版本舊於 Go 1.13,則包含整數文字(例如 0b1011
)的原始程式碼將會被拒絕。
下表說明在 Go 1 之後新增的功能所需的最低語言版本。
Go 1.9
- 可以透過 別名宣告 來宣告類型的別名。
Go 1.13
-
整數文字 可使用前綴
0b
、0B
、0o
和0O
,分別表示二進位和八進位文字。 - 十六進位的 浮點文字 可以使用前綴
0x
和0X
。 - 虛數字尾
i
不只能用於十進位文字,也可以用於任何(二進位、十進位、十六進位)整數或浮點文字。 - 任何數值文字的數字都可以使用底線
_
進行 分隔(群組)。 - 位移運算 中的位移計數可以為有號整數類型。
Go 1.14
- 透過不同的 嵌入式介面 將方法嵌入多於一次並非錯誤。
Go 1.17
Go 1.18
1.18 釋出版本將多型函式與類型(「泛型」)加入程式語言中。具體來說
- 運算子與符號組新增
~
。 - 函式與類型聲明可宣告類型參數。
- 介面類型可嵌入任意類型(不僅限於介面類型名稱),以及聯集與
~T
類型元素。 - 預先宣告類型組新增
any
與comparable
等新類型。
Go 1.20
- 若區段和陣列元素類型相符且陣列長度不超過區段長度,則區段可以轉換為陣列。
- 內建封裝
unsafe
包含新的函式SliceData
、String
和StringData
。 -
即使類型參數無法嚴格比較,可比較類型(例如常見介面)也能滿足
comparable
約束。
Go 1.21
Go 1.22
Go 1.23
- 帶「range」子句的「for」語句會將反覆運算函式視為範圍運算式。
類型統一規則
類型統一規則描述兩個類型是否相符,以及若相符,該怎麼相符。精確細節與 Go 實作有關,會影響錯誤訊息的詳細內容(例如編譯器會報告類型推論錯誤或其他錯誤),並可在異常程式碼情況下解釋類型推論失敗的原因。不過,在撰寫 Go 程式碼時,原則上可以忽略這些規則:類型推論設計為大部分「依預期運作」,而統一規則也會據此進行微調。
類型統一是由配對模式所控制的,它可以是精確的或寬鬆的。當統一遞迴下降到複合式類型結構時,用於類型元素的配對模式,也就是元素配對模式,仍然與配對模式相同,除非兩種類型統一於可配置性(≡A
)中:此情況下,配對模式在頂層為寬鬆的,但接著對元素類型變更為精確的,這反映了類型不必相同才能夠可配置化的事實。
若符合下列任何一個條件,兩個未繫結的類型參數會精確統一
- 這兩種類型為同一。
- 這兩種類型具有相同的結構,且其元素類型會精確統一。
- 其中一種類型是具有核心類型的未繫結類型參數,且該核心類型會依照
≡A
的統一規則與另一種類型統一(頂層寬鬆統一和元素類型的精確統一)。
若這兩種類型都是繫結類型參數,它們會依照給定的配對模式統一,前提是
- 這兩個類型參數相同。
- 最多只有一個類型參數具有已知的類型引數。這種情況下,類型參數會合併:它們對於相同的類型引數而言,都是代表類型引數。如果到目前為止,這兩個類型參數都沒有已知的類型引數,則日後為其中一個類型參數推斷的類型引數,會同時為它們兩個推斷。
- 這兩個類型參數具有已知的類型引數,且類型引數會依照給定的配對模式統一。
若含有繫結類型參數 P
和另一種類型 T
,它們會依照給定的配對模式統一,前提是
-
P
沒有已知的類型引數。這種情況下,T
被推斷為P
的類型引數。 -
P
具有已知的類型引A
,依據給定的配對模式,A
和T
會統一,且必須符合下列其中一個條件
最後,在滿足下列條件時,二個未繫結類型參數將會執行鬆散統一(依元素配對模式)
- 兩個類型執行精確統一。
- 其中一個類型為 已定義類型,而另一類型則為類型字面值,但並非介面,而且其底層類型依元素配對模式執行統一。
- 兩個類型皆為介面(但並非類型參數),且具有相同的 類型項,這兩個介面都包含或都不包含預設宣告的類型 comparable,對應的方法類型將會執行精確統一,而其中一個介面方法組必須是另一個介面方法組的子集。
- 僅有一個類型為介面(但並非類型參數),兩個類型的對應方法將會依元素配對模式執行統一,而介面方法組必須是另一個類型方法組的子集。
- 兩個類型的結構相同,且其元素類型依元素配對模式執行統一。