Go程式語言規範
語言版本 go1.26 (2026年1月12日)
簡介
Go是一種通用型程式語言,在設計時特別考量了系統程式開發的需求。它具備強型別、垃圾回收機制,並對並行程式設計提供明確的支援。程式由「套件」(package)所組成,這種套件特性使得相依性能夠被有效管理。
語法簡潔直觀,便於剖析,讓整合開發環境(IDE)等自動化工具能夠輕鬆進行程式碼分析。
主要特色:
- 強型別系統,減少執行期錯誤
- 自動垃圾回收,降低記憶體管理負擔
- 原生支援並行程式設計
- 模組化套件架構,利於大型專案維護
- 精簡語法設計,提高開發效率與可讀性
此特性組合使Go在雲端服務、分散式系統及高效能伺服器開發領域特別具有優勢。
符號表示法
語法使用擴展巴科斯範式(EBNF)(https://en.wikipedia.org/wiki/Wirth_syntax_notation)變體定義:
產生式由項目和以下運算符構成,並依優先級遞增排序:
命名規則說明:
- 小寫的「產生式名稱」用於辨識詞法單元(terminal token)
- 非終結符(non-terminal)使用駝峰式大小寫(CamelCase)
- 詞法標記以雙引號
""或反引號``包圍
特殊符號說明:
a … b形式表示從a到b的字符集合選擇- 水平省略號
…用於列舉或程式碼片段的非正式表示 - 請注意:單個
…字符(與三點...不同)並非Go語言的正式標記
版本控制連結: 形式為[[Go 1.xx](/cn/docs/spec#Language_versions)]的連結,表示相關語言特性(或其部分面向)隨著Go 1.xx版本進行了修改或新增,使用這些特性時需確保編譯器至少支援該語言版本。詳細變更請參考附錄中的版本說明連結。
原始碼表示
原始碼是UTF-8編碼的Unicode文字。文字未標準化,因此單一個重音碼點的字元是獨特的,不同於由重音和字母組合構建的相同字元;這些被視為兩個碼點。為簡化起見,本文檔將使用非限定術語字元來指代原始碼中的Unicode碼點。
每個碼點都是獨特的;例如,大寫和小寫字母是不同的字元。
實作限制:為與其他工具相容,編譯器可能不允許原始碼中的NUL字元(U+0000)。
實作限制:為與其他工具相容,編譯器可能忽略原始碼中第一個Unicode碼點的UTF-8編碼位元組順序標記(U+FEFF)。位元組順序標記可能不允許出現在原始碼的其他任何地方。
字元
下列術語用來表示特定的Unicode字元類別:
在Unicode標準8.0第4.5節「通用類別」中定義了一組字元類別。Go將所有屬於任何字母類別Lu, Ll, Lt, Lm或Lo的字元視為Unicode字母,將屬於數字類別Nd的字元視為Unicode數字。
字母和數字
底線字元_(U+005F)被視為小寫字母。
詞法元素
註解作為程式文件。有兩種形式:
- 行註解以字元序列
//開始,在行尾停止。 - 通用註解以字元序列
/*開始,在第一個後續字元序列*/停止。
註解不能開始於rune字面量或string字面量內部,或註解內部。不包含新行的通用註解表現為空格。任何其他註解表現為換行字元。
標記
標記構成Go語言的詞彙。有四類:識別字、關鍵字、運算子和標點符號以及字面量。由空格(U+0020)、水平定位符(U+0009)、歸位(U+000D)和換行字元(U+000A)形成的空白被忽略,除非它分隔本應合併為單個標記的標記。此外,換行字元或文件結束可能觸發分號的插入。在將輸入分解為標記時,下一個標記是形成有效標記的最長字元序列。
分號
形式語法在許多產生式中使用分號";"作為終止符。Go程式可以使用以下兩個規則省略大多數這些分號:
- 當輸入被分解為標記時,如果行的最終標記是
- 為允許複雜陳述佔用單行,分號可在閉合
")"或"}"前省略。
為反映慣用法,本文檔中的程式碼範例使用這些規則省略分號。
識別字
識別字命名程式實體,如變數和型別。識別字是一個或多個字母和數字的序列。識別字中的第一個字元必須是字母。
一些識別字是預宣告的。
關鍵字
以下關鍵字是保留的,不能用作識別字。
運算子和標點符號
以下字元序列表示運算子(包括指定運算子)和標點符號[Go 1.18]:
整數字面量
整數字面量是表示整數常數的數字序列。可選前綴設定非十進位基數:0b或0B表示二進位,0、0o或0O表示八進位,0x或0X表示十六進位[Go 1.13]。單個0被視為十進位零。在十六進位字面量中,字母a到f和A到F表示值10到15。
為便於閱讀,底線字元_可出現在基數前綴後或連續數字之間;此類底線不改變字面量的值。
浮點字面量
浮點字面量是浮點常數的十進位或十六進位表示。
十進位浮點字面量由整數部分(十進位數字)、小數點、小數部分(十進位數字)和指數部分(e或E後跟可選符號和十進位數字)組成。整數部分或小數部分之一可省略;小數點或指數部分之一可省略。指數值exp將尾數(整數和小數部分)按10exp縮放。
十六進位浮點字面量由0x或0X前綴、整數部分(十六進位數字)、基數點、小數部分(十六進位數字)和指數部分(p或P後跟可選符號和十進位數字)組成。整數部分或小數部分之一可省略;基數點也可省略,但指數部分是必需的。(此語法符合IEEE 754-2008 §5.12.3中給出的語法。)指數值exp將尾數(整數和小數部分)按2exp縮放[Go 1.13]。
為便於閱讀,底線字元_可出現在基數前綴後或連續數字之間;此類底線不改變字面量的值。
虛數字面量
虛數字面量表示複數常數的虛部。它由整數或浮點字面量後跟小寫字母i組成。虛數字面量的值是相應整數或浮點字面量的值乘以虛數單位_i_[Go 1.13]
為向後相容,完全由十進位數字(可能包含底線)組成的虛數字面量的整數部分被視為十進位整數,即使它以前導0開頭。
Rune字面量
Rune字面量表示rune常數,識別Unicode碼點的整數值。Rune字面量表示為單引號內的一個或多個字元,如'x'或'\n'。在引號內,除換行字元和未跳脫的單引號外,任何字元都可以出現。單引號字元表示字元本身的Unicode值,而以反斜線開始的多字元序列以各種格式編碼值。
最簡單的形式表示引號內的單個字元;由於Go原始碼是UTF-8編碼的Unicode字元,多個UTF-8編碼位元組可能表示單個整數值。例如,字面量'a'包含表示字面量a(Unicode U+0061, 值0x61)的單個位元組,而'ä'包含兩個位元組(0xc3 0xa4)表示a-分音符字面量(U+00E4, 值0xe4)。
幾種反斜線跳脫允許將任意值編碼為ASCII文字。有四種方式將整數值表示為數值常數:\x後跟恰好兩個十六進位數字;\u後跟恰好四個十六進位數字;\U後跟恰好八個十六進位數字;以及普通反斜線\後跟恰好三個八進位數字。每種情況下,字面量的值是相應基數中數字表示的值。
儘管這些表示都導致整數,但它們有不同的有效範圍。八進位跳脫必須表示0到255(含)之間的值。十六進位跳脫透過構造滿足此條件。跳脫\u和\U表示Unicode碼點,因此其中一些值是非法的,特別是那些超過0x10FFFF的值和代理半部分。
在反斜線後,某些單字元跳脫表示特殊值:
在rune字面量中反斜線後的未識別字元是非法的。
字串字面量
字串字面量表示透過連接字元序列獲得的字串常數。有兩種形式:原始字串字面量和解釋字串字面量。
原始字串字面量是反引號之間的字元序列,如`foo`。在引號內,除反引號外,任何字元都可以出現。原始字串字面量的值是引號之間未解釋的(隱式UTF-8編碼的)字元組成的字串;特別是,反斜線沒有特殊含義,字串可以包含換行字元。原始字串值中的歸位字元('\r')被丟棄。
解釋字串字面量是雙引號之間的字元序列,如"bar"。在引號內,除換行字元和未跳脫的雙引號外,任何字元都可以出現。引號之間的文字形成字面量的值,反斜線跳脫按rune字面量中的方式解釋(除了\'是非法的且\"是合法的),具有相同的限制。三位八進位(\nnn)和兩位十六進位(\xnn)跳脫表示結果字串的單個位元組;所有其他跳脫表示單個字元的(可能多位元組的)UTF-8編碼。因此,在字串字面量中,\377和\xFF表示值0xFF=255的單個位元組,而ÿ、\u00FF、\U000000FF和\xc3\xbf表示字元U+00FF的UTF-8編碼的兩個位元組0xc3 0xbf。
這些範例都表示相同的字串:
如果原始碼將字元表示為兩個碼點,如涉及重音和字母的組合形式,如果放在rune字面量中(它不是單個碼點)會出錯,如果放在字串字面量中會顯示為兩個碼點。
常數
有布林常數、rune常數、整數常數、浮點常數、複數常數和字串常數。Rune、整數、浮點和複數常數統稱為數值常數。
常數值由rune、整數、浮點、虛數或string字面量、表示常量的識別字、常量運算式、結果為常量的轉換或某些內建函數(如應用於常量參數的min或max、應用於某些值的unsafe.Sizeof、應用於某些運算式的cap或len、應用於複數常量的real和imag以及應用於數值常量的complex)的結果值表示。布林真值由預宣告常數true和false表示。預宣告識別字iota表示整數常數。
一般來說,複數常數是常量運算式的一種形式,在該節中討論。
數值常數表示任意精度的精確值,不會溢出。因此,沒有常數表示IEEE 754負零、無窮大和非數值。
常數可以是型別化或未型別化。字面量常數、true、false、iota以及僅包含未型別化常數運算元的某些常量運算式是未型別化的。
常數可以透過常數宣告或轉換明確指定型別,或在變數宣告、指定運算式中隱式指定型別,或作為運算式中的運算元。如果常數值不能表示為相應型別的值,則出錯。如果型別是型別參數,常數將轉換為型別參數的非常數值。
未型別化常數有預設型別,這是在需要型別化值的上下文中常數隱式轉換成的型別,例如,在i := 0這樣的短變數宣告中,沒有明確型別。未型別化常數的預設型別分別是bool、rune、int、float64、complex128或string,取決於它是布林、rune、整數、浮點、複數還是字串常數。
實作限制:儘管數值常數在語言中具有任意精度,但編譯器可能使用有限精度的內部表示來實作它們。也就是說,每個實作必須:
- 用至少256位元表示整數常數。
- 用至少256位元的尾數和至少16位元的帶符號二進位指數表示浮點常數(包括複數常數的部分)。
- 如果無法精確表示整數常數,則給出錯誤。
- 如果由於溢出而無法表示浮點或複數常數,則給出錯誤。
- 如果由於精度限制而無法表示浮點或複數常數,則四捨五入到最接近的可表示常數。
這些要求既適用於字面量常數,也適用於常量運算式的求值結果。
變數
變數是保存值的儲存位置。允許值的集合由變數的型別決定。
變數宣告或函式參數和結果的函式宣告或函式字面值的簽名保留命名變數的儲存。呼叫內建函式new或取複合字面值的位址在執行時期分配變數的儲存。這樣的匿名變數透過(可能是隱式的)指標間接引用。
陣列、切片和結構體型別的結構化變數有可以單獨定址的元素和欄位。每個這樣的元素都像變數一樣。
變數的靜態型別(或簡稱型別)是其宣告中給出的型別、new呼叫或複合字面值中提供的型別,或結構化變數的元素的型別。介面型別變數還有不同的動態型別,它是執行時期分配給變數的值的(非介面)型別(除非值是預宣告識別字nil,它沒有型別)。動態型別可能在執行期間變化,但儲存在介面變數中的值總是可指定給變數的靜態型別。
變數的值透過運算式中引用變數來取得;它是指定給變數的最新值。如果變數尚未指定值,則其值為其型別的零值。
型別
型別確定一組值以及特定於這些值的操作和方法。型別可以由型別名稱表示(如果有),如果是泛型型別,則必須後跟型別參數。型別也可以使用型別字面值指定,它從現有型別組合型別。
語言預宣告某些型別名稱。其他型別透過型別宣告或型別參數列表引入。複合型別—陣列、結構體、指標、函數、介面、切片、對應和通道型別—可以使用型別字面值建構。
預宣告型別、定義型別和型別參數稱為命名型別。如果別名宣告中給出的型別是命名型別,則別名表示命名型別。
布林型別
布林型別表示由預宣告常數true和false表示的布林真值集合。預宣告布林型別是bool;它是一種定義型別。
數值型別
整數、浮點或複數型別分別表示整數、浮點或複數值的集合。它們統稱為數值型別。預宣告與架構無關的數值型別有:
n位整數的值是n位元寬,並使用二補數算術表示。
還有一組預宣告具有實作特定大小的整數型別:
為避免可攜性問題,所有數值型別都是定義型別,因此除了byte(是uint8的別名)和rune(是int32的別名)外都是distinct的。當不同數值型別在運算式或指定中混合時,需要明確轉換。例如,即使int32和int在特定架構上大小相同,它們也不是相同型別。
字串型別
字串型別表示字串值集合。字串值是(可能為空的)位元組序列。位元組數稱為字串的長度,永不為負。字串是不可變的:一旦建立,就無法更改字串的內容。預宣告字串型別是string;它是一種定義型別。
字串s的長度可以使用內建函數len發現。如果字串是常數,則長度是編譯時期常數。字串的位元組可以透過整數索引0到len(s)-1存取。取這種元素的位址是非法的;如果s[i]是字串的第i個位元組,&s[i]是無效的。
陣列型別
陣列是稱為元素型別的單一型別的編號元素序列。元素數稱為陣列的長度,永不為負。
長度是陣列型別的一部分;它必須求值為非負常數可表示為int型別的值。陣列a的長度可以使用內建函數len發現。元素可以透過整數索引0到len(a)-1定址。陣列型別始終是一維的,但可以組合形成多維型別。
陣列型別T不能有型別為T的元素,或包含T作為組件的型別,直接或間接,如果這些包含型別僅是陣列或結構體型別。
切片型別
切片是底層陣列連續段的描述符,提供對該陣列中編號元素序列的存取。切片型別表示其元素型別所有陣列的切片的集合。元素數稱為切片的長度,永不為負。未初始化切片的值是nil。
切片s的長度可以使用內建函數len發現;與陣列不同,它在執行期間可能變化。元素可以透過整數索引0到len(s)-1定址。給定元素在切片中的索引可能小於其在底層陣列中的索引。
切片一旦初始化,總是與保存其元素的底層陣列相關聯。切片因此與其陣列及同一陣列的其他切片共享儲存;相比之下,不同陣列總是表示不同的儲存。
切片底層陣列可能延伸到切片末尾之外。容量是這種延伸的度量:它是切片長度和切片後陣列長度之和;可以透過從原始切片切片新切片來建立長度達到該容量的切片。切片a的容量可以使用內建函數cap(a)發現。
給定元素型別T的新初始化切片值可以使用內建函數make建立,該函數接受切片型別和指定長度和可選容量的參數。使用make建立的切片總是分配一個新的隱藏陣列,傳回的切片值引用該陣列。也就是說,執行
產生與分配陣列並切片它相同的切片,因此這兩個運算式等價:
與陣列一樣,切片始終是一維的,但可以組合建構高維物件。對於陣列的陣列,內部陣列在構造上總是相同長度;然而對於切片的切片(或陣列的切片),內部長度可能動態變化。此外,內部切片必須單獨初始化。### 結構體型別
結構體是稱為欄位的有名元素序列,每個元素都有名稱和型別。欄位名稱可以明確指定(IdentifierList)或隱式指定(EmbeddedField)。在結構體中,非空白欄位名稱必須是唯一的。
宣告了型別但沒有明確欄位名稱的欄位稱為嵌入欄位。嵌入欄位必須指定為型別名稱T或指向非介面型別名稱*T的指標,且T本身不能是指標型別或型別參數。未限定的型別名稱充當欄位名稱。
以下宣告是非法的,因為結構體型別中的欄位名必須是唯一的:
結構體x中嵌入欄位的欄位或方法 f被稱為提升,如果x.f是表示該欄位或方法f的合法選擇子。
提升的欄位表現得像結構體的普通欄位,只是它們不能在複合字面值中用作欄位名。
給定結構體型別S和型別名稱T,提升的方法按以下方式包含在結構體的方法集中:
- 如果
S包含嵌入欄位T,S和*S的方法集都包含接收器為T的提升方法。*S的方法集還包含接收器為*T的提升方法。 - 如果
S包含嵌入欄位*T,S和*S的方法集都包含接收器為T或*T的提升方法。
欄位宣告後可以跟一個可選的字串字面值標籤,它成為相應欄位宣告中所有欄位的屬性。空標籤字串等價於不存在的標籤。標籤透過反射介面可見,並參與結構體的型別識別,但除此之外被忽略。
結構體型別T不能包含型別為T的欄位,或包含T作為組件的型別,直接或間接,如果這些包含型別僅是陣列或結構體型別。
指標型別
指標型別表示給定型別變數的所有指標的集合,該型別稱為指標的基型別。未初始化指標的值是nil。
函數型別
函數型別表示具有相同參數和結果型別的所有函數的集合。未初始化函數型別變數的值是nil。
在參數或結果列表中,名稱(IdentifierList)必須全部存在或全部不存在。如果存在,每個名稱代表指定型別的一個項目(參數或結果),且簽名中非空白名稱必須是唯一的。如果不存在,每個型別代表該型別的一個項目。參數和結果列表總是用括號括起來,但如果有一個且僅有一個未命名的結果,它可以寫成未加括號的形式。
函數簽名中的最後一個傳入參數可以有前綴為...的型別。具有這種參數的函數稱為可變參數函數,可以用零個或多個參數呼叫該參數。
介面型別
介面型別定義型別集。介面型別的變數可以儲存型別集中任何型別的值。這種型別被稱為實作介面。未初始化介面型別變數的值是nil。
介面型別由介面元素列表指定。介面元素是方法或型別元素,其中型別元素是一組或多型別項的並集。型別項是單個型別或單個潛在型別。
基本介面
在其最基本的形式中,介面指定方法(可能為空)列表。這種介面定義的型別集是實作所有這些方法的型別的集合,相應的方法集完全由介面指定的方法組成。完全由方法列表定義型別集的介面稱為基本介面。
多個型別可能實作介面。例如,如果兩個型別S1和S2都有方法集
(其中T代表S1或S2),那麼File介面由S1和S2實作,不管S1和S2可能有的其他方法或共用的方法。
介面的每個成員型別都實作該介面。任何給定型別都可以實作幾個不同的介面。例如,所有型別都實作空介面,它代表所有(非介面)型別的集合:
為方便起見,預宣告型別any是空介面的別名。[Go 1.18]
類似地,考慮這個介面規範,它出現在型別宣告中,定義了一個名為Locker的介面:
如果S1和S2也實作
它們也實作Locker介面以及File介面。
嵌入介面
在稍更一般的形式中,介面T可以使用(可能限定的)介面型別名E作為介面元素。這稱為在T中嵌入介面E [Go 1.14]。T的型別集是T的明確宣告方法和嵌入介面型別集的交集。換句話說,T的型別集是實作T所有明確宣告方法和E所有方法的型別集 [Go 1.18]。
通用介面
在其最一般的形式中,介面元素也可以是任意型別項T,或形式為~T的項,指定潛在型別T,或項的並集t<sub>1</sub>|t<sub>2</sub>|…|t<sub>n</sub> [Go 1.18]。結合方法規範,這些元素使介面的型別集得以精確定義如下:
- 空介面的型別集是所有非介面型別的集合。
- 非空介面的型別集是其介面元素型別集的交集。
- 方法規範的型別集是方法集包含該方法的所有非介面型別的集合。
- 非介面型別項的型別集僅由該型別組成的集合。
- 形式為
~T的項的型別集是潛在型別為T的所有型別的集合。 - 項的並集
t<sub>1</sub>|t<sub>2</sub>|…|t<sub>n</sub>的型別集是各項型別集的並集。
"所有非介面型別的集合"不僅指程式中宣告的所有(非介面)型別,還包括所有可能程式中的所有可能型別,因此是無限的。同樣,給定實作特定方法的所有非介面型別,這些型別的方法集的交集將恰好包含該方法,即使程式中的所有型別總是將該方法與另一個方法配對。
通過構造,介面的型別集從不包含介面型別。
在形式為~T的項中,T的潛在型別必須是T本身,且T不能是介面。
並集元素表示型別集的並集:
形式為T或~T的項中的型別T不能是型別參數,且所有非介面項的型別集必須是兩兩不相交的(型別集的成對交集必須為空)。給定型別參數P:
實作限制:並集(有多個項)不能包含預宣告識別字comparable或指定方法的介面,或嵌入comparable或指定方法的介面。
非基本介面只能用作型別約束,或用作其他用作約束的介面的元素。它們不能是值或變數的型別,或其他非介面型別的組件。
介面型別T不能嵌入直接或間接是T、包含T或嵌入T的型別元素。
實作介面
如果滿足下列條件,型別T實作介面I:
T不是介面且是I型別集的元素;或T是介面且T的型別集是I型別集的子集。
如果T實作介面,則T型別的值實作介面。
對應型別
對應是無序的元素組,元素為一種型別(稱為元素型別),由另一型別的鍵索引(稱為鍵型別)。未初始化對應的值是nil。
鍵型別的運算元必須完全定義比較運算子 ==和!=;因此鍵型別不能是函數、對應或切片。如果鍵型別是介面型別,則這些比較運算子必須為動態鍵值定義;失敗將導致執行時期恐慌。
對應中元素的數量稱為其長度。對於對應m,可以使用內建函數len發現長度,且在執行期間可能變化。在執行期間,可以使用指定新增元素,使用索引運算式檢索元素;可以使用delete和clear內建函數刪除元素。
可以使用內建函數make建立新的空對應值,該函數接受對應型別和一個可選容量提示作為參數:
初始容量不限制其大小:對應會成長以適應其中儲存的項目數量,nil對應除外。nil對應等效於空對應,只是不能新增元素。
通道型別
通道提供了一種機制,用於並行執行的函數透過指定元素型別的傳送和接收值進行通訊。未初始化通道的值是nil。
可選的<-運算子指定通道的方向,傳送或接收。如果指定了方向,通道是定向的,否則是雙向的。通道可以透過指定或明確轉換限制為僅傳送或僅接收。
<-運算子與最左邊的chan關聯:
可以使用內建函數make建立新的初始化通道值,該函數接受通道型別和一個可選容量作為參數:
容量(以元素數量計)設定通道中緩衝區的大小。如果容量為零或省略,通道是無緩衝的,只有當傳送方和接收方都準備好時通訊才成功。否則,通道是有緩衝的,只要緩衝區未滿(傳送)或非空(接收),通訊就無需阻塞。nil通道永遠無法進行通訊。
通道可以使用內建函數close關閉。使用接收運算子的多值指定形式可以報告接收的值是否在通道關閉前傳送。
單個通道可以在傳送陳述、接收操作以及內建函數cap和len的呼叫中由任意數量的goroutines使用,無需進一步同步。通道充當先進先出佇列。例如,如果一個goroutine在通道上傳送值,第二個goroutine接收它們,則值按傳送順序接收。
型別和值的屬性
值的表示
預宣告型別(介面any和error除外)、陣列和結構體的值是獨立的:每個這樣的值包含其所有資料的完整副本,且這些型別的變數儲存整個值。例如,陣列變數為陣列的所有元素提供儲存(變數)。相應的零值是特定於值型別的;它們永遠不是nil。
非nil指標、函數、切片、對應和通道值包含對基礎資料的引用,這些資料可能被多個值共享:
介面值可能是獨立的,也可能包含對基礎資料的引用,取決於介面的動態型別。預宣告識別字nil是值可以包含引用的型別的零值。
當多個值共享基礎資料時,更改一個值可能會更改另一個值。例如,更改切片的元素將更改底層陣列中所有共享該陣列切片的元素。
潛在型別
每個型別T都有一個潛在型別:如果T是預宣告的布林、數值或字串型別之一,或者是型別字面值,則相應的潛在型別是T本身。否則,T的潛在型別是T宣告中引用型別的潛在型別。對於總是介面的型別參數,該型別是它的型別約束的潛在型別。
string、A1、A2、B1和B2的潛在型別是string。[]B1、B3和B4的潛在型別是[]B1。P的潛在型別是interface{}。
型別識別
兩種型別要麼是相同的("相同"),要麼是不同的。
命名型別總是與其他任何型別不同。否則,如果它們的潛在型別字面值結構等價,則兩種型別是相同的;也就是說,它們具有相同的字面值結構,相應的組件具有相同的型別。詳細來說:
- 如果兩種陣列型別具有相同的元素型別且長度相同,則它們是相同的。
- 如果兩種切片型別具有相同的元素型別,則它們是相同的。
- 如果兩種結構體型別具有相同順序的欄位,且對應欄位對具有相同的名稱、相同的型別、相同的標籤,並且要麼都是嵌入的,要麼都不是嵌入的,則它們是相同的。來自不同套件的未匯出欄位名稱總是不同的。
- 如果兩種指標型別具有相同的基型別,則它們是相同的。
- 如果兩種函數型別具有相同數量的參數和結果值,對應的參數和結果型別是相同的,且要麼都是可變參數,要麼都不是,則它們是相同的。參數和結果名稱不需要匹配。
- 如果兩種介面型別定義相同的型別集,則它們是相同的。
- 如果兩種對應型別具有相同的鍵和元素型別,則它們是相同的。
- 如果兩種通道型別具有相同的元素型別且方向相同,則它們是相同的。
- 如果兩種實例化型別的定義型別和所有型別參數都是相同的,則它們是相同的。
給定宣告
以下型別是相同的:
A0、A1和[]stringA2和struct{ a, b int }A3和intA4、func(int, float64) *[]string和A5B0和C0D0[int, string]和E0[]int和[]intstruct{ a, b *B5 }和struct{ a, b *B5 }func(x int, y float64) *[]string、func(int, float64) (result *[]string)和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型別的值表示,如果x可由T型別集中的每個型別的值表示。
方法集
型別的方法集決定了可以在該型別的運算元上呼叫的方法。每種型別都有一個關聯的(可能為空的)方法集:
- 定義型別
T的方法集包括所有接收器型別為T的方法。 - 指向定義型別
T的指標(其中T既不是指標也不是介面)的方法集包括所有接收器為*T或T宣告的方法。 - 介面型別的方法集是介面型別集中每種型別的方法集的交集(結果方法集通常只是介面中宣告的方法集)。
進一步規則適用於包含嵌入欄位的結構體(和指向結構體的指標),如結構體型別部分所述。任何其他型別都有空方法集。
塊
塊是可能為空的宣告和陳述序列,位於匹配的大括號內。
除了原始碼中的明確塊外,還有隱式塊:
- 宇宙塊包含所有Go原始碼。
- 每個套件都有一個套件塊,包含該套件的所有Go原始碼。
- 每個檔案都有一個檔案塊,包含該檔案中的所有Go原始碼。
- 每個"if"、"for"和"switch"陳述被認為在其自己的隱式塊中。
- "switch"或"select"陳述中的每個子句都充當一個隱式塊。
塊嵌套並影響作用域。
宣告和作用域
宣告將非空白識別字繫結到常數、型別、型別參數、變數、函式、標籤或套件。程式中每個識別字都必須宣告。在同一個塊中,沒有識別字可以宣告兩次,且沒有識別字可以在檔案塊和套件塊中同時宣告。
空白識別字可以像其他識別字一樣在宣告中使用,但它不引入繫結,因此不被宣告。在套件塊中,識別字init只能用於init函式宣告,且與空白識別字一樣,它不引入新的繫結。
宣告識別字的作用域是原始碼中識別字表示指定常數、型別、變數、函式、標籤或套件的範圍。
Go使用塊進行詞法作用域:
- 預宣告識別字的作用域是宇宙塊。
- 在頂層(函式外部)宣告的常數、型別、變數或函式(但不是方法)的識別字的作用域是套件塊。
- 匯入套件的套件名的作用域是包含匯入宣告的檔案的檔案塊。
- 表示方法接收器、函式參數或結果變數的識別字的作用域是函式體。
- 表示函式的型別參數或方法接收器宣告的型別參數識別字的作用域從函式名後開始,到函式體結束時結束。
- 表示型別的型別參數的識別字的作用域從型別名後開始,到TypeSpec結束時結束。
- 在函式內宣告的常數或變數識別字的作用域從ConstSpec或VarSpec(短變數宣告的ShortVarDecl)結束時開始,到最內層包含塊結束時結束。
- 在函式內宣告的型別識別字的作用域從TypeSpec中的識別字開始,到最內層包含塊結束時結束。
在塊中宣告的識別字可以在內部塊中重新宣告。當內部宣告的識別字在作用域時,它表示內部宣告。
套件子句不是宣告;套件名不出現在任何作用域中。它的目的是識別屬於同一個套件的檔案,並指定匯入宣告的預設套件名。
標籤作用域
標籤由標籤陳述宣告,並在"break"、"continue"和"goto"陳述中使用。定義了從未使用的標籤是非法的。與其他識別字不同,標籤不是塊作用域,且不與非標籤識別字衝突。標籤的作用域是宣告它的函式體,不包括任何嵌套函式的體。
空白識別字
空白識別字由底線字元_表示。它在宣告、運算元和指定陳述中作為匿名佔位符而不是常規(非空白)識別字使用,具有特殊含義。
預宣告識別字
以下識別字在宇宙塊中隱式宣告 [Go 1.18] [Go 1.21]:
匯出識別字
識別字可以匯出,以允許從另一個套件存取它。如果滿足下列兩個條件,則識別字是匯出的:
所有其他識別字未匯出。
識別字唯一性
給定一組識別字,如果一個識別字與該組中的其他識別字不同,則稱該識別字為唯一。如果拼寫不同,或出現在不同套件中且未匯出,則兩個識別字不同。否則,它們是相同的。
常數宣告
常數宣告將識別字列表(常數名)繫結到常數運算式的值列表。識別字數量必須與運算式數量相等,且左邊的n個識別字繫結到右邊的n個運算式的值。
如果型別存在,所有常數採用指定的型別,運算式必須可指定給該型別,該型別不能是型別參數。如果省略型別,常數採用對應運算式的型別。如果運算式值是未型別化的常數,則宣告的常數保持未型別化,且常數識別字表示常數值。例如,如果運算式是浮點字面值,即使字面值的分數部分為零,常數識別字也表示浮點常數。
在括號內的const宣告列表中,除了第一個ConstSpec外,任何ConstSpec都可以省略運算式列表。空列表等價於文字替換前一個非空運算式列表及其型別(如果有的話)。因此,省略運算式列表等價於重複前一個列表。識別字數量必須等於前一個列表中的運算式數量。結合iota常數產生器,此機制允許輕量級順序值宣告:
Iota
在常數宣告中,預宣告識別字iota表示連續的無型別整數常數。其值是相應ConstSpec在該常數宣告中的索引,從零開始。它可用於建構一組相關的常數:
根據定義,在同一個ConstSpec中多次使用iota具有相同的值:
最後一個例子利用了最後一個非空運算式列表的隱式重複。
型別宣告
型別宣告將識別字(型別名)繫結到型別。型別宣告有兩種形式:別名宣告和型別定義。
別名宣告
別名宣告將識別字繫結到給定型別 [Go 1.9]。
在識別字的作用域內,它作為給定型別的別名。
如果別名宣告指定了型別參數 [Go 1.24],則型別名表示泛型別名。泛型別名在使用時必須被實例化。
在別名宣告中,給定型別不能是型別參數。
型別定義
型別定義建立具有相同潛在型別和操作的新定義型別,並將識別字(型別名)繫結到它。
新型別被稱為定義型別。它與任何其他型別都不同,包括建立它的型別。
定義型別可以有其關聯的方法。它不從給定型別繼承任何方法,但介面型別或複合型別元素的方法集保持不變:
型別定義可用於定義不同的布林、數值或字串型別,並與它們關聯方法:
如果型別定義指定了型別參數,則型別名表示泛型型別。泛型型別在使用時必須被實例化。
在型別定義中,給定型別不能是型別參數。
泛型型別也可以有其關聯的方法。在這種情況下,方法接收器必須宣告與泛型型別定義中相同數量的型別參數。
型別參數宣告
型別參數列表宣告泛型函式或型別宣告的型別參數。型別參數列表看起來像普通的函式參數列表,除了型別參數名稱都必須存在且列表用方括號而不是括號包圍 [Go 1.18]。
列表中所有非空白名稱都必須是唯一的。每個名稱宣告一個型別參數,它是新的不同命名型別,作為宣告中(尚未)未知型別的佔位符。型別參數在泛型函式或型別被實例化時被替換為型別參數。
就像每個普通函式參數都有參數型別一樣,每個型別參數都有相應的(元)型別,這被稱為其型別約束。
當泛型型別的型別參數列表宣告單個型別參數P且約束C使得文字P C形成有效運算式時,會產生解析歧義:
在這些罕見情況下,型別參數列表與運算式無法區分,型別宣告被解析為陣列型別宣告。為了消除歧義,將約束嵌入介面或使用尾隨逗號:
型別參數也可以由泛型型別的方法宣告的接收器規範宣告。
在泛型型別T的型別參數列表中,型別約束不能(直接,或間接透過另一個泛型型別的型別參數列表)引用T。
型別約束
型別約束 是一種介面,它定義了相應型別參數允許的型別參數集合,並控制該型別參數值支援的操作 [Go 1.18]。
如果約束是形如 interface{E} 的介面字面值,其中 E 是嵌入的型別元素(不是方法),在型別參數列表中,為了方便可以省略外圍的 interface{ … }:
預宣告的介面型別 comparable 表示所有嚴格可比較的非介面型別的集合 [Go 1.18]。
儘管非型別參數的介面是可比較的,但它們不是嚴格可比較的,因此它們不實作 comparable。但是,它們滿足 comparable。
comparable 介面和(直接或間接)嵌入了 comparable 的介面只能用作型別約束。它們不能作為值或變數的型別,也不能作為其他非介面型別的組成部分。
滿足型別約束
如果型別參數 T 是 C 定義的型別集合中的元素,則稱型別參數 T 滿足型別約束 C;換句話說,如果 T 實作了 C。作為例外,嚴格可比較的型別約束也可以透過可比較的(不一定嚴格可比較)型別參數滿足 [Go 1.20]。更精確地說:
型別 T 滿足 約束 C,如果
由於約束滿足規則中的例外,比較型別參數型別的運算元可能在執行時期 panic(即使可比較的型別參數始終是嚴格可比較的)。
變數宣告
變數宣告會建立一個或多個變數,將相應的識別字與它們繫結,並為每個變數提供型別和初始值。
如果提供了運算式列表,則按照指定陳述的規則初始化變數。否則,每個變數將初始化為它的零值。
如果存在型別,則每個變數都將被賦予該型別。否則,每個變數將獲得相應初始化值的型別。如果該值是無型別常數,則首先將其隱式轉換為它的預設型別;如果它是無型別的布林值,則首先隱式轉換為 bool 型別。不能使用預宣告識別字 nil 來初始化沒有明確型別的變數。
實作限制:如果變數從未被使用,編譯器可以禁止在函式體內宣告變數。
短變數宣告
短變數宣告 使用如下語法:
它是省略了型別的常規變數宣告的簡寫形式:
與常規變數宣告不同,短變數宣告可以重新宣告變數,前提是它們最初已在同一塊中(如果塊是函式體,則為參數列表)以相同型別宣告,且至少有一個非空變數是新的。因此,重新宣告只能出現在多變數短宣告中。重新宣告不會引入新變數;它只是為原始變數分配新值。:= 左側的非空變數名必須是唯一的。
短變數宣告只能出現在函式內部。在某些上下文中,例如 "if"、"for" 或 "switch" 陳述的初始化部分,它們可用於宣告本地臨時變數。
函式宣告
函式宣告將識別字(即函式名)與函式繫結。
如果函式的簽名宣告了結果參數,則函式體的陳述列表必須以終止陳述結束。
如果函式宣告指定了型別參數,則函式名表示一個泛型函式。泛型函式在使用前必須實例化。
沒有型別參數的函式宣告可以省略函式體。這種宣告提供了在 Go 外部實作的函式的簽名,例如組譯程式。
方法宣告
方法是具有接收器的函式。方法宣告將識別字(即方法名)與方法繫結,並將方法與接收器的基本型別關聯。
接收器透過位於方法名之前的額外參數部分指定。該參數部分必須宣告一個非可變參數,即接收器。其型別必須是已定義的型別 T 或指向已定義型別 T 的指標,可能後跟用方括號括起來的型別參數名稱列表 [P1, P2, …]。T 被稱為接收器基本型別。接收器的基本型別不能是指標或介面型別,且它必須在同一套件中定義。方法被稱為綁定到其接收器的基本型別,方法名僅在型別 T 或 *T 的選擇子中可見。
非空接收器識別字在方法簽名中必須是唯一的。如果接收器的值在方法體內未被引用,則可以在宣告中省略其識別字。這通常也適用於函式和方法的參數。
對於基本型別,綁定到它的非空方法名必須是唯一的。如果基本型別是結構體型別,則非空方法和欄位名必須不同。
給定已定義的型別 Point,宣告
將方法 Length 和 Scale 與接收器型別 *Point 綁定到基本型別 Point。
如果接收器的基本型別是泛型型別,則接收器規範必須宣告相應的方法使用的型別參數。這使得接收器的型別參數對方法可用。從語法上講,這種型別參數宣告看起來像接收者基本型別的實例化:型別參數必須是表示正在宣告的型別參數的識別字,每個接收者基本型別的型別參數對應一個。型別參數名不需要與接收者基本型別定義中的相應參數名匹配,所有非空參數名在接收者參數部分和方法簽名中必須是唯一的。接收者型別參數約束由接收者基本型別定義隱含:相應的型別參數具有相應的約束。
如果接收器型別由(指向)別名表示,則別名不能是泛型的,也不能表示實例化的泛型型別,無論是直接還是透過另一個別名間接表示,也不考慮指標間接。
運算式
運算式透過將運算子和函式應用於運算元來指定值的計算。
運算元
運算元表示運算式中的基本值。運算元可以是字面值、(可能限定的)非空識別字,表示常數、變數或函式,或者括號內的運算式。
表示泛型函式的運算元名稱後面可以跟一個型別參數列表;結果運算元是實例化的函式。
實作限制:如果運算元的型別是具有空型別集合的型別參數,編譯器無需報告錯誤。具有此類型別參數的函式無法實例化;任何嘗試都將在實例化位置導致錯誤。
限定識別字
限定識別字 是帶有套件名前綴的識別字。套件名和識別字都不能是空的。
限定識別字存取不同套件中的識別字,該套件必須匯入。該識別字必須是匯出的,並在該套件的套件塊中宣告。
複合字面值
複合字面值每次求值時都會為結構體、陣列、切片和對應建構新型別的值。它們由字面值型別後跟一個用大括號括起來的元素列表組成。每個元素前面可以有相應的鍵。
除非 LiteralType 是型別參數,否則其潛在型別必須是結構體、陣列、切片或對應型別(語法強制此約束,除非型別作為 TypeName 給出)。如果 LiteralType 是型別參數,則其型別集合中的所有型別必須具有相同的潛在型別,且必須是有效的複合字面值型別。元素和鍵的型別必須是可指定到型別 T 的相應欄位、元素和鍵型別;沒有額外的轉換。對於結構體字面值,鍵解釋為欄位名;對於陣列和切片字面值,鍵解釋為索引;對於對應字面值,鍵解釋為鍵。對於對應字面值,所有元素必須有鍵。指定具有相同欄位名或常數鍵值的多個元素是錯誤的。對於非常數對應鍵,請參閱求值順序部分。
對於結構體字面值,適用以下規則:
- 鍵必須是結構體型別中宣告的欄位名。
- 不包含任何鍵的元素列表必須按欄位宣告的順序列出每個結構體欄位的元素。
- 如果任何元素有鍵,則每個元素都必須有鍵。
- 包含鍵的元素列表不需要為每個結構體欄位提供元素。省略的欄位將獲取該欄位的零值。
- 字面值可以省略元素列表;這樣的字面值求值為其型別的零值。
- 為屬於不同套件的結構體的非匯出欄位指定元素是錯誤的。
給定宣告
可以寫成
對於陣列和切片字面值,適用以下規則:
- 每個元素都有一個關聯的整數索引,標記其在陣列中的位置。
- 帶鍵的元素使用鍵作為其索引。鍵必須是非負常數,可表示為
int型別的值;如果鍵有型別,則必須是整數型別。 - 不帶鍵的元素使用前一個元素的索引加一。如果第一個元素沒有鍵,則其索引為零。
取值複合字面值會產生一個指向用字面值值初始化的唯一變數的指標。
注意,切片或對應型別的零值與同型別的初始化但空值不同。因此,對空切片或對應複合字面值取值的效果與使用new分配新切片或對應值的效果不同。
陣列字面值的長度是字面值型別中指定的長度。如果字面值中提供的元素少於長度,則缺失的元素將設定為陣列元素型別的零值。提供超出陣列索引範圍的索引值是錯誤的。符號 ... 指定陣列長度等於最大元素索引加一。
切片字面值描述整個底層陣列字面值。因此,切片字面值的長度和容量是最大元素索引加一。切片字面值具有以下形式
且是應用於陣列的切片操作的簡寫:
在陣列、切片或對應型別 T 的複合字面值中,如果元素或對應鍵本身是複合字面值,則可以省略相應的 LiteralType(如果它與 T 的元素或鍵型別相同)。類似地,如果元素或鍵是複合字面值的位址,則可以省略 &T(當元素或鍵型別為 *T 時)。
當使用 LiteralType 的 TypeName 形式的複合字面值出現在 "if"、"for" 或 "switch" 陳述塊的關鍵字和左大括號之間,且複合字面值未用括號、方括號或大括號括起來時,會出現解析歧義。在這種罕見情況下,字面值的左大括號被錯誤地解析為引入陳述塊的大括號。為解決此歧義,複合字面值必須出現在括號內。
有效的陣列、切片和對應字面值範例:
函數字面值
函數字面值表示匿名函式。函數字面值不能宣告型別參數。
函數字面值可以指派給變數或直接呼叫。
函數字面值是_閉包_:它們可以引用周圍函式中定義的變數。這些變數在周圍函式和函數字面值之間共享,並且只要它們可存取,就會一直存在。
基本運算式
基本運算式是一元和二元運算式的運算元。
選擇子
表示值 x(或有時是 *x;見下文)的欄位或方法 f。識別字 f 稱為(欄位或方法)選擇子;它不能是空識別字。選擇子運算式的型別是 f 的型別。如果 x 是套件名,請參閱限定識別字部分。
選擇子 f 可以表示型別 T 的欄位或方法 f,也可以引用 T 的嵌套嵌入欄位的欄位或方法 f。為到達 f 而遍歷的嵌入欄位的數量稱為其在 T 中的_深度_。在 T 中宣告的欄位或方法 f 的深度為零。在 T 的嵌入欄位 A 中宣告的欄位或方法 f 的深度是 f 在 A 中的深度加一。
選擇子適用以下規則:
- 對於型別為
T或*T的值x,其中T不是指標或介面型別,x.f表示T中深度最淺的欄位或方法。如果深度最淺的f沒有唯一一個,則選擇子運算式非法。 - 對於型別為
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會導致執行時期恐慌。
例如,給定宣告:
可以寫成:
但以下無效:
方法運算式
如果 M 在型別 T 的方法集中,則 T.M 是一個函式,可以像普通函式一樣呼叫,參數與 M 相同,但前綴一個額外參數,即方法的接收器。
考慮一個結構體型別 T,有兩個方法 Mv(接收器型別為 T)和 Mp(接收器型別為 *T)。
運算式
生成一個等價於 Mv 的函式,但明確接收器作為第一個參數,其簽名為
該函式可以明確接收器正常呼叫,因此以下五次呼叫等價:
類似地,運算式
生成一個表示 Mp 的函式值,簽名為
對於值接收器的方法,可以派生出一個明確指標接收器的函式,因此
生成一個表示 Mv 的函式值,簽名為
這樣的函式透過接收器間接傳遞值,以作為接收器傳遞給底層方法;方法不會覆蓋函式呼叫中傳遞位址的值。
最後一種情況,為指標接收器方法派生值接收器函式,是非法的,因為指標接收器方法不在值型別的方法集中。
從方法派生的函式值使用函式呼叫語法呼叫;接收器作為呼叫的第一個參數提供。即,給定 f := T.Mv,f 以 f(t, 7) 呼叫,而非 t.f(7)。要建構繫結接收器的函式,使用函數字面值或方法值。
從介面型別的方法派生函式值是合法的。結果函式明確接收該介面型別。
方法值
如果運算式 x 具有靜態型別 T,且 M 在型別 T 的方法集中,則 x.M 稱為_方法值_。方法值 x.M 是一個函式值,可以使用相同參數像 x.M 的方法呼叫一樣呼叫。運算式 x 在方法值求值期間被求值並儲存;儲存的副本隨後作為接收者用於任何呼叫,呼叫可能在之後執行。
型別 T 可以是介面或非介面型別。
如方法表達式討論中所述,考慮一個結構體型別 T,有兩個方法 Mv(接收器型別為 T)和 Mp(接收器型別為 *T)。
運算式
生成一個型別為
的函式值。以下兩次呼叫等價:
類似地,運算式
生成一個型別為
的函式值。
與選擇子類似,使用指標引用值接收者的非介面方法會自動解引用該指標:pt.Mv 等價於 (*pt).Mv。
與方法呼叫類似,使用可定址值引用指標接收者的非介面方法會自動取該值的位址:t.Mp 等價於 (&t).Mp。
儘管上述範例使用非介面型別,但從介面型別的值建立方法值也是合法的。
索引運算式
形如
的基本運算式表示由 x 索引的陣列、指向陣列的指標、切片、字串或對應 a 的元素。值 x 分別稱為_索引_或_映射鍵_。適用以下規則:
如果 a 不是對應也不是型別參數:
- 索引
x必須是無型別常數,或其型別必須是整數或型別參數(其型別集合僅包含整數型別) - 常數索引必須非負且可表示為
int型別的值 - 無型別常數索引將賦予型別
int - 索引
x在0 <= x < len(a)範圍內為_在範圍內_,否則為_超出範圍_
對於陣列型別 A 的 a:
對於指向陣列型別的指標的 a:
a[x]是(*a)[x]的簡寫
對於切片型別 S 的 a:
- 如果
x在執行時期超出範圍,發生執行時期恐慌 a[x]是指索引x的切片元素,其型別為S的元素型別
對於字串型別的 a:
對於對應型別 M 的 a:
對於型別參數型別 P 的 a:
- 索引運算式
a[x]必須對P型別集合中所有型別的值有效。 P型別集合中所有型別的元素型別必須相同。在此上下文中,字串型別的元素型別是byte。- 如果
P的型別集合中有對應型別,則該型別集合中的所有型別必須都是對應型別,且相應的鍵型別必須全部相同。 a[x]是指索引x的陣列、切片或字串元素,或鍵為x的對應元素,其型別為P實例化時的(相同)元素型別。- 如果
P的型別集合包含字串型別,則a[x]不能被賦值。
否則 a[x] 非法。
在指定陳述或特殊形式初始化中使用的對應 a(型別為 map [K]V)的索引運算式
會生成一個額外的無型別的布林值。如果鍵 x 存在於對應中,ok 為 true,否則為 false`。
向 nil 對應的元素賦值會導致執行時期恐慌。
切片運算式
切片運算式從字串、陣列、指向陣列的指標或切片操作數構造子字串或切片。有兩種變體:指定上下界的基本形式,以及還指定容量界限的完整形式。
如果操作數型別是型別參數,除非其型別集合包含字串型別,否則型別集合中的所有型別必須具有相同的潛在型別,且切片運算式必須對那種型別的操作數有效。如果型別集合包含字串型別,它也可以包含潛在型別為 []byte 的位元組切片。此時,切片運算式必須對 string 型別的操作數有效。
基本切片運算式
對於字串、陣列、指向陣列的指標或切片 a,基本運算式
構造子字串或切片。索引 low 和 high 選擇操作數 a 中出現在結果中的元素。結果從索引 0 開始,長度等於 high - low。對陣列 a 切片後
切片 s 具有型別 []int,長度 3,容量 4,元素為
為方便起見,可以省略任意索引。缺失的 low 索引預設為 0;缺失的 high 索引預設為切片操作數的長度:
如果 a 是指向陣列的指標,a[low : high] 是 (*a)[low : high] 的簡寫。
對於陣列或字串,索引在 0 <= low <= high <= len(a) 範圍內為_在範圍內_,否則為_超出範圍_。對於切片,上界是切片容量 cap(a) 而非長度。常數索引必須非負且可表示為 int 型別的值;對於陣列或常數字串,常數索引也必須在範圍內。如果兩個索引都是常數,必須滿足 low <= high。如果索引在執行時期超出範圍,發生執行時期恐慌。
除了無型別字串,如果切片操作數是字串或切片,切片操作結果是操作數型別的非常量值。對於無型別字串操作數,結果是 string 型別的非常量值。如果切片操作數是陣列,它必須是可定址的,且切片操作結果是陣列元素型別的切片。
如果有效切片表達式的切片操作數是 nil 切片,結果是 nil 切片。否則,如果結果是切片,它與操作數共享底層陣列。
完整切片運算式
對於陣列、指向陣列的指標或切片 a(但非字串),基本運算式
構造與基本切片運算式 a[low : high] 型別相同、長度和元素相同的切片。此外,它透過設定 max - low 控制結果切片的容量。只能省略第一個索引;它預設為 0。對陣列 a 切片後
切片 t 具有型別 []int,長度 2,容量 4,元素為
對於基本切片運算式,如果 a 是指向陣列的指標,a[low : high : max] 是 (*a)[low : high : max] 的簡寫。如果切片操作數是陣列,它必須是可定址的。
索引在 0 <= low <= high <= max <= cap(a) 範圍內為_在範圍內_,否則為_超出範圍_。常數索引必須非負且可表示為 int 型別的值;對於陣列,常數索引也必須在範圍內。如果多個索引是常數,存在的常數必須彼此在範圍內。如果索引在執行時期超出範圍,發生執行時期恐慌。
型別斷言
對於介面型別(但不是型別參數)的運算式 x 和型別 T,基本運算式
斷言 x 不為 nil 且 x 中儲存的值型別為 T。記號 x.(T) 稱為_型別斷言_。
更精確地說,如果 T 不是介面型別,x.(T) 斷言 x 的動態型別與型別 T 相同。此時,T 必須實作 x 的(介面)型別;否則型別斷言無效,因為 x 不可能儲存 T 型別的值。如果 T 是介面型別,x.(T) 斷言 x 的動態型別實作介面 T。
如果型別斷言成立,運算式的值是 x 中儲存的值,其型別為 T。如果型別斷言為假,發生執行時期恐慌。換句話說,即使 x 的動態型別僅在執行時期可知,x.(T) 的型別在正確程式中已知為 T。
在指定陳述或特殊形式初始化中使用的型別斷言
會產生一個額外的無型別布林值。如果斷言成立,ok 的值為 true;否則為 false,且 v的值為型別T` 的零值。在這種情況下,不會發生執行時期恐慌。
呼叫
給定一個函數型別的運算式 f,
用參數 a1, a2, … an 呼叫 f。除一個特殊情況外,參數必須是可指定給 f 的參數型別的單值運算式,並在函數呼叫前求值。運算式的型別是 f 的結果型別。方法呼叫類似,但方法本身指定為方法接收者類型值上的選擇子。
如果 f 表示泛型函數,則必須實例化後才能呼叫或用作函數值。
如果 f 的型別是型別參數,其型別集合中的所有型別必須具有相同的潛在型別,且必須是函數型別,函數呼叫必須對那種型別有效。
在函數呼叫中,函數值和參數按常規順序求值。求值後,為函數的變數(包括參數和結果)分配新儲存。然後,呼叫參數被_傳遞_給函數,這意味著它們被指定給相應的函數參數,被調函數開始執行。函數返回時,返回參數傳回呼叫者。
呼叫 nil 函數值會導致執行時期恐慌。
作為特殊情況,如果函數或方法 g 的返回值數量與另一個函數或方法 f 的參數數量相同,且可單獨指定給 f 的參數,則呼叫 f(g(_parameters_of_g_)) 會將 g 的返回值按順序傳遞給 f 的參數後呼叫 f。f 的呼叫除 g 的呼叫外不能有其他參數,且 g 必須至少有一個返回值。如果 f 有最終的 ... 參數,它將被分配 g 的常規參數指定後剩餘的返回值。
方法呼叫 x.m() 在(x 的型別的)方法集包含 m 且參數列表可指定給 m 的參數列表時有效。如果 x 是可定址的且 &x 的方法集包含 m,則 x.m() 是 (&x).m() 的簡寫:
沒有獨立的方法型別,也沒有方法字面值。
向 ... 參數傳遞參數
如果 f 是可變參數且最終參數 p 的型別為 ...T,則 f 內部 p 的型別等價於 []T。如果 f 呼叫時沒有為 p 提供實際參數,傳遞給 p 的值是 nil。否則,傳遞的值是型別 []T 的新切片,具有新底層陣列,其連續元素為實際參數(都必須可指定給 T)。因此切片的長度和容量是繫結到 p 的參數數量,每次呼叫可能不同。
給定函數和呼叫
在 Greeting 內,who 在第一次呼叫時為 nil,第二次呼叫時為 []string{"Joe", "Anna", "Eileen"}。
如果最終參數可指定給切片型別 []T 且後跟 ...,它將原樣作為 ...T 參數的值傳遞。此時不建立新切片。
給定切片 s 和呼叫
在 Greeting 內,who 將與 s 具有相同值和底層陣列。
實例化
泛型函數或型別透過將_型別參數_替換為_型別實參_來_實例化_ [Go 1.18]。實例化分兩步:
實例化型別產生新的非泛型命名型別;實例化函數產生新的非泛型函數。
使用泛型函數時,型別實參可以明確提供,也可以部分或完全從使用函數的上下文中推斷。只要可推斷,如果函數:
則可以完全省略型別實參列表。
在所有其他情況下,必須提供(可能部分)型別實參列表。如果型別實參列表缺失或部分,所有缺失的型別實參必須能從使用函數的上下文中推斷。
部分型別實參列表不能為空;至少第一個實參必須存在。列表是完整型別實參列表的前綴,剩餘實參待推斷。通俗地說,型別實參可從"右到左"省略。
對於泛型型別,所有型別實參必須始終明確提供。
型別推斷
泛型函數的使用中,如果型別實參能從使用函數的上下文中_推斷_(包括函數型別參數的約束),則可以省略部分或全部型別實參。如果可推斷缺失的型別實參且實例化在推斷的型別實參下成功,則型別推斷成功;否則型別推斷失敗,程式無效。
型別推斷利用成對型別間的型別關係進行推斷:例如,函數參數必須可指定給對應的函數參數;這建立了參數型別與參數型別的關係。如果這兩個型別之一包含型別參數,型別推斷會尋找取代型別參數的實參,使可指定關係成立。類似地,型別推斷利用型別實參必須滿足對應型別參數的約束的事實。
每對匹配型別對應一個包含一個或多個型別參數的_型別方程_(可能來自多個泛型函數)。推斷缺失型別實參意味著求解所得型別方程組對相應型別參數的值。
例如,給定
程式有效時,型別 Slice 的變數 s 必須可指定給函數參數型別 S。為降低複雜性,型別推斷忽略指定方向性,因此 Slice 和 S 的型別關係可表示為(對稱的)型別方程 Slice ≡<sub>A</sub> S(或 S ≡<sub>A</sub> Slice),其中 ≡<sub>A</sub> 中的 <sub>A</sub> 表示左右型別必須按可指定規則匹配(詳見型別統一部分)。類似地,型別參數 S 必須滿足約束 ~[]E。這可表示為 S ≡<sub>C</sub> ~[]E,其中 X ≡<sub>C</sub> Y 表示 "X 滿足約束 Y"。這些觀察導致兩個方程
現在可求解型別參數 S 和 E。由 (1) 編譯器可推斷 S 的型別實參是 Slice。類似地,因為 Slice 的潛在型別是 []int 且 []int 必須匹配約束中的 []E,編譯器可推斷 E 必須是 int。因此,對這兩個方程,型別推斷得出
給定型別方程組,待求解的型別參數是需要實例化且未提供明確型別實參的函數的型別參數。這些型別參數稱為_綁定_型別參數。例如,在 dedup 範例中,型別參數 S 和 E 綁定到 dedup。泛型函數呼叫的參數可以是泛型函數本身。該函數的型別參數包含在綁定型別參數集合中。函數參數的型別可能包含其他函數的型別參數(如包含函數呼叫的泛型函數)。這些型別參數可能出現在型別方程中,但在該上下文中不綁定。型別方程始終只針對綁定型別參數求解。
型別推斷支援泛型函數呼叫和泛型函數指定給(明確函數型別的)變數。這包括將泛型函數作為參數傳遞給其他(可能也是泛型的)函數,以及將泛型函數作為結果回傳。型別推斷對每種情況的特定方程組操作。方程如下(為清晰省略型別實參列表):
-
對函數呼叫
f(a<sub>0</sub>, a<sub>1</sub>, …),其中f或函數參數a<sub>i</sub>是泛型函數:
每對對應的函數參數(a<sub>i</sub>, p<sub>i</sub>)(其中a<sub>i</sub>不是無型別常數)生成方程typeof(p<sub>i</sub>) ≡<sub>A</sub> typeof(a<sub>i</sub>)。
如果a<sub>i</sub>是無型別常數c<sub>j</sub>且typeof(p<sub>i</sub>)是綁定型別參數P<sub>k</sub>,則對(c<sub>j</sub>, P<sub>k</sub>)單獨收集,不納入型別方程。 -
對泛型函數
f指定給函數型別(非泛型)變數v的v = f:
typeof(v) ≡<sub>A</sub> typeof(f)。 -
對回傳陳述
return …, f, …,其中f是泛型函數作為結果回傳給函數型別(非泛型)結果變數r:
typeof(r) ≡<sub>A</sub> typeof(f)。
此外,每個型別參數 P<sub>k</sub> 和對應約束 C<sub>k</sub> 生成型別方程 P<sub>k</sub> ≡<sub>C</sub> C<sub>k</sub>。
型別推斷優先處理從型別操作數獲得的資訊,再考慮無型別常數。因此推斷分兩個階段:
-
使用型別統一求解型別方程對綁定型別參數的值。如果統一失敗,型別推斷失敗。
-
對每個尚未推斷型別實參且收集到一個或多個對
(c<sub>j</sub>, P<sub>k</sub>)的綁定型別參數P<sub>k</sub>,確定所有對中常數c<sub>j</sub>的常數種類(方法與常數表達式相同)。P<sub>k</sub>的型別實參是確定常數種類的預設型別。如果因衝突常數種類無法確定常數種類,型別推斷失敗。
如果兩階段後未找到所有型別實參,型別推斷失敗。
如果兩階段成功,型別推斷為每個綁定型別參數確定型別實參:
型別實參 A<sub>k</sub> 可以是複合型別,包含其他綁定型別參數 P<sub>k</sub> 作為元素型別(或僅為另一個綁定型別參數)。在重複簡化過程中,每個型別實參中的綁定型別參數替換為對應型別實參,直到每個型別實參不含綁定型別參數。
如果型別實參透過綁定型別參數包含對自身的循環引用,簡化及型別推斷失敗。否則,型別推斷成功。#### 型別統一
型別推斷透過_型別統一_求解型別方程。型別統一遞迴比較方程的左右型別(任一或兩者可能是或包含綁定型別參數),尋找這些型別參數的實參,使左右型別匹配(根據上下文完全相同或可指定兼容)。為此,型別推斷維護綁定型別參數到推斷型別實參的對映;該對映在型別統一過程中查詢和更新。初始時,綁定型別參數已知但對映為空。型別統一過程中,如果推斷新型別實參 A,則將型別參數到實參的對映 P ➞ A 新增到對映。相反,比較型別時,已知型別實參(已有對映條目的型別實參)取代對應型別參數。隨著型別推斷進行,對映逐漸填滿,直到考慮所有方程或統一失敗。如果無統一步驟失敗且對映有每個型別參數的條目,則型別推斷成功。
例如,給定含綁定型別參數 P 的型別方程
型別推斷從空對映開始。統一首先比較左右型別的頂層結構。兩者都是相同長度的陣列;如果元素型別統一,則它們統一。兩個元素型別都是結構體;如果欄位數量、名稱相同且欄位型別統一,則它們統一。P 的型別實參尚不知(無對映條目),因此統一 P 和 string 將對映 P ➞ string 新增到對映。統一 list 欄位型別需要統一 []P 和 []string,從而統一 P 和 string。由於此時 P 的型別實參已知(有 P 的對映條目),其型別實參 string 取代 P。由於 string 與 string 相同,此統一步驟也成功。方程左右型別的統一現在完成。由於只有一個型別方程,無統一步驟失敗且對映完全填滿,型別推斷成功。
統一根據兩種型別必須相同、可指定兼容或僅結構相等,組合使用_精確_和_放鬆_統一。詳細的型別統一規則在附錄中說明。
對於形如 X ≡<sub>A</sub> Y 的方程(其中 X 和 Y 是涉及指定(包括參數傳遞和回傳陳述)的型別),頂層型別結構可放鬆統一,但元素型別必須精確統一,匹配指定規則。
對於形如 P ≡<sub>C</sub> C 的方程(其中 P 是型別參數,C 是其對應約束),統一規則更複雜:
- 如果
C的型別集合中所有型別有相同潛在型別U,且P有已知型別實參A,則U和A必須放鬆統一。 - 類似地,如果
C的型別集合中所有型別是相同元素型別且無衝突通道方向的通道型別,且P有已知型別實參A,則C的型別集合中最嚴格的通道型別與A必須放鬆統一。 - 如果
P無已知型別實參且C恰好包含一個不是潛在(波浪號)型別的型別項T,則統一將對映P ➞ T新增到對映。 - 如果
C無上述型別U且P有已知型別實參A,則A必須具有C的所有方法(如果有),且對應方法型別必須精確統一。
求解型別約束的型別方程時,求解一個方程可能推斷額外型別實參,這些實參可能使依賴它們的方程可求解。只要推斷新型別實參,型別推斷就重複型別統一。
運算子
運算子將運算元組合成運算式。
比較運算在其他部分討論。對於其他二元運算子,除非運算涉及移位或無型別常數,否則運算元型別必須相同。僅涉及常數的運算,參見常數表達式部分。
除了移位運算,如果一個運算元是無型別常數而另一個不是,常數將隱式轉換為另一個運算元的型別。
移位運算式中的右運算元必須具有整數型別 [Go 1.13],或為可表示為 uint 型別值的無型別常數。如果非常量移位運算式的左運算元是無型別常數,它將先隱式轉換為移位運算式被其左運算元單獨取代時所採用的型別。
運算子優先級
一元運算子具有最高優先級。由於 ++ 和 -- 運算子形成陳述而非運算式,它們不屬於運算子層次結構。因此,陳述 *p++ 等同於 (*p)++。
二元運算子有五個優先級等級。乘法運算子綁定最強,其次是加法運算子、比較運算子、&&(邏輯與),最後是 ||(邏輯或):
相同優先級的二元運算子從左到右結合。例如,x / y * z 等價於 (x / y) * z。
算術運算子
算術運算子應用於數值並產生與第一個運算元相同型別的結果。四種標準算術運算子(+, -, *, /)適用於整數、浮點數和複數型別;+ 也適用於字串。位元邏輯和移位運算子僅適用於整數。
如果運算元型別是型別參數,運算元必須表示為型別參數實例化時的型別實參值,運算以該型別實參的精度計算。例如,給定函數:
乘積 x * y 和加法 s += x * y 分別以 float32 或 float64 精度計算,取決於 F 的型別實參。
整數運算子
對於兩個整數值 x 和 y,整數商 q = x / y 和餘數 r = x % y 滿足以下關係:
其中 x / y 向零截斷("截斷除法")。
此規則的唯一例外是當被除數 x 是 x 的整數型別的最小負值時,商 q = x / -1 等於 x(且 r = 0),這是由於二進位補碼整數溢出:
如果除數是常數,它必須不為零。如果除數在執行時期為零,發生執行時期恐慌。如果被除數非負且除數是 2 的常數次冪,除法可用右移取代,計算餘數可用位元和操作取代:
移位運算子將左運算元按右運算元指定的移位計數移位,右運算元必須非負。如果移位計數在執行時期為負,發生執行時期恐慌。如果左運算元是帶符號整數,移位運算子實作算術移位;如果是無符號整數,實作邏輯移位。移位計數無上界。移位行為如同左運算元按移位計數 n 次每次移 1 位。因此,x << 1 等價於 x*2,x >> 1 等價於 x/2 但向負無窮截斷。
對於整數運算元,一元運算子 +, -, 和 ^ 定義如下:
整數溢出
對於無符號整數值,運算 +, -, *, 和 << 按模 2n 計算,其中 n 是無符號整數型別的位元寬。通俗地說,這些無符號整數運算在溢出時捨棄高位,程式可依賴"回繞"行為。
對於有符號整數,運算 +, -, *, /, 和 << 可能合法溢出,結果值由有符號整數表示、運算及其操作數確定性定義。溢出不會導致執行時期恐慌。編譯器不能假設溢出不會發生來最佳化程式碼。例如,不能假設 x < x + 1 始終為真。
浮點運算子
對於浮點數和複數,+x 等同於 x,而 -x 是 x 的取負。浮點或複數除以零的結果超出 IEEE 754 標準未指定;是否發生執行時期恐慌取決於實作。
實作可將多個浮點運算合併為單一融合運算(可能跨陳述),產生與單獨執行並捨入指令獲得值不同的結果。明確浮點型別轉換捨入到目標型別精度,防止丟失該捨入的融合。
例如,某些架構提供"融合乘加"(FMA)指令,計算 x*y + z 時不捨入中間結果 x*y。以下範例顯示 Go 實作何時可使用該指令:
字串連接
字串可用 + 運算子或 += 指定運算子連接:
字串加法透過連接操作數建立新字串。
比較運算子
比較運算子比較兩個操作數並產生無類型布林值。
任何比較中,第一個運算元必須可指定給第二個運算元的型別,或反之。
相等運算元 == 和 != 適用於可比較型別的運算元。排序運算元 <, <=, >, 和 <= 適用於有序型別的運算元。這些術語和比較結果定義如下:
- 布林型別可比較。兩個布林值相等當且僅當它們同為
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 比較。指標、通道和介面值與 nil 的比較也允許,遵循上述通用規則。
型別嚴格可比較當且僅當它可比較且不是介面型別也不由介面型別組成。具體:
- 布林、數值、字串、指標和通道型別嚴格可比較。
- 結構體型別嚴格可比較當且僅當所有欄位型別嚴格可比較。
- 陣列型別嚴格可比較當僅當陣列元素型別嚴格可比較。
- 型別參數嚴格可比較當僅當型別集合中所有型別嚴格可比較。
邏輯運算子
邏輯運算子應用於布林值,並產生與運算元相同型別的結果。先求值左運算元,然後根據條件需要再求值右運算元。
地址運算子
對於型別 T 的運算元 x,地址運算 &x 會生成一個指向 x 的型別 *T 的指標。運算元必須是可定址的,也就是變數、指標間接、切片索引運算、可定址結構體的欄位選擇器,或可定址陣列的陣列索引運算。作為定址性的例外,x 也可以是(可能帶括號的)複合字面值。如果 x 的求值會導致執行期恐慌,那麼 &x 的求值也會導致恐慌。
對於指標型別 *T 的運算元 x,指標間接運算 *x 表示 x 指向的型別 T 的變數。如果 x 為 nil,嘗試求值 *x 將導致執行期恐慌。
接收運算子
對於通道型別的運算元 ch,接收運算 <-ch 的值是從通道 ch 接收的值。通道方向必須允許接收操作,接收運算的型別是通道的元素型別。運算式會阻塞,直到有值可用為止。從 nil 通道接收會永遠阻塞。從關閉的通道接收,則總是能立即進行,並在接收所有先前傳送的值後,產生元素型別的零值。
如果運算元型別是型別參數,其型別集合中所有型別都必須是允許接收操作的通道型別,且必須具有相同元素型別(也就是接收運算的型別)。
在賦值敘述或特殊形式初始化中使用的接收運算式
會產生額外的無型別布林值結果,回報通訊是否成功。如果接收的值成功傳送到通道,ok 為 true;如果通道已關閉且為空,產生零值,則為 false。
轉換
轉換會將運算式的型別更改為轉換所指定的型別。轉換可能在原始碼中以字面出現,也可能由運算式所出現的上下文隱含。
顯式轉換是形如 T(x) 的運算式,其中 T 是型別,x 是可被轉換為型別 T 的運算式。
如果型別以運算子 * 或 <- 開頭,或型別以關鍵字 func 開頭且沒有結果列表,為避免歧義,必要時必須加上括號:
常數值 x 可轉換為型別 T,若 x 可表示為 T 的值。特殊情況是,整數常數 x 可顯式轉換為字串型別,使用與非常數 x 相同規則。
常數轉換為非型別參數型別,會產生具型別的常數。
常數轉換為型別參數會產生該型別的非常數值,值表示為型別參數實例化時的型別引數值。例如,給定函式:
轉換 P(1.1) 會產生型別 P 的非常數值,值 1.1 依 f 的型別引數,表示為 float32 或 float64。因此,若 f 以 float32 型別實例化,運算式 P(1.1) + 1.2 的數值將以對應非常數 float32 加法的相同精度計算。
非常數值 x 可在以下任一情況下轉換為型別 T:
x可賦值給T。- 忽略結構體標籤(見下文),
x的型別與T皆非型別參數,且擁有相同的底層型別。 - 忽略結構體標籤(見下文),
x的型別與T都是非命名型別的指標型別,且其指標基底型別皆非型別參數,但擁有相同底層型別。 x的型別與T都是整數或浮點型別。x的型別與T都是複數型別。x是整數或位元組或符文切片,T是字串型別。x是字串,T是位元組或符文切片。x是切片,T是陣列 [Go 1.20] 或指向陣列的指標 [Go 1.17],且切片與陣列型別擁有相同的元素型別。
此外,若 T 或 x 的型別 V 是型別參數,x 也可在以下任一條件下轉換為型別 T:
V與T都是型別參數,且V型別集合中每種型別的值可轉換為T型別集合中每種型別。- 只有
V是型別參數,且V型別集合中每種型別的值可轉換為T。 - 只有
T是型別參數,且x可轉換為T型別集合中每種型別。
結構體標籤在比較結構體型別以進行轉換時忽略:
特定規則適用於數值型別之間或與字串型別之間的(非常數)轉換。這些轉換可能更改 x 的表示,並產生執行期開銷。所有其他轉換僅更改型別,不更改 x 的表示。
語言本身未提供指標與整數之間的轉換機制。套件 unsafe 在受限情況下實現此功能。
數值型別之間的轉換
對於非常數數值值的轉換,適用以下規則:
- 在整數型別之間轉換時,若值是有符號整數,則符號擴展至隱式無限精度;否則零擴展。再截斷以符合結果型別的大小。例如,若
v := uint16(0x10F0),則uint32(int8(v)) == 0xFFFFFFF0。轉換總是產生有效值,無溢出指示。 - 將浮點數轉換為整數時,小數部分捨去(向零截斷)。
- 將整數或浮點數轉換為浮點型別,或將複數轉換為另一複數型別時,結果值會依目標型別指定的精度捨入。例如,型別
float32的變數x的值可能以超過 IEEE 754 32 位數的額外精度儲存,但float32(x)表示將x的值捨入至 32 位精度的結果。類似地,x + 0.1可能使用超過 32 位精度,但float32(x + 0.1)不會使用。
在所有涉及浮點或複數值的非常數轉換中,若結果型別無法表示該值,轉換成功,但結果值實作相依。
與字串型別之間的轉換
-
將位元組切片轉換為字串型別會產生字串,其連續位元組就是切片的元素。
-
將符文切片轉換為字串型別會產生字串,即各個符文值轉換為字串後的連接。
-
將字串型別的值轉換為位元組切片型別會產生非
nil的切片,其連續元素就是字串的位元組。結果切片的容量實作相依,可能大於切片長度。 -
將字串型別的值轉換為符文切片型別會產生包含字串各個 Unicode 代碼點的切片。結果切片的容量實作相依,可能大於切片長度。
-
最後,因歷史因素,整數值可轉換為字串型別。這種轉換會產生包含給定整數值之(可能多位元組)UTF-8 表示的字串。超出有效 Unicode 代碼點範圍的值會轉換為
"\uFFFD"。注意:這種形式的轉換可能最終會被語言移除。
go vet工具會將某些整數到字串的轉換標記為潛在錯誤。應改用函式如utf8.AppendRune或utf8.EncodeRune。
切片到陣列或陣列指標的轉換
將切片轉換為陣列會產生包含切片底層陣列元素的陣列。類似地,將切片轉換為陣列指標會產生指向切片底層陣列的指標。兩種情況下,若切片的長度小於陣列長度,會發生執行期恐慌。
常數運算式
常數運算式只能包含常數運算元,並在編譯時期求值。
無型別布林、數值和字串常數可在任何可使用布林、數值或字串型別運算元的地方作為運算元。
常數比較一定會產生無型別布林常數。若常數移位運算式的左運算元是無型別常數,結果是無型別整數常數;否則結果是與左運算元同型別的常數,左運算元必須是整數型別。
對無型別常數進行的任何其他運算,會產生同種類的無型別常數:布林、整數、浮點、複數或字串常數。若二元運算(不含移位)的無型別運算元種類不同,結果取列表中後出現的運算元種類:整數、符文、浮點、複數。例如,無型別整數常數除以無型別複數常數,會產生無型別複數常數。
對無型別整數、符文或浮點常數套用內建函式 complex,會產生無型別複數常數。
常數運算式總是精確求值;中間值與常數本身可能需要比語言任何預設型別支援的精度更寬廣。以下是合法的宣告:
常數除法或取餘運算的除數不能為零:
型別化常數的值必須總是能由常數型別的值精確表示。下列常數運算式非法:
無型別常數套用一元位元補碼運算子 ^ 的遮罩符合非常數規則:無符號常數遮罩全為 1,有符號和無型別常數遮罩為 -1。
實作限制:編譯器在計算無型別浮點或複數常數運算式時可能採用捨入;參見常數章節的實作限制。這種捨入可能導致浮點常數運算式在整數上下文中無效,即使無限精度計算時為整數值,反之亦然。
求值順序
在套件層級,初始化相依性決定變數宣告中各個初始化運算式的求值順序。否則,在求值運算式、賦值或回傳敘述的運算元時,所有函式呼叫、方法呼叫、接收操作和二元邏輯運算皆按詞法從左到右順序求值。
例如,在(函式局部)賦值
函式呼叫與通訊依序發生:f(), h()(若 z 求值為假), i(), j(), <-c, g(), 和 k()。但這些事件與 x 的求值及索引、y 和 z 的求值相比,其順序未指定,僅限於詞法要求。例如,g 的參數求值完畢前不能呼叫 g。
在套件層級,初始化相依性會覆蓋各個初始化運算式的從左到右規則,但不會覆蓋每個運算式內部操作數的順序:
函式呼叫順序為:u(), sqr(), v(), f(), v(), 和 g()。
運算式內的浮點運算根據運算子結合性求值。顯式括號會透過覆蓋預設結合性來影響求值。在運算式 x + (y + z) 中,加法 y + z 會在加 x 之前執行。
敘述
敘述控制執行流程。
終止敘述
終止敘述 會中斷區塊中的常規控制流程。下列敘述是終止敘述:
- "return" 或 "goto" 敘述。
- 對內建函式
panic的呼叫。 - 以終止敘述結尾的區塊。
- "if" 敘述,其中:
- 存在 "else" 分支,且
- 兩個分支都是終止敘述。
- "for" 敘述,其中:
- 沒有引用該 "for" 敘述的 "break" 敘述,且
- 迴圈條件不存在,且
- "for" 敘述不使用 range 子句。
- "switch" 敘述,其中:
- 沒有引用該 "switch" 敘述的 "break" 敘述,
- 有預設情況,且
- 每個情況(包括預設情況)的敘述列表以終止敘述結尾,或以帶標籤的"fallthrough" 敘述結尾。
- "select" 敘述,其中:
- 沒有引用該 "select" 敘述的 "break" 敘述,且
- 每個情況(包括存在的預設情況)的敘述列表以終止敘述結尾。
- 標記為終止敘述的標記敘述。
其他敘述皆非終止敘述。
若敘述列表非空,且其最終非空敘述是終止敘述,則稱此敘述列表以終止敘述結尾。
空敘述
空敘述不執行任何操作。
標記敘述
標記敘述可作為 goto、break 或 continue 敘述的目標。
運算式敘述
除特定內建函式外,函式與方法呼叫及接收操作可出現在敘述上下文中。此類敘述可加括號。
下列內建函式不允許出現在敘述上下文中:
發送敘述
發送敘述在通道上傳送值。通道運算元必須是通道型別,通道方向必須允許發送操作,且待傳送的值之型別必須可賦值給通道的元素型別。
通訊開始前,會先求值通道與值運算元。通訊會阻塞,直到發送可以繼續。無緩衝通道的發送可以繼續,若有接收者就緒;緩衝通道的發送可以繼續,若緩衝區有空間。對已關閉的通道發送會導致執行期恐慌。對 nil 通道發送會永遠阻塞。
若通道運算元的型別是型別參數,其型別集合中所有型別都必須是允許發送操作的通道型別,必須具有相同元素型別,且待傳送的值之型別必須可賦值給該元素型別。
自增自減敘述
"++" 和 "--" 敘述會將其運算元遞增或遞減無型別常數 1。與賦值類似,運算元必須可定址,或為對應索引運算式。
下列賦值敘述在語意上相等:
賦值敘述
賦值 會用以運算式指定的新值,替換變數中儲存的當前值。賦值敘述可將單個值賦給單個變數,或將多個值賦給匹配數量的變數。
每個左側運算元必須可定址、為對應索引運算式,或(僅用於 = 賦值)空識別字。運算元可加括號。
賦值運算 x op= y(其中 op 是二元算術運算子)等價於 x = x op (y),但 x 只要求值一次。op= 結構是單個標記。在賦值運算中,左右兩側運算式列表都必須恰好包含一個單值運算式,且左側運算式不能是空識別字。
元組賦值會將多值操作的各個元素賦給變數列表。有兩種形式。第一種形式中,右側運算元是單個多值運算式,如函式呼叫、通道或對應操作,或型別斷言。左側運算元數量必須匹配值的數量。例如,若 f 是返回兩個值的函式,
會將第一個值賦給 x,第二個值賦給 y。第二種形式中,左側運算元數量必須等於右側運算式數量,每個運算式都必須是單值的,第 n 個右側運算式賦給左側第 n 個運算元:
空識別字可用於忽略賦值右側的值:
賦值分兩階段進行。第一階段,先按常規順序求值左側的索引運算式與指標間接(包括選擇子中的隱式指標間接),以及右側所有運算式。第二階段,再按從左到右順序執行賦值。
賦值時,每個值必須可賦值給賦值的運算元型別,有以下特殊情況:
將值賦給變數時,僅替換變數中儲存的資料。若值包含參照,賦值僅複製參照,不複製參照的資料(如切片的底層陣列)。
If 敘述
"If" 敘述根據布林運算式的值,指定兩個分支的條件執行。若運算式求值為真,則執行 "if" 分支;否則執行存在的 "else" 分支。
運算式前可有簡單敘述,會在運算式求值前執行。
Switch 敘述
"Switch" 敘述提供多路執行。將運算式或型別與 "switch" 內的 "cases" 比較,以決定執行哪個分支。
有兩種形式:運算式 switch 與 型別 switch。在運算式 switch 中,cases 包含與 switch 運算式的值比較的運算式。在型別 switch 中,cases 包含與特殊標記的 switch 運算式的型別比較的型別。switch 運算式在 switch 敘述中只求值一次。
運算式 switch
在運算式 switch 中,switch 運算式求值後,case 運算式(不必是常數)會從左到右、從上到下求值;第一個等於 switch 運算式的 case 會觸發關聯 case 敘述的執行;其他 case 會被跳過。若無 case 匹配且有 "default" case,則執行其敘述。最多只有一個 default case,且可出現在 "switch" 敘述的任何位置。缺失的 switch 運算式等價於布林值 true。
若 switch 運算式求值為無型別常數,會先隱式轉換為預設型別。預宣告的無型別值 nil 不能用作 switch 運算式。switch 運算式的型別必須可比較。
若 case 運算式是無型別的,會先隱式轉換為 switch 運算式的型別。對於每個(可能轉換的)case 運算式 x 與 switch 運算式的值 t,必須滿足 x == t 是有效的比較。
換句話說,switch 運算式被視為宣告並初始化一個無明確型別的臨時變數 t;將 t 的值與每個 case 運算式 x 進行相等測試。
在 case 或 default 子句中,最後一個非空敘述可以是(可能標記)"fallthrough" 敘述,表示控制應從此子句末尾流向下一個子句的第一條敘述。否則控制流到 "switch" 敘述的末尾。"fallthrough" 敘述可出現在運算式 switch 除最後一個子句外的任何子句的最後敘述。
switch 運算式前可有簡單敘述,會在運算式求值前執行。
實作限制:編譯器可能禁止多個 case 運算式求值為同一常數。例如,現行編譯器禁止 case 運算式中出現重複的整數、浮點或字串常數。
型別 switch
型別 switch 比較型別而非值。其他方面類似運算式 switch。它使用特殊 switch 運算式標記,該標記具有型別斷言的形式,但用關鍵字 type 取代實際型別:
cases 會將實際型別 T 與運算式 x 的動態型別比較。與型別斷言類似,x 必須是介面型別,但不能是型別參數,且每個 case 中列出的非介面型別 T 必須實作 x 的型別。type switch 中 case 列出的型別必須全部不同。
TypeSwitchGuard 可包含短變數宣告。使用此形式時,變數會在 TypeSwitchCase 的每個子句的隱式區塊末尾宣告。在恰好列出一個型別的 case 子句中,變數具有該型別;否則變數具有 TypeSwitchGuard 中運算式的型別。
case 可用預宣告識別字 nil 取代型別;當 TypeSwitchGuard 中的運算式為 nil 介面值時,選擇該 case。最多只有一個 nil case。
給定型別為 interface{} 的運算式 x,下列型別 switch:
可改寫為:
型別參數或泛型型別可用作 case 中的型別。若實例化後該型別與其他 switch 項目重複,則選擇第一個匹配的 case。
TypeSwitchGuard 前可有簡單敘述,會在 guard 求值前執行。
型別 switch 中不允許 "fallthrough" 敘述。
For 敘述
"For" 敘述指定區塊的重複執行。有三種形式:迭代可由單個條件、"for" 子句或 "range" 子句控制。
單條件 For 敘述
最簡單的形式中,"for" 敘述指定區塊重複執行,只要布林條件求值為真。每次迭代前求值條件。若條件缺失,等價於布林值 true。
帶 ForClause 的 For 敘述
帶 ForClause 的 "for" 敘述也由其條件控制,但額外指定 init 與 post 敘述,如賦值、自增或自減敘述。init 敘述可以是短變數宣告,但 post 敘述不能。
若非空,init 敘述執行一次,在首次迭代前求值條件;post 敘述執行於每次區塊執行後(且僅在區塊執行時)。ForClause 的任何元素可為空,但分號要求除非僅有條件。若條件缺失,等價於布林值 true。
每個迭代有獨立的宣告變數(或變數) [Go 1.22]。首次迭代使用的變數由 init 敘述宣告。後續迭代使用的變數,在執行 post 敘述前會隱式宣告並初始化為前次迭代變數的值。
輸出
在 [Go 1.22] 之前,迭代共享一組變數而非獨立變數。此時上述範例輸出
帶 range 子句的 For 敘述
帶 "range" 子句的 "for" 敘述會遍歷陣列、切片、字串或對應的所有項目、通道上接收的值、從零到上限的整數值 [Go 1.22],或傳遞給迭代器函式的 yield 函式的值 [Go 1.23]。對每個項目,若存在,會將迭代值賦給相應的迭代變數,然後執行區塊。
"range" 子句右側的運算式稱為 range 運算式,可以是陣列、指向陣列的指標、切片、字串、允許接收操作的通道、整數,或具有特定簽名的函式(見下文)。與賦值類似,若左側存在運算元,必須是可定址或對應索引運算式;它們代表迭代變數。若 range 運算式是函式,迭代變數的最大數量取決於函式簽名。若 range 運算式是通道或整數,最多允許一個迭代變數;否則最多允許兩個。若最後一個迭代變數是空識別字,range 子句等價於不包含該識別字的相同子句。
Range 運算式 x 在循環開始前求值,有一個例外:若最多只有一個迭代變數存在,且 x 或 len(x) 是常數,則不求值 range 運算式。
每次迭代時,若存在相應的迭代變數,按以下方式產生迭代值:
- 對於陣列、指向陣列的指標或切片值
a,索引迭代值按遞增順序,從元素索引 0 開始產生。若最多只有一個迭代變數存在,range 循環從 0 到len(a)-1產生迭代值,且不對陣列或切片本身進行索引。對於nil切片,迭代次數為 0。 - 對於字串值,"range" 子句從位元組索引 0 開始迭代字串中的 Unicode 代碼點。連續迭代中,索引值將是字串中連續 UTF-8 編碼代碼點的第一個位元組的索引,第二個值(型別為
rune)將是相應代碼點的值。若迭代遇到無效 UTF-8 序列,第二個值將是0xFFFD(Unicode 替代字元),下一次迭代將在字串中前進一個位元組。 - 對於對應,迭代順序未指定且不保證連續迭代相同。若迭代中移除尚未到達的對應項目,不會產生對應的迭代值。若迭代中建立對應項目,該項目可能在迭代中產生或跳過。選擇可能因建立的每個項目及連續迭代而異。若對應為
nil,迭代次數為 0。 - 對於通道,迭代值是通道上發送的連續值,直到通道關閉。若通道為
nil,range 運算式永遠阻塞。 - 對於整數值
n(其中n是整數型別或無型別整數常數),按遞增順序產生 0 到n-1的迭代值。若n是整數型別,迭代值具有相同型別。否則,n的型別按賦值給迭代變數決定。具體地:若迭代變數已存在,迭代值的型別是迭代變數的型別(必須是整數型別);否則,若迭代變數由 "range" 子句宣告或不存在,迭代值的型別是n的預設型別。若n≤ 0,循環不執行任何迭代。 - 對於函式
f,迭代透過將f與新的合成yield函式作為引數呼叫進行。若yield在f返回前被呼叫,yield的引數成為執行循環體一次的迭代值。每次循環迭代後,yield返回true並可再次呼叫以繼續循環。只要循環體不終止,"range" 子句將持續為每次yield呼叫產生迭代值,直到f返回。若循環體終止(如透過break敘述),yield返回false且不得再次呼叫。
迭代變數可用短變數宣告的形式由 "range" 子句宣告(:=)。此時它們的作用域是 "for" 敘述的區塊,且每個迭代有獨立的新變數 [Go 1.22](見 "for" 敘述帶 ForClause 部分)。變數具有相應迭代值的型別。
若迭代變數未由 "range" 子句明確宣告,則必須已存在。此時迭代值按賦值敘述的方式賦給相應變數。
若 range 運算式的型別是型別參數,其型別集合中所有型別必須具有相同底層型別,且 range 運算式必須對該型別有效;或者,若型別集合包含通道型別,則必須只包含具有相同元素型別的通道型別,且所有通道型別都必須允許接收操作。
Go 敘述
"Go" 敘述會在同一地址空間中,作為獨立並行執行緒(或稱 goroutine)啟動函式呼叫的執行。
運算式必須是函式或方法呼叫;不能加括號。內建函式呼叫受運算式敘述的限制。
函式值與參數在呼叫的 goroutine 中按常規方式求值,但與常規呼叫不同,程式執行不會等待被呼叫的函式完成。相反,函式會在新的 goroutine 中獨立執行。函式終止時,其 goroutine 也隨之終止。若函式有回傳值,函式完成時回傳值會被丟棄。
Select 敘述
"select" 敘述會從一組可能的發送或接收操作中,選擇將進行的操作。它類似"switch"敘述,但所有 case 都引用通訊操作。
帶有 RecvStmt 的 case,可將 RecvExpr 的結果賦給一個或兩個變數,可用短變數宣告宣告。RecvExpr 必須是(可能加括號)接收操作。最多只有一個 default case,可出現在 case 列表的任何位置。
"select" 敘述的執行按以下步驟進行:
- 對於敘述中的所有 case,接收操作的通道運算元與發送敘述的通道及右側運算式,會按原始碼順序只求值一次,在 "select" 敘述進入時。結果是接收或發送的通道集合及對應的發送值。求值中的任何副作用都會發生,不論選擇哪種通訊操作進行。帶短變數宣告或賦值的 RecvStmt 左側運算式尚未求值。
- 若有一個或多個通訊操作可進行,則透過均勻偽隨機選擇,選擇其中可進行的一個。否則若有 default case,則選擇 default case。若無 default case,"select" 敘述會阻塞,直到至少一個通訊操作可進行。
- 除非選擇的 case 是 default case,否則執行相應的通訊操作。
- 若選擇的 case 是帶短變數宣告或賦值的 RecvStmt,則求值左側運算式並賦接收值(或多個值)。
- 執行選擇的 case 的敘述列表。
由於 nil 通道上的通訊永遠無法進行,只有 nil 通道且無 default case 的 select 會永遠阻塞。
回傳敘述
函式 F 中的 "return" 敘述會終止 F 的執行,並可選擇性提供多個結果值。任何由 F 延遲的函式,會在 F 返回給呼叫者前執行。
在無結果型別的函式中,"return" 敘述不得指定結果值。
有結果型別的函式返回值有三種方式:
-
返回值可在 "return" 敘述中明確列出。每個運算式必須是單值且可賦值給函式結果型別的對應元素。
-
"return" 敘述中的運算式列表可以是多值函式的單次呼叫。效果如同該函式返回的每個值都賦給具有相應值的臨時變數,然後 "return" 敘述列出這些變數,此時適用前一情況的規則。
-
若函式的結果型別指定了結果參數的名稱,則運算式列表可為空。結果參數充當普通局部變數,函式可按需賦給它們值。"return" 敘述返回這些變數的值。
不論宣告方式如何,所有結果值在函式入口時都初始化為型別的零值。指定結果的 "return" 敘述會在延遲函式執行前設定結果參數。
實作限制:若結果參數同名且不同實體(常數、型別或變數)在 return 位置作用域內,則編譯器可能禁止 "return" 敘述中的空運算式列表。
Break 敘述
"break" 敘述會終止同一函式內最內層"for"、"switch"或"select"敘述的執行。
如果有標籤,必須是包含的 "for"、"switch" 或 select 敘述的標籤,並終止該敘述的執行。
Continue 敘述
"continue" 敘述會透過將控制推進到循環區塊末尾,開始最內層包含"for" 循環的下一次迭代。"for" 循環必須在同一函式內。
如果有標籤,必須是包含的 "for" 敘述的標籤,且推進該敘述的執行。
Goto 敘述
"goto" 敘述會在同一函式內將控制轉移到對應標籤的敘述。
執行 "goto" 敘述不得導致任何變數進入作用域,而這些變數在 goto 位置尚未在作用域內。例如,以下範例:
錯誤,因為跳轉到標籤 L 跳過了 v 的建立。
"goto" 敘述在區塊外不得跳轉到該區塊內的標籤。例如,以下範例:
錯誤,因為標籤 L1 在 "for" 敘述的區塊內,但 "goto" 在區塊外。
Fallthrough 敘述
"fallthrough" 敘述會將控制轉移到運算式 "switch" 敘述的下一個 case 子句的第一條敘述。只能作為此類子句的最後一個非空敘述使用。
Defer 敘述
"defer" 敘述會呼叫函式,其執行延遲到周圍函式返回時,無論周圍函式透過執行回傳敘述、到達函式體末尾,或因對應 goroutine panicking而返回。
運算式必須是函式或方法呼叫;不能加括號。內建函式呼叫受運算式敘述的限制。
每次執行 "defer" 敘述時,函式值與呼叫參數會按常規方式求值並儲存,但不會立即呼叫函式。相反,延遲函式會在周圍函式返回前立即呼叫,按延遲順序的逆序呼叫。也就是說,如果周圍函式透過明確回傳敘述返回,則延遲函式會在結果參數由回傳敘述設定後,但在函式返回給呼叫者前執行。如果延遲函式求值為 nil,函式呼叫時會執行panic,而非 "defer" 敘述執行時。
例如,如果延遲函式是函式字面量且周圍函式有命名結果參數在字面量作用域內,延遲函式可在結果參數返回前存取與修改結果參數。如果延遲函式有回傳值,函式完成時回傳值會被丟棄。(另見處理 panic部分。)
內建函式
內建函式預宣告。它們像其他函式一樣呼叫,但其中一些函式接受型別而非運算式作為第一個引數。
內建函式沒有標準 Go 型別,因此只能出現在呼叫運算式中;不能用作函式值。
切片附加與複製
內建函式 append 與 copy 輔助切片常用操作。對於這兩個函式,結果與引數引用的記憶體是否重疊無關。
可變引數函式 append 會將零個或多個值 x 附加到型別 S 的切片 s,並返回結果切片(也是型別 S)。值 x 會傳遞給型別 ...E 的引數,其中 E 是 S 的元素型別,適用相應的引數傳遞規則。作為特殊情況,append 也接受可賦值給 []byte 型別的第一個引數,以及字串型別的第二個引數後跟 ...。這種形式附加字串的位元組。
若 S 是型別參數,其型別集合中所有型別必須具有相同底層切片型別 []E。
若 s 的容量不足以容納附加值,append 會分配新的底層陣列,其大小足夠容納現有切片元素與附加值。否則 append 重用底層陣列。
函式 copy 會將來源切片 src 的元素複製到目標切片 dst,並返回複製元素的數量。兩個引數必須具有相同元素型別 E 且必須可賦值給型別 []E 的切片。複製的元素數量是 len(src) 和 len(dst) 的最小值。作為特殊情況,copy 也接受可賦值給 []byte 型別的目標引數和字串型別的來源引數。這種形式會將字串的位元組複製到位元組切片。
如果一個或兩個引數的型別是型別參數,其相應型別集合中所有型別必須具有相同底層切片型別 []E。
範例:
清除
內建函式 clear 接受對應、切片或型別參數型別的引數,並刪除或清零所有元素 [Go 1.21]。
若 clear 的引數型別是型別參數,其型別集合中所有型別都必須是對應或切片,且 clear 執行與實際型別引數對應的操作。
若對應或切片為 nil,clear 是無操作。
關閉
對於通道 ch,內建函式 close(ch) 會記錄通道上不再發送值。若 ch 是只接收通道,則錯誤。向關閉的通道發送或關閉 nil 通道也會導致執行期恐慌。呼叫 close 後,任何先前傳送的值接收後,接收操作將返回通道型別的零值而不阻塞。多值接收操作返回接收值及通道是否關閉的指示。
若 close 的引數型別是型別參數,其型別集合中所有型別都必須是具有相同元素型別的通道。若這些通道中有只接收通道,則錯誤。
複數操作
三個函式用於構造與分解複數。內建函式 complex 從浮點實部與虛部構造複數,而 real 和 imag 提取複數的實部與虛部。
引數與回傳值的型別對應。對於 complex,兩個引數必須是相同浮點型別,回傳值是相應複數型別:float32 引數回傳 complex64,float64 引數回傳 complex128。若引數求值為無型別常數,則先隱式轉換為另一個引數的型別。若兩個引數都求值為無型別常數,它們必須是非複數或其虛部為零,函式回傳無型別複數常數。
對於 real 和 imag,引數必須是複數型別,回傳值是相應浮點型別:complex64 引數回傳 float32,complex128 引數回傳 float64。若引數求值為無型別常數,它必須是複數,函式回傳無型別浮點常數。
real 和 imag 函式共同構成 complex 的逆,對於複數型別 Z 的值 z,z == Z(complex(real(z), imag(z)))。
若這些函式的所有操作數都是常數,回傳值是常數。
型別參數型別的引數不允許。
對應元素刪除
內建函式 delete 從對應 m 中刪除鍵為 k 的元素。值 k 必須可賦值給 m 的鍵型別。
若 m 的型別是型別參數,其型別集合中所有型別都必須是對應,且它們必須具有相同鍵型別。
若對應 m 為 nil 或元素 m[k] 不存在,delete 是無操作。
長度與容量
內建函式 len 和 cap 接受各種型別的引數並回傳 int 型別的結果。實作保證結果永遠適合 int。
若引數型別是型別參數 P,則呼叫 len(e)(或 cap(e))對 P 型別集合中的每種型別都必須有效。結果是 P 被實例化時對應型別引數的引數的長度(或容量)。
切片的容量是底層陣列中已分配空間可容納的元素數量。任何時候都滿足:
nil 切片、對應或通道的長度為 0。nil 切片或通道的容量為 0。
若 s 是字串常數,運算式 len(s) 是常數。若 s 的型別是陣列或指向陣列的指標,且運算式 s 不包含通道接收操作或(非常數)函式呼叫,那麼運算式 len(s) 和 cap(s) 也是常數;在這種情況下,s 不會被求值。否則,呼叫 len 和 cap 不是常數,且 s 會被求值。
建立切片、對應與通道
內建函式 make 接受一個型別 T,該型別必須是切片、對應或通道型別,或者是一個型別參數,後面可選擇性地跟隨特定型別的運算式列表。它會回傳型別為 T 的值(不是 *T)。記憶體的初始化在初始值部分有描述。
若第一個引數是型別參數,其型別集合的所有型別都必須具有相同的基礎型別,該基礎型別必須是切片或對應型別,或者,若是通道型別,則必須是相同元素型別的通道型別,且通道方向不能衝突。
每個大小引數 n 和 m 都必須是整數型別,其型別集只包含整數型別,或者是未型別的常數。常數大小引數必須是負數,且必須可由型別 int 的值表示;若是未型別的常數,則其型別會被指定為 int。若 n 和 m 都提供且為常數,則 n 必須不大於 m。對於切片和通道,若 n 在執行期是負數或大於 m,則會發生執行期恐慌。
使用對應型別與大小提示 n 呼叫 make,會建立初始空間可容納 n 個對應元素的對應。具體行為相依於實作。
最小值與最大值
內建函式 min 和 max 計算一組固定數量有序型別引數的最小值(或最大值)。必須至少有一個引數 [Go 1.21]。
與運算子相同的型別規則適用:對於有序引數 x 和 y,若 x + y 有效,則 min(x, y) 有效,且 min(x, y) 的型別與 x + y 的型別相同(對於 max 也類似)。若所有引數都是常數,則結果為常數。
對於數值引數,假設所有 NaN 相等,min 和 max 是可交換與可結合的:
對於浮點引數負零、NaN 和無窮大,適用以下規則:
對於字串引數,min 的結果是字典序最小(或 max 最大)的第一個引數:
分配
內建函式 new 接受一個型別 T,在執行期為該型別的變數分配儲存空間,並回傳一個指向該變數的型別為 *T 的指標值。變數按初始值部分描述進行初始化。
例如
為型別 S 的變數分配儲存空間,將其初始化(a=0,b=0.0),並回傳一個包含該位置地址的 *S 型別的值。
處理恐慌
兩個內建函式 panic 和 recover 協助報告與處理執行期恐慌和程式定義的錯誤條件。
在執行函式 F 時,明確呼叫 panic 或執行期恐慌會終止 F 的執行。然後,F 延遲執行的所有函式會照常執行。接下來,F 的呼叫者延遲執行的所有函式會被執行,依此類推,直到執行 goroutine 中的頂層函式的延遲函式。此時,程式終止,並報告錯誤條件,包括傳遞給 panic 的引數值。這個終止序列稱為 panicking。
recover 函式允許程式管理 panic 的 goroutine 的行為。假設函式 G 延遲執行一個呼叫 recover 的函式 D,並且在與 G 執行的相同 goroutine 中的某個函式中發生 panic。當延遲函式的執行到達 D 時,D 的 recover 呼叫的回傳值將是傳遞給 panic 呼叫的值。若 D 正常返回,沒有啟動新的 panic,則 panicking 序列停止。在這種情況下,丟棄 G 和 panic 呼叫之間的函式狀態,恢復正常執行。然後執行 G 在 D 之前延遲的所有函式,G 的執行透過返回給其呼叫者而終止。
若 goroutine 沒有發生 panic,或是 recover 不是由延遲函式直接呼叫,則 recover 的回傳值為 nil。相反,若 goroutine 正在 panic,且 recover 是由延遲函式直接呼叫的,則 recover 的回傳值保證不為 nil。為了確保這一點,使用 nil 介面值(或未型別的 nil)呼叫 panic 會導致執行期恐慌。
以下範例中的 protect 函式呼叫函式引數 g,並保護呼叫者免受由 g 引起的執行期恐慌。
啟動
現行實作提供了幾個在啟動期間有用的內建函式。這些函式為了完整性而記錄,但不保證會保留在語言中。它們不回傳結果。
實作限制:print 和 println 不需要接受任意引數型別,但必須支援布林、數值和字串型別的列印。
套件
Go 程式透過連接多個套件來建構。一個套件由多個原始檔組成,這些原始檔共同宣告了屬於該套件的常數、型別、變數和函式,且這些元素在同一個套件的所有檔案中都是可存取的。這些元素可以被匯出並在其他套件中使用。
原始檔組織
每個原始檔由定義其所屬套件的套件子句組成,後跟一組可能為空的匯入宣告,宣告希望使用的套件的內容,然後是一組可能為空的函式、型別、變數和常數的宣告。
套件子句
每個原始檔以套件子句開始,定義該檔案所屬的套件。
PackageName 不能是空識別字。
共用相同 PackageName 的一組檔案形成套件的實作。實作可能要求套件的所有原始檔都位於同一目錄中。
匯入宣告
匯入宣告指出包含該宣告的原始檔相依於匯入套件的功能(§程式初始化與執行),並啟用對該套件的匯出識別字的存取。匯入宣告了一個用於存取的識別字(PackageName)與一個指定要匯入套件的 ImportPath。
PackageName 在限定識別字中用於在匯入原始檔中存取套件的匯出識別字。它在檔案區塊中宣告。若省略 PackageName,則預設使用匯入套件的套件子句中指定的識別字。若顯式句點(.)取代名稱出現,則該套件套件區塊中宣告的所有匯出識別字將在匯入原始檔的檔案區塊中宣告,且必須無修飾符存取。
ImportPath 的解釋相依於實作,但通常是編譯套件的完整檔案名稱的子字串,且可能相對於已安裝套件的儲存庫。
實作限制:編譯器可能限制 ImportPath 為僅使用屬於 Unicode's L, M, N, P, 和 S 通用類別(無空格的圖形字元)的字元,並可能排除字元 !"#$%&'()*,:;<=>?[\]^`{|} 和 Unicode 替代字元 U+FFFD。
考慮一個包含套件子句 package math 的已編譯套件,它匯出了函式 Sin,並在 "lib/math" 標識的檔案中安裝了編譯套件。下表說明了如何在不同類型的匯入宣告後,在匯入套件的檔案中存取 Sin。
匯入宣告宣告了匯入套件和匯入套件之間的相依關係。套件直接或間接匯入自身是非法的,或直接匯入一個套件卻不引用其任何匯出識別字也是非法的。為了僅為了其副作用(初始化)匯入套件,請使用空識別字作為顯式套件名:
範例套件
以下是一個實作並行質數篩選的完整 Go 套件。
程式初始化與執行
零值
當為變數分配儲存空間時,無論是透過宣告或呼叫 new,或是建立新值,無論是透過複合字面量或呼叫 make,且未提供明確初始化,則變數或值將被賦予預設值。這種變數或值的每個元素都會被設定為對應型別的零值:布林型別為 false,數值型別為 0,字串為 "",指標、函式、介面、切片、通道和對應為 nil。這種初始化是遞迴進行的,因此,例如,若未指定值,則結構體陣列中的每個元素的欄位都會被清零。
以下兩個簡單宣告是等價的:
在
之後,以下成立:
以下也成立
套件初始化
在套件內,套件層級變數的初始化逐步進行,每個步驟選擇宣告順序中最早且對未初始化變數沒有相依關係的變數。
更準確地說,若套件層級變數尚未初始化,且沒有初始化運算式或其初始化運算式對未初始化變數沒有相依,則認為該變數已準備好進行初始化。初始化透過重複初始化宣告順序中最早且準備好初始化的下一個套件層級變數來進行,直到沒有變數準備好初始化。
若此過程結束時仍有變數未初始化,則這些變數屬於一個或多個初始化循環,且程式無效。
由右側單個(多值)運算式初始化的變數宣告左側的多個變數將一起初始化:若左側的任何變數已初始化,則所有這些變數將在同一步驟中初始化。
出於套件初始化的目的,空變數在宣告中被視為任何其他變數。
多檔案宣告的變數宣告順序由檔案呈現給編譯器的順序決定:第一個檔案中宣告的變數在第二個檔案中宣告的任何變數之前宣告,依此類推。為了確保可重複的初始化行為,建構系統應鼓勵按字典檔名順序向編譯器呈現屬於同一套件的多個檔案。
相依分析不依賴於變數的實際值,僅依賴於原始碼中對它們的詞法引用,進行傳遞性分析。例如,若變數 x 的初始化運算式引用了一個其主體引用變數 y 的函式,則 x 相依於 y。具體來說:
- 對變數或函式的引用是表示該變數或函式的識別字。
- 對方法
m的引用是形式為t.m的方法值或方法運算式,其中t的(靜態)型別不是介面型別,且方法m在t的方法集中。無論結果函式值t.m是否被呼叫都無關緊要。 - 若變數、函式或方法
x的初始化運算式或主體(對於函式和方法)包含對y或相依於y的函式或方法的引用,則x相依於變數y。
例如,給定以下宣告
初始化順序是 d, b, c, a。注意初始化運算式中子運算式的順序是無關緊要的:a = c + b 和 a = b + c 在這個例子中會導致相同的初始化順序。
相依分析按套件進行;僅考慮引用當前套件中宣告的變數、函式和(非介面)方法的引用。若變數之間存在其他隱藏的資料相依關係,則這些變數之間的初始化順序是未指定的。
例如,給定以下宣告
變數 a 將在 b 之後初始化,但 x 是在 b 之前、b 和 a 之間還是 a 之後初始化,以及 sideEffect() 被呼叫的時刻(在 x 初始化之前或之後)是未指定的。
變數也可以使用在套件區塊中宣告的名為 init 的函式進行初始化,這些函式沒有引數且沒有結果參數。
每個套件可以定義多個這樣的函式,甚至可以在單個原始檔中定義多個。在套件區塊中,init 識別字只能用於宣告 init 函式,而識別字本身不會被宣告。因此 init 函式在程式的任何地方都不能被引用。
整個套件的初始化透過為其所有套件層級變數分配初始值,然後按照它們在原始碼中出現的順序呼叫所有 init 函式來完成,可能涉及多個檔案,如呈現給編譯器的順序。
程式初始化
完整程式的套件逐步初始化,一次一個套件。若一個套件有匯入,則在初始化該套件本身之前,先初始化匯入的套件。若多個套件匯入同一個套件,則匯入的套件只初始化一次。透過建構,套件的匯入保證了不存在循環初始化相依關係。更精確地說:
給定所有套件按匯入路徑排序的列表,在每個步驟中,列表中第一個未初始化的套件,其所有匯入的套件(若有)已經初始化,則被初始化。重複此步驟,直到所有套件都初始化。
套件初始化——變數初始化與 init 函式的呼叫——在單個 goroutine 中依序發生,一次一個套件。init 函式可以啟動其他 goroutines,這些 goroutines 可以與初始化程式碼並行執行。然而,初始化總是依序執行 init 函式:在呼叫下一個 init 函式之前,前一個 init 函式必須返回。
程式執行
完整程式透過將一個單一的、未匯入的套件(稱為 main 套件)與它匯入的所有套件(傳遞性地)連結來建立。main 套件必須有套件名 main 並宣告一個不帶引數且不回傳值的 main 函式。
程式執行從初始化程式開始,然後呼叫 main 套件中的 main 函式。當該函式呼叫返回時,程式退出。它不會等待其他(非 main)goroutines 完成。
錯誤
預宣告型別 error 定義為
它是表示錯誤條件的常規介面,nil 值表示沒有錯誤。例如,從檔案讀取資料的函式可能定義為:
執行期恐慌
執行錯誤,如嘗試索引超出陣列邊界,會觸發一個執行期恐慌,相當於呼叫內建函式 panic,其引數為實作定義的介面型別 runtime.Error。該型別滿足預宣告介面型別 error。表示不同執行期錯誤條件的精確錯誤值是未指定的。
系統考量
unsafe 套件
內建套件 unsafe 對編譯器可見,並透過匯入路徑 "unsafe" 存取,提供低階程式設計設施,包括違反型別系統的作業。使用 unsafe 的套件必須手動檢查型別安全性,且可能不可移植。該套件提供以下介面:
Pointer 是指標型別,但 Pointer 值不能被解引用。任何指標或底層型別為 uintptr 的值都可以轉換為底層型別為 Pointer 的型別,反之亦然。若相應型別是型別參數,則它們各自型別集合中的所有型別必須具有相同的基礎型別,分別為 uintptr 和 Pointer。Pointer 和 uintptr 之間轉換的效果是相依於實作的。
函式 Alignof 和 Sizeof 接受任何型別的運算式 x,並分別回傳假設變數 v 的對其或大小,就像 v 透過 var v = x 宣告一樣。
函式 Offsetof 接受一個(可能加括號)選擇子 s.f,表示結構體 s 或 *s 的欄位 f,並回傳欄位相對於結構體地址的位元組偏移量。若 f 是嵌入欄位,則它必須可以透過結構體的欄位無指標間接存取。對於具有欄位 f 的結構體 s:
電腦架構可能要求記憶體地址對齊;即變數的地址是某個因子的倍數,該變數的型別的對齊。函式 Alignof 接受表示任何型別變數的運算式,並回傳(變數型別的)對齊位元組數。對於變數 x:
(變數)型別 T 具有可變大小,若 T 是型別參數,或是包含可變大小元素或欄位的陣列或結構體型別。否則大小是常數。若其引數(或選擇子運算式 s.f 中的結構體 s 對於 Offsetof)是常數大小的型別,則對 Alignof、Offsetof 和 Sizeof 的呼叫是型別 uintptr 的編譯期常數運算式。
函式 Add 將 len 加到 ptr 上並回傳更新後的指標 unsafe.Pointer(uintptr(ptr) + uintptr(len)) [Go 1.17]。len 引數必須是整數型別或未型別的常數。常數 len 引數必須可由型別 int 的值表示;若是未型別的常數,則其型別為 int。Pointer 的有效使用規則仍然適用。
函式 Slice 回傳一個切片,其基礎陣列從 ptr 開始,其長度與容量為 len。Slice(ptr, len) 等價於
除了特殊情況,若 ptr 是 nil 且 len 為零,Slice 回傳 nil [Go 1.17]。
len 引數必須是整數型別或未型別的常數。常數 len 引數必須是負數,且可由型別 int 的值表示;若是未型別的常數,則其型別為 int。在執行期,若 len 為負數,或是 ptr 是 nil 且 len 不為零,則會發生執行期恐慌 [Go 1.17]。
函式 SliceData 回傳 slice 引數的基礎陣列的指標。若切片的容量 cap(slice) 不為零,則該指標為 &slice[:1][0]。若 slice 為 nil,則結果為 nil。否則它是對未指定記憶體地址的非 nil 指標 [Go 1.20]。
函式 String 回傳一個 string 值,其基礎位元組從 ptr 開始,其長度為 len。ptr 和 len 引數的要求與 Slice 函式相同。若 len 為零,則結果為空字串 ""。由於 Go 字串是不可變的,傳遞給 String 的位元組之後不能被修改 [Go 1.20]。
函式 StringData 回傳 str 引數的基礎位元組的指標。對於空字串,回傳值是未指定的,可能為 nil。由於 Go 字串是不可變的,StringData 回傳的位元組不能被修改 [Go 1.20]。
大小與對齊保證
對於數值型別,以下大小是保證的:
以下最小對齊屬性是保證的:
- 對於任何型別的變數
x:unsafe.Alignof(x)至少為 1。 - 對於結構體型別的變數
x:unsafe.Alignof(x)是x的每個欄位f的unsafe.Alignof(x.f)值的最大值,但至少為 1。 - 對於陣列型別的變數
x:unsafe.Alignof(x)與陣列元素型別變數的對齊方式相同。
若結構體或陣列型別不包含任何大小大於零的欄位(或元素),則其大小為零。兩個不同的零大小變數可能在記憶體中具有相同的地址。
附錄
語言版本
Go 1 相容性保證確保根據 Go 1 規範撰寫的程式在該規範的整個生命週期內將繼續正確編譯與執行,且無需更改。更一般地,隨著語言調整與新增功能,相容性保證確保適用於特定 Go 語言版本的 Go 程式將繼續適用於任何後續版本。
例如,使用 0b 前綴表示二進位整數字面量的能力是在 Go 1.13 中引入的,如整數字面量部分中的 [Go 1.13] 所示。若編譯器使用的隱含或必需的語言版本早於 Go 1.13,則包含 0b1011 等整數字面量的原始碼會被拒絕。
下表說明了 Go 1 之後引入的功能所需的最低語言版本。
Go 1.9
- 可以使用別名宣告為型別宣告別名名稱。
Go 1.13
- 整數字面量可以使用前綴
0b、0B、0o和0O分別表示二進位與八進位字面值。 - 十六進位浮點字面值可以使用前綴
0x和0X編寫。 - 虛部後綴
i可以用於任何(二進位、十進位、十六進位)整數或浮點字面值,而不僅僅是十進位字面值。 - 任何數字字面值的數字可以使用下劃線
_分隔(分組)。 - 移位操作中的移位計數可以是帶符號的整數型別。
Go 1.14
- 透過不同的嵌入介面多次嵌入方法不是錯誤。
Go 1.17
Go 1.18
1.18 版本為語言添加了多型函式與型別(「泛型」)。具體來說:
- 運算子與標點符號集合包括新符號
~。 - 函式與型別宣告可以宣告型別參數。
- 介面型別可以嵌入任意型別(不僅僅是介面的型別名)以及聯合與
~T型別元素。 - 預宣告型別集合包括新型別
any與comparable。
Go 1.20
- 若切片和陣列元素型別匹配,且陣列長度不超過切片長度,則切片可以轉換為陣列。
- 內建套件
unsafe包括新的函式SliceData、String與StringData。 - 可比較型別(如普通介面)可以滿足
comparable限制,即使型別參數不是嚴格可比較的。
Go 1.21
Go 1.22
- 在"for" 敘述中,每次迭代都有自己的迭代變數集,而不是在每次迭代中共享相同的變數。
- 帶有"range" 子句的 "for" 敘述可以從零到上限的整數值進行迭代。
Go 1.23
- 帶有"range" 子句的 "for" 敘述接受一個迭代器函式作為範圍運算式。
Go 1.24
型別統一規則
型別統一規則描述了兩種型別是否以及如何統一。具體細節與 Go 實作相關,影響錯誤訊息的具體內容(例如,編譯器報告型別推論錯誤還是其他錯誤),並可能解釋為什麼在異常程式碼情況下型別推論失敗。但在撰寫 Go 程式碼時,這些規則大多可以忽略:型別推論設計為基本「按預期運作」,統一規則也相應進行了微調。
型別統一由匹配模式控制,可能是精確或寬鬆。當統一遞迴下降到複合型別結構時,用於型別元素的匹配模式(元素匹配模式)與匹配模式相同,除非兩種型別因可賦值性(≡<sub>A</sub>)而統一:在此情況下,匹配模式在頂層是寬鬆的,但對於元素型別則變為精確,反映出型別不必完全相同即可賦值的事實。
若滿足以下任一條件,則兩種非綁定型別參數的型別完全統一:
- 兩種型別相同。
- 兩種型別具有相同的結構,且其元素型別完全統一。
- 恰好一種型別是未綁定的型別參數,且其型別集合中的所有型別根據
≡<sub>A</sub>的統一規則(頂層寬鬆統一,元素型別精確統一)與另一種型別統一。
若兩種型別都是綁定型別參數,則它們根據給定的匹配模式統一,若:
- 兩種型別參數相同。
- 最多一種型別參數具有已知型別參數。在此情況下,型別參數被合併:它們都代表相同的型別參數。若兩種型別參數都還沒有已知型別參數,則為一個型別參數推論的未來型別參數同時推論為兩種型別參數。
- 兩種型別參數都具有已知型別參數,且型別參數根據給定的匹配模式統一。
單個綁定型別參數 P 與另一種型別 T 根據給定的匹配模式統一,若:
P沒有已知型別參數。在此情況下,T被推論為P的型別參數。P確實有已知型別參數A,A與T根據給定的匹配模式統一,並滿足以下條件之一:
最後,若滿足以下條件,則兩種非綁定型別參數的型別鬆散統一(並根據元素匹配模式):