Go 程式設計語言規格

2021 年 10 月 15 日版本

前言

這是 Go 程式設計語言的參考手冊,適用於 2021 年 10 月之前,在加入泛型功能之前的 1.17 版語言。提供這些內容以供史料參考。最新的參考手冊可以從 此處 找到。進一步資訊和其他文件,請參閱 go.dev

Go 是以系統程式設計為主要考量的通用語言。它具有強類別、自動垃圾回收的功能,並且明確地支援並行程式設計。程式是由 套件 組成,而套件的屬性可以有效地管理相依性。

語法簡潔,且易於解析,讓自動化工具(例如整合開發環境)可以輕易地進行分析。

符號說明

語法使用擴充巴科斯范式 (EBNF) 來指定

Production  = production_name "=" [ Expression ] "." .
Expression  = Alternative { "|" Alternative } .
Alternative = Term { Term } .
Term        = production_name | token [ "…" token ] | Group | Option | Repetition .
Group       = "(" Expression ")" .
Option      = "[" Expression "]" .
Repetition  = "{" Expression "}" .

製作是從條款和下列運算符所建構的表達式,並採由低階遞增優先權原則

|   alternation
()  grouping
[]  option (0 or 1 times)
{}  repetition (0 to n times)

小寫製作名稱用來識別詞法符號。非終端符號採駝峰式大小寫表示法。詞法符號包含在雙引號 "" 或反引號 `` 內。

a … b 樣式表示 ab 之間的字元集,可以互換。橫向省略符號 也用在規範中的其他地方,非正式地表示各種未進一步說明的列舉或程式碼片段。 字元(相對於三個字元 ...)並非 Go 語言的符號。

原始碼表示

原始碼是編碼於 UTF-8 的 Unicode 文字。文字並未標準化,因此單一帶重音的碼點與由合併重音和字母建構的相同字元有所不同;這些會被視為兩個碼點。為了簡化說明,本文件將使用沒有限定條件的術語 字元 來表示原始文字中的 Unicode 碼點。

每個碼點都不相同;例如,大小寫字母是不同的字元。

實作限制:為與其他工具相容,編譯器可能會禁止原始碼中出現空字元 (U+0000)。

實作限制:為與其他工具相容,若 UTF-8 編碼的位元組順序標記 (U+FEFF) 是原始碼中的第一個 Unicode 碼點,則編譯器可能會略過它。在原始碼中的其他地方可能會禁止位元組順序標記。

字元

下列術語用來表示特定 Unicode 字元類別

newline        = /* the Unicode code point U+000A */ .
unicode_char   = /* an arbitrary Unicode code point except newline */ .
unicode_letter = /* a Unicode code point classified as "Letter" */ .
unicode_digit  = /* a Unicode code point classified as "Number, decimal digit" */ .

Unicode 標準 8.0 中,第 4.5 節「一般類別」定義了字元類別集合。Go 將 Lu、Ll、Lt、Lm 或 Lo 任一字母類別中的所有字元都視為 Unicode 字母,而將數字類別 Nd 中的字元視為 Unicode 數字。

字母和數字

底線字元 _ (U+005F) 視為一個字母。

letter        = unicode_letter | "_" .
decimal_digit = "0" … "9" .
binary_digit  = "0" | "1" .
octal_digit   = "0" … "7" .
hex_digit     = "0" … "9" | "A" … "F" | "a" … "f" .

詞法元素

註解

註解用於作為程式文件。有兩種形式

  1. 行註解 起始於字元順序 //,並結束於該行尾。
  2. 一般註解 起始於字元順序 /*,並結束於第一個之後的字元順序 */

註解無法從符文字串文字內部開始,也不能開始於註解內部。不包含換行符號的一般註解就像一個空白。任何其他註解類似於換行符號。

符號

符號構成 Go 語言的詞彙表。有四種類別:識別碼關鍵字運算子和標點符號以及文字空白(由空格 (U+0020)、水平標籤 (U+0009)、回車 (U+000D) 和換行符號 (U+000A) 組成)會被忽略,僅當它將其他符號分開而使該符號組成一個符號時除外。另外,換行或檔案尾端可能會觸發分號的插入。將輸入分解為符號時,下一個符號會是構成有效符號的最長一串字元。

分號

正規文法使用分號 ";" 作為多個製程的終止符號。Go 程式可以使用以下兩個規則省略大多數這些分號

  1. 將輸入分解為符號時,如果符號為下列位置,系統會自動將分號插入符號串流,緊接在該行的最後一個符號之後
  2. 為了使複雜的陳述式能佔用單一行,分號可以在封閉的 ")""}" 前省略。

為了反映習慣用語,本文檔中的程式碼範例會使用這些規則省去分號。

識別碼

識別碼為程式實體(例如變數和型別)命名。識別碼是包含一個以上字母和數位的一串字元。識別碼的第一個字元必須是字母。

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

運算子和標點符號

下列字元序列代表運算子(包括指定運算子)和標點符號

+    &     +=    &=     &&    ==    !=    (    )
-    |     -=    |=     ||    <     <=    [    ]
*    ^     *=    ^=     <-    >     >=    {    }
/    <<    /=    <<=    ++    =     :=    ,    ;
%    >>    %=    >>=    --    !     ...   .    :
     &^          &^=

整數文字

整數文字是代表整數常數的一連串數字。選擇性的前綴設定非十進制底數:0b0B 代表二進制,00o0O 代表八進制,0x0X 代表十六進制。單一的 0 視為十進制的零。在十六進制文字中,字母 afAF 代表值 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

浮點文字

浮點文字是浮點常數的十進制或十六進制表示式。

十進制浮點文字包含整數部分(十進制數字)、小數點、小數部分(十進制數字)和指數部分(eE 後接選擇性的正負號和十進制數字)。整數部分或小數部分其中之一可以省略;小數點或指數部分其中之一可以省略。指數值 exp 以 10exp 為底數,縮放尾數(整數和小數部分)。

十六進制浮點文字包含 0x0X 前綴、整數部分(十六進制數字)、基底點、小數部分(十六進制數字)和指數部分(pP 後接選擇性的正負號和十進制數字)。整數部分或小數部分其中之一可以省略;基底點也可以省略,但指數部分是必要的。(此語法與 IEEE 754-2008 §5.12.3 中所述相符。)指數值 exp 以 2exp 為底數,縮放尾數(整數和小數部分)。

為了易讀性,底數前綴後或連續數字間可以出現底線字元 _;此類底線不會改變文字值。

float_lit         = decimal_float_lit | hex_float_lit .

decimal_float_lit = decimal_digits "." [ decimal_digits ] [ decimal_exponent ] |
                    decimal_digits decimal_exponent |
                    "." decimal_digits [ decimal_exponent ] .
decimal_exponent  = ( "e" | "E" ) [ "+" | "-" ] decimal_digits .

hex_float_lit     = "0" ( "x" | "X" ) hex_mantissa hex_exponent .
hex_mantissa      = [ "_" ] hex_digits "." [ hex_digits ] |
                    [ "_" ] hex_digits |
                    "." hex_digits .
hex_exponent      = ( "p" | "P" ) [ "+" | "-" ] decimal_digits .
0.
72.40
072.40       // == 72.40
2.71828
1.e+0
6.67428e-11
1E6
.25
.12345E+5
1_5.         // == 15.0
0.15e+0_2    // == 15.0

0x1p-2       // == 0.25
0x2.p10      // == 2048.0
0x1.Fp+0     // == 1.9375
0X.8p-0      // == 0.5
0X_1FFFP-16  // == 0.1249847412109375
0x15e-2      // == 0x15e - 2 (integer subtraction)

0x.p1        // invalid: mantissa has no digits
1p-2         // invalid: p exponent requires hexadecimal mantissa
0x1.5e-2     // invalid: hexadecimal mantissa requires p exponent
1_.5         // invalid: _ must separate successive digits
1._5         // invalid: _ must separate successive digits
1.5_e1       // invalid: _ must separate successive digits
1.5e_1       // invalid: _ must separate successive digits
1.5e1_       // invalid: _ must separate successive digits

虛數文字

虛數文字代表複數常數的虛部。它由整數浮點文字構成,後面接小寫字母 i。虛數文字的值等於各自整數或浮點文字的值,乘以虛數單位 i

imaginary_lit = (decimal_digits | int_lit | float_lit) "i" .

為了向後相容,虛數文字的整數部分如果完全由十進制數字(以及可能的底線)組成,則視為十進制整數,即使它以開頭的 0 開頭。

0i
0123i         // == 123i for backward-compatibility
0o123i        // == 0o123 * 1i == 83i
0xabci        // == 0xabc * 1i == 2748i
0.i
2.71828i
1.e+0i
6.67428e-11i
1E6i
.25i
.12345E+5i
0x1p-2i       // == 0x1p-2 * 1i == 0.25i

符文文字

符文文字代表符文常數,一個用於識別 Unicode 碼點的整數值。符文文字表示為單引號引起來的一個或多個字元,如 'x''\n'。在引號內,除了換行和未跳脫的單引號,任何字元都可以出現。單引號字元代表字元本身的 Unicode 值,而以反斜線開頭的多字元序列則以各種格式編碼值。

最簡單的形式代表引號中的單一字元,因為 Go 原始碼文字是以 UTF-8 編碼的 Unicode 字元,多個 UTF-8 編碼的位元組可以代表一個單一的整數值。例如,字面值 'a' 包含一個位元組來代表字面值 a,Unicode U+0061,值 0x61,而 'ä' 包含兩個位元組 (0xc3 0xa4) 來代表字面值 a-分音符,U+00E4,值 0xe4

多個反斜線逸出符號允許將任意值編碼成 ASCII 文字。有四種方法將整數值表示成數字常數:\x 後接兩個十六進位數字;\u 後接四個十六進位數字;\U 後接八個十六進位數字,以及一個反斜線 \ 後接三個八進位數字。在每一個情況中,字面值的 \ 分別以對應的基數所代表的數值。

雖然所有這些表示方式都會產生一個整數,它們有不同的有效範圍。八進位逸出符號必須表示 0 到 255(包含)之間的值。十六進位逸出符號通過結構滿足這個條件。逸出符號 \u\U 表示 Unicode 碼點,因此其中一些值是非法的,特別是那些大於 0x10FFFF 和代理項半部的。

在反斜線之後,某些單一字元逸出符號表示特殊的值。

\a   U+0007 alert or bell
\b   U+0008 backspace
\f   U+000C form feed
\n   U+000A line feed or newline
\r   U+000D carriage return
\t   U+0009 horizontal tab
\v   U+000B vertical tab
\\   U+005C backslash
\'   U+0027 single quote  (valid escape only within rune literals)
\"   U+0022 double quote  (valid escape only within string literals)

所有其他以反斜線開始的序列在符文字面值中都是非法的。

rune_lit         = "'" ( unicode_value | byte_value ) "'" .
unicode_value    = unicode_char | little_u_value | big_u_value | escaped_char .
byte_value       = octal_byte_value | hex_byte_value .
octal_byte_value = `\` octal_digit octal_digit octal_digit .
hex_byte_value   = `\` "x" hex_digit hex_digit .
little_u_value   = `\` "u" hex_digit hex_digit hex_digit hex_digit .
big_u_value      = `\` "U" hex_digit hex_digit hex_digit hex_digit
                           hex_digit hex_digit hex_digit hex_digit .
escaped_char     = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | "'" | `"` ) .
'a'
'ä'
'本'
'\t'
'\000'
'\007'
'\377'
'\x07'
'\xff'
'\u12e4'
'\U00101234'
'\''         // rune literal containing single quote character
'aa'         // illegal: too many characters
'\xa'        // illegal: too few hexadecimal digits
'\0'         // illegal: too few octal digits
'\uDFFF'     // illegal: surrogate half
'\U00110000' // illegal: invalid Unicode code point

字串字面值

字串字面值代表從連接字元序列中取得的 字串常數。有兩種形式:原始字串字面值和已詮釋字串字面值。

原始字串字面值是反引號之間的字元序列,就像 `foo`。在引號內,任何字元都可以出現,除了反引號。原始字串字面值的值是由引號之間未詮釋(隱含的 UTF-8 編碼)字元組成的字串;特別是,反斜線沒有特殊含意,字串可以包含新行。原始字串字面值中的回車字元 ('\r') 會從原始字串值中捨棄。

解析字串文字是雙引號內的字元序列,例如:"bar"。在引號中,除了換行符號和未跳脫的雙引號,任何字元都可以出現。引號中的文字組成文字的值,反斜線跳脫字元的解析,與 rune 文字 相同(除了 \' 是不合法,而 \" 是合法的),但有相同的限制。三碼八進位(\nnn)和兩碼十六進位(\xnn)跳脫字元代表結果字串的個別位元組;其他所有跳脫字元都代表個別字元的 UTF-8 編碼(可能是多位元組)。因此在字串文字中,\377\xFF 代表值為 0xFF=255 的單一位元組,而 ÿ\u00FF\U000000FF\xc3\xbf 則代表字元 U+00FF 的 UTF-8 編碼的兩個位元組:0xc30xbf

string_lit             = raw_string_lit | interpreted_string_lit .
raw_string_lit         = "`" { unicode_char | newline } "`" .
interpreted_string_lit = `"` { unicode_value | byte_value } `"` .
`abc`                // same as "abc"
`\n
\n`                  // same as "\\n\n\\n"
"\n"
"\""                 // same as `"`
"Hello, world!\n"
"日本語"
"\u65e5本\U00008a9e"
"\xff\u00FF"
"\uD800"             // illegal: surrogate half
"\U00110000"         // illegal: invalid Unicode code point

這些範例都代表相同的字串

"日本語"                                 // UTF-8 input text
`日本語`                                 // UTF-8 input text as a raw literal
"\u65e5\u672c\u8a9e"                    // the explicit Unicode code points
"\U000065e5\U0000672c\U00008a9e"        // the explicit Unicode code points
"\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"  // the explicit UTF-8 bytes

如果原始碼將字元表示為兩個碼點,例如包含重音符號和字母的組合形式,如果放在 rune 文字中,結果將會出錯(這不是單一碼點),如果放在字串文字中,將會顯示為兩個碼點。

常數

共有布林常數rune 常數整數常數浮點常數複數常數字串常數。Rune、整數、浮點和複數常數統稱為數字常數

常數值由 rune整數浮點數學字串 文字,表示常數的識別碼、常數表達式、結果為常數的 轉換,或某些內建函式的結果值表示,例如套用至任何值的 unsafe.Sizeof 套用至 某些表達式caplen、套用至複數常數的 realimag 以及套用至數字常數的 complex。布林真值由預先宣告的常數 truefalse 表示。預先宣告的識別碼 iota 表示整數常數。

一般而言,複數常數是 常數表達式 的一種形式,在該部分會說明。

數字常數表示任意精度的確切值,而且不會溢位。因此沒有常數表示 IEEE 754 的負零、無限大或非數字值。

常數可能是 類型化非類型化。字面常數、truefalseiota,以及僅包含非類型化常數運算元的某些 常數表達式 是非類型化的。

常量可以透過 常量宣告轉換 明確指定類型,或在 變數宣告賦值運算式 的運算元中使用時隱式指定類型。如果無法將常量值 表示 為特定類型的值,則會產生錯誤。

未指定類型的常量具有 預設類型,當在需要有指定類型值的環境中使用常量時會將常量轉換為此預設類型,例如沒有指定類型時,在 i := 0簡短變數宣告 中。未指定類型常量的預設類型分別是 boolruneintfloat64complex128string,視其為布林值、符號、整數、浮點數、複數或字串常量而定。

實作限制:儘管數字常量在程式語言中具有任意精度,編譯器仍可能使用具有限精度之內部表示。話雖如此,每個實作都必須

這些需求適用於文字常量以及評估 常態運算式 的結果。

變數

變數是儲存 的儲存位置。允許值集合由變數的 類型 決定。

變數宣告(對於函式參數和結果)或 函式宣告函式文字 的特徵在於保留儲存空間給已命名變數。呼叫內建函式 new 或取得 複合文字 的位址會於執行時期配置儲存空間給變數。此類匿名變數會透過(可能是隱含的)指標解參考 參照。

陣列切片結構 類型的結構化變數具有可以個別 設定位址 的元素和欄位。每個此類元素都如同一個變數一般。

變數的靜態類型(或僅類型)是在其宣告中給定的類型、提供於new呼叫或複合文字中的類型,或是一個結構變數中某一元素的類型。介面型式的變數也有一個不同的動態類型,那是變數在執行階段指派的具體類型(除非該值為預先宣告的識別碼nil,其沒有類型)。動態類型可能會隨著執行改變,但儲存在介面變數中的值總是可指派到變數的靜態類型。

var x interface{}  // x is nil and has static type interface{}
var v *T           // v has value nil, static type *T
x = 42             // x has value 42 and dynamic type int
x = v              // x has value (*T)(nil) and dynamic type *T

變數的值是透過在一個表示式中參照該變數而取得的;那是最近指派給該變數的值。如果尚未指派一個值給一個變數,它的值會是它的類型中的零值

類型

一個類型決定了一組值,以及針對那些值而定的運算和方法。如果一個類型有一個類型名稱,則可以透過它來表示類型,或使用類型文字來指定類型,它使用現有的類型來組合一個類型。

Type      = TypeName | TypeLit | "(" Type ")" .
TypeName  = identifier | QualifiedIdent .
TypeLit   = ArrayType | StructType | PointerType | FunctionType | InterfaceType |
	    SliceType | MapType | ChannelType .

這門語言預先宣告某些類型名稱。其他類型則會透過類型宣告來引入。複合類型(陣列、結構、指標、函式、介面、切片、對應、和通道類型)可以使用類型文字來建構。

每個類型T有一個底層類型:如果T是預先宣告的布林、數字、或字串類型中的一種,或是一個類型文字,那麼對應的底層類型就是T本身。否則,T的底層類型就是T在其類型宣告中所參照類型的底層類型。

type (
	A1 = string
	A2 = A1
)

type (
	B1 string
	B2 B1
	B3 []B1
	B4 B3
)

字串A1A2B1、和B2的底層類型是字串[]B1B3、和B4的底層類型是[]B1

方法集

類型具有與其相關聯的(可能為空)方法組介面類型 的方法組即為其介面。任何其他類型 T 的方法組包含透過接收類型 T 宣告的所有 方法。對應的 指標類型 *T 的方法組,是透過接收類型 *TT 宣告的所有方法組(亦即,也包含 T 的方法組)。其他規則適用於含有內嵌欄位的結構,如 結構類型 中的章節所述。任何其他類型都有空方法組。在方法組中,每個方法都必須有 唯一的空白 方法名稱

類型的使用方法組可決定類型會 實作 什麼介面,以及可使用該類型接收項 呼叫 哪些方法。

布林類型

布林類型代表布林真值集,由預先宣告的常數 truefalse 表示。預先宣告的布林類型為 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 的別名。當在表示式或指定中混用不同的數值類型時,需要明確轉換。例如,int32int 不是同一個類型,即使在特定架構中它們的大小可能相同。

字串類型

字串類型代表字串值的集合。字串值是(可能為空)的位元組序列。位元組數量稱為字串的長度,且永遠是非負值。字串是不可變的:建立後,不可能變更字串的內容。預先宣告的字串類型為 string;它是一種 已定義的類型

字串 s 的長度可以使用內建函式 len 找出。如果字串是常數,長度就是編譯時期常數。字串的位元組可以用整數 指標 0 到 len(s)-1 存取。取得此類元素的位址是不合法的;如果 s[i] 是字串中第 i 個位元組,則 &s[i] 是無效的。

陣列類型

陣列是一個單一類型元素的有序序列,稱為元素類型。元素數量稱為陣列長度,絕不會為負值。

ArrayType   = "[" ArrayLength "]" ElementType .
ArrayLength = Expression .
ElementType = Type .

長度是陣列類型的一部分;它必須評估為非負值 常數,並可由 int 類型的值 表示。陣列 a 的長度可以使用內建函式 len 找出。元素可以用整數 指標 0 到 len(a)-1 來處理。陣列類型永遠是一維的,但可以組合來形成多維類型。

[32]byte
[2*N] struct { x, y int32 }
[1000]*float64
[3][5]int
[2][2][2]float64  // same as [2]([2]([2]float64))

切片類型

切片是對 基礎陣列 中的連續區段的描述,並提供對該陣列中一系列排序元素的存取。切片類型表示其元素類型的陣列的所有切片的集合。元素數量稱為切片長度,絕不會為負值。未初始化的切片的值為 nil

SliceType = "[" "]" ElementType .

切片 s 的長度可以用內建函式 len 找出;與陣列不同的是,它在執行期間可能會改變。元素可以用整數 指標 0 到 len(s)-1 來處理。特定元素的切片指標可能會小於其在基礎陣列中相同元素的指標。

切片一旦初始化,就會永遠與儲存其元素的基礎陣列關聯。因此,切片會與其陣列和同一陣列的其他切片共用儲存空間;相比之下,不同的陣列永遠代表不同的儲存空間。

基礎陣列的切片可能會延伸到切片的結尾後。容量 是衡量該範圍的指標:它是切片長度加上切片外的陣列長度的總和;長度小於此容量的切片可以透過 切片 在原始切片中建立一個新的切片。切片 a 的容量可以用內建函式 cap(a) 找出。

一個新、已初始化的切片值會使用內建函數 make 建立,此函數會採用切片類型和參數,指定長度和(選擇性)容量。使用 make 建立的切片總是會配置一個新的、隱藏的陣列,已傳回的切片值會參考此陣列。也就是說,執行

make([]T, length, capacity)

會產生與配置一個陣列並對它執行 切片 相同的切片,因此這兩個運算式相等

make([]int, 50, 100)
new([100]int)[0:50]

和陣列一樣,切片始終是一維的,但可以用來構成多維物件。對於陣列陣列,內部陣列在結構上總是具有相同的長度;但對於切片切片(或切片陣列)而言,內部長度可能會動態變化。此外,內部切片必須個別初始化。

結構型別

結構是一個命名元素的序列,稱為欄位,每個元素都有名稱和型別。欄位名稱可以明確指定(識別子清單),或暗中指定(嵌入欄位)。在一個結構中,非空白欄位名稱必須唯一

StructType    = "struct" "{" { FieldDecl ";" } "}" .
FieldDecl     = (IdentifierList Type | EmbeddedField) [ Tag ] .
EmbeddedField = [ "*" ] TypeName .
Tag           = string_lit .
// An empty struct.
struct {}

// A struct with 6 fields.
struct {
	x, y int
	u float32
	_ float32  // padding
	A *[]int
	F func()
}

欄位宣告為一個型別,但沒有明確欄位名稱,稱為嵌入欄位。嵌入欄位必須指定為一個型別名稱T,或一個非介面型別名稱*T的指標,而且T本身不能夠是指標型別。未限定的型別名稱會作用為欄位名稱。

// A struct with four embedded fields of types T1, *T2, P.T3 and *P.T4
struct {
	T1        // field name is T1
	*T2       // field name is T2
	P.T3      // field name is T3
	*P.T4     // field name is T4
	x, y int  // field names are x and y
}

下列宣告為非法,因為欄位名稱在結構型別中必須唯一

struct {
	T     // conflicts with embedded field *T and *P.T
	*T    // conflicts with embedded field T and *P.T
	*P.T  // conflicts with embedded field T and *T
}

結構x中嵌入欄位的欄位或方法 f被稱為提升,如果x.f是一個合法的選擇器用以表示欄位或方法f

提升欄位的作用與結構的普通欄位類似,但不能用於結構的複合文字作為欄位名稱。

給定一個結構型別S和一個定義的型別 T,提升方法會以下列方式包含在結構的方法集中

欄位宣告後面可以接續一個選擇性的字串文字標籤,這會成為對應欄位宣告中所有欄位的屬性。空的標籤字串等於沒有標籤。標籤可透過反射介面看見,並參與結構的型別身分,但其他情況下會被略過。

struct {
	x, y float64 ""  // an empty tag string is like an absent tag
	name string  "any string is permitted as a tag"
	_    [4]byte "ceci n'est pas un champ de structure"
}

// A struct corresponding to a TimeStamp protocol buffer.
// The tag strings define the protocol buffer field numbers;
// they follow the convention outlined by the reflect package.
struct {
	microsec  uint64 `protobuf:"1"`
	serverIP6 uint64 `protobuf:"2"`
}

指標型別

指標型別表示所有指標的集合,這些指標指向特定型別的變數(稱為指標的基礎型別)。未初始化指標的值為nil

PointerType = "*" BaseType .
BaseType    = Type .
*Point
*[4]int

函數型別

函數型別表示具有相同參數和結果型別的所有函數集合。未初始化函數型別變數的值為 nil

FunctionType   = "func" Signature .
Signature      = Parameters [ Result ] .
Result         = Parameters | Type .
Parameters     = "(" [ ParameterList [ "," ] ] ")" .
ParameterList  = ParameterDecl { "," ParameterDecl } .
ParameterDecl  = [ IdentifierList ] [ "..." ] Type .

在參數或結果清單中,名稱 (IdentifierList) 必須全部存在或全部不存在。如果存在,每個名稱代表已指定型別的一項 (參數或結果),且簽章中所有非 空白 名稱都必須是

函數簽章中的最後輸入參數可能會有一個開頭帶有 ... 的型別。具有此參數的函數稱為變長引數函數,而且可以針對該參數呼叫零個或多個引數。

func()
func(x int) int
func(a, _ int, z float32) bool
func(a, b int, z float32) (bool)
func(prefix string, values ...int)
func(a, b int, z float64, opt ...interface{}) (success bool)
func(int, int, float64) (float64, *[]int)
func(n int) func(p *T)

介面類型

介面類型指定一個稱為 *介面* 的 方法集。介面型別的變數可以儲存具有方法集的值,而方法集是該介面的任何超集。這樣的型別稱為 *實作介面*。介面型別的未初始化變數的值為 nil

InterfaceType      = "interface" "{" { ( MethodSpec | InterfaceTypeName ) ";" } "}" .
MethodSpec         = MethodName Signature .
MethodName         = identifier .
InterfaceTypeName  = TypeName .

介面類型可能會透過方法規格明確指定方法,或者它可能會透過介面型別名稱內嵌其他介面的方法。

// A simple File interface.
interface {
	Read([]byte) (int, error)
	Write([]byte) (int, error)
	Close() error
}

每個明確指定方法的名稱必須是 且不可為 空白

interface {
	String() string
	String() string  // illegal: String not unique
	_(x int)         // illegal: method must have non-blank name
}

一個以上的型別可能會實作介面。例如,如果兩個型別 S1S2 具有方法集

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 代表 S1S2) 則 檔案 介面由 S1S2 實作,而不論 S1S2 可能具有或共用哪種其他方法。

一個型別實作包含其方法的任何子集的任何介面,因此可能會實作幾個不同的介面。例如,所有型別都實作 *空介面*

interface{}

類似地,考慮出現在 型別宣告 中以定義稱為 Locker 的介面的介面規格

type Locker interface {
	Lock()
	Unlock()
}

如果 S1S2 也實作

func (p T) Lock() { … }
func (p T) Unlock() { … }

它們實作 Locker 介面和 檔案 介面。

介面 `T` 可以使用 (可能加註限定詞) 的介面類型名稱 `E` 來取代函式規格。這稱為在 `T` 中「嵌入」介面 `E`。`T` 的 函式組 是 `T` 的明確宣告函式和 `T` 的嵌入介面的函式組的「聯集」。

type Reader interface {
	Read(p []byte) (n int, err error)
	Close() error
}

type Writer interface {
	Write(p []byte) (n int, err error)
	Close() error
}

// ReadWriter's methods are Read, Write, and Close.
type ReadWriter interface {
	Reader  // includes methods of Reader in ReadWriter's method set
	Writer  // includes methods of Writer in ReadWriter's method set
}

函式組的「聯集」包含每個函式組的 (已匯出和未匯出) 函式,且名稱 相同 的函式必須有 相同 的特徵碼。

type ReadCloser interface {
	Reader   // includes methods of Reader in ReadCloser's method set
	Close()  // illegal: signatures of Reader.Close and Close are different
}

介面類型 `T` 不能嵌入自己或任何嵌入 `T` 的介面類型 (遞迴)。

// illegal: Bad cannot embed itself
type Bad interface {
	Bad
}

// illegal: Bad1 cannot embed itself using Bad2
type Bad1 interface {
	Bad2
}
type Bad2 interface {
	Bad1
}

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 移除元素。

可以使用內建函式 make 建立新的空 map 值。此函式將 map 類型和選用的容量提示作為引數。

make(map[string]int)
make(map[string]int, 100)

初始容量不會限制其大小: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 進行關閉。接收運算子 的多值賦值形式會報告收到的值是否在通道關閉之前傳送。

單一通道可以在 傳送敘述接收運算 和呼叫內建函數 caplen 時由任何數量的 goroutine 使用,而不需要進一步同步。通道會作為先進先出的佇列執行。例如,如果一個 goroutine 在通道上傳送值,而另一個 goroutine 接收值,則會依據傳送順序接收到這些值。

類型與值的特性

類型的同一性

兩種類型可以是相等相異

已定義類型 永遠不同於其他任何類型。否則,兩種類型在其 基礎 類型文字結構相等時就屬於相等類型;也就是說,它們具有相同的文字結構,且對應的組件具有相同的類型。以下是詳細說明

給定以下宣告

type (
	A0 = []string
	A1 = A0
	A2 = struct{ a, b int }
	A3 = int
	A4 = func(A3, float64) *A0
	A5 = func(x int, _ float64) *[]string
)

type (
	B0 A0
	B1 []string
	B2 struct{ a, b int }
	B3 struct{ a, c int }
	B4 func(int, float64) *B0
	B5 func(x int, y float64) *A1
)

type	C0 = B0

這些類型相符

A0, A1, and []string
A2 and struct{ a, b int }
A3 and int
A4, func(int, float64) *[]string, and A5

B0 and C0
[]int and []int
struct{ a, b *T5 } and struct{ a, b *T5 }
func(x int, y float64) *[]string, func(int, float64) (result *[]string), and A5

B0B1 不同,因為它們是由不同的 類型定義 新建立的類型;func(int, float64) *B0func(x int, y float64) *[]string 不同,因為 B0 不同於 []string

可指派的

如果滿足以下條件之一,則值 x 可指派給類型為 T變數(「x 可指派給 T」)

可表述的

如果滿足以下條件之一,則 常數 x 可以由類型為 T 的值 表述

x                   T           x is representable by a value of T because

'a'                 byte        97 is in the set of byte values
97                  rune        rune is an alias for int32, and 97 is in the set of 32-bit integers
"foo"               string      "foo" is in the set of string values
1024                int16       1024 is in the set of 16-bit integers
42.0                byte        42 is in the set of unsigned 8-bit integers
1e10                uint64      10000000000 is in the set of unsigned 64-bit integers
2.718281828459045   float32     2.718281828459045 rounds to 2.7182817 which is in the set of float32 values
-1e-1000            float64     -1e-1000 rounds to IEEE -0.0 which is further simplified to 0.0
0i                  int         0 is an integer value
(42 + 0i)           float32     42.0 (with zero imaginary part) is in the set of float32 values
x                   T           x is not representable by a value of T because

0                   bool        0 is not in the set of boolean values
'a'                 string      'a' is a rune, it is not in the set of string values
1024                byte        1024 is not in the set of unsigned 8-bit integers
-1                  uint16      -1 is not in the set of unsigned 16-bit integers
1.1                 int         1.1 is not an integer value
42i                 float32     (0 + 42i) is not in the set of float32 values
1e1000              float64     1e1000 overflows to IEEE +Inf after rounding

區塊

區塊是在配對大括弧內可能為空的宣告與陳述序列。

Block = "{" StatementList "}" .
StatementList = { Statement ";" } .

除了原始碼中的明確區塊外,還有隱含的區塊

  1. 全域區塊包含所有 Go 原始碼文字。
  2. 每個 套件 都有 套件區塊,其中包含該套件的所有 Go 原始碼文字。
  3. 每個檔案都有 檔案區塊,其中包含該檔案的所有 Go 原始碼文字。
  4. 每個 「if」「for」「switch」 陳述被視為在其自己的隱含區塊中。
  5. 「switch」「select」 陳述式中的每個句項會作用為一個隱式區塊。

區塊會巢狀排列並影響 範圍

宣告和範圍

宣告 會將非 空白 識別碼繫結到 常數型別變數函式標籤封包。程式中的每個識別碼都必須宣告。在同一個區塊中不得宣告同一個識別碼兩次,而且在檔案和封包區塊中都不能宣告同一個識別碼。

空白識別碼 可以像宣告中的任何其他識別碼一樣使用,但不會建立繫結,因此不會宣告。在封包區塊中,識別碼 init 只可使用在 init 函式 宣告中,且和空白識別碼一樣不會建立新的繫結。

Declaration   = ConstDecl | TypeDecl | VarDecl .
TopLevelDecl  = Declaration | FunctionDecl | MethodDecl .

已宣告識別碼的範圍 是來源文字的範圍,其中識別碼表示指定的常數、型別、變數、函式、標籤或封包。

使用 區塊,Go 是以詞彙為範圍。

  1. 預先宣告的識別碼 的範圍是宇宙區塊。
  2. 表示在頂層 (在任何函式之外) 宣告的常數、型別、變數或函式 (但非方法) 的識別碼的範圍是封包區塊。
  3. 封包名稱的範圍是包含 import 宣告的檔案的檔案區塊。
  4. 表示方法接收方、函式參數或結果變數的識別碼範圍是函式主體。
  5. 在函式內部宣告的常數或變數識別碼的範圍始於 ConstSpec 或 VarSpec (用於宣告短變數的 ShortVarDecl) 結尾處,並於最內層包含的區塊的結尾處結束。
  6. 在函式內部宣告的型別識別碼的範圍始於 TypeSpec 中的識別碼,並於最內層包含的區塊的結尾處結束。

在區塊中宣告的識別碼可以在內部區塊中重新宣告。當內部宣告的識別碼在範圍內時,它會表示由內部宣告宣告的實體。

封包句 並非宣告;封包名稱不會出現在任何範圍中。其用意是識別屬於同一個 封包 的檔案,並針對匯入宣告指定預設的封包名稱。

標籤範圍

標籤是由 標籤陳述式 宣告,並用於 "break""continue""goto" 陳述式。定義一個從未使用的標籤並非合法。與其他識別碼不同的是,標籤不具有區塊範圍,且不會與非標籤的識別碼衝突。標籤的範圍是一個宣告該標籤的函數主體,但排除任何巢狀函數的主體。

空白識別碼

空白識別碼 以底線字元 _ 表示。它作為匿名佔位符用於一般(非空白)識別碼,且在 宣告運算元指定 中具有特別意義。

已預先宣告的識別碼

以下識別碼會在 universe 區塊 中隱含宣告

Types:
	bool byte complex64 complex128 error float32 float64
	int int8 int16 int32 int64 rune string
	uint uint8 uint16 uint32 uint64 uintptr

Constants:
	true false iota

Zero value:
	nil

Functions:
	append cap close complex copy delete imag len
	make new panic print println real recover

匯出的識別碼

一個識別碼可能會被匯出,以允許從另一個封包存取它。如果滿足以下兩個條件,則識別碼會被匯出:

  1. 識別碼名稱的第一個字元是 Unicode 大寫字母(Unicode 類別 "Lu");以及
  2. 識別碼已在 封包區塊 中宣告,或它是 欄位名稱方法名稱

其他所有識別碼皆不會被匯出。

識別碼的唯一性

考量一組識別碼,如果識別碼與該組中的每個其他識別碼都不同,則稱該識別碼為唯一。如果兩個識別碼拼字不同,或者出現在不同的 封包 中且未被 匯出,則這兩個識別碼不同。否則,它們相同。

常數宣告

常數宣告會將一個識別碼清單(常數名稱)繫結至 常數運算式 清單中的值。識別碼的數量必須等於運算式的數量,而且左邊第 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索引,從 0 開始。它可協助建立一組相關的常數

const (
	c0 = iota  // c0 == 0
	c1 = iota  // c1 == 1
	c2 = iota  // c2 == 2
)

const (
	a = 1 << iota  // a == 1  (iota == 0)
	b = 1 << iota  // b == 2  (iota == 1)
	c = 3          // c == 3  (iota == 2, unused)
	d = 1 << iota  // d == 8  (iota == 3)
)

const (
	u         = iota * 42  // u == 0     (untyped integer constant)
	v float64 = iota * 42  // v == 42.0  (float64 constant)
	w         = iota * 42  // w == 84    (untyped integer constant)
)

const x = iota  // x == 0
const y = iota  // y == 0

根據定義,在同一個ConstSpec中多次使用iota都會產生相同的值

const (
	bit0, mask0 = 1 << iota, 1<<iota - 1  // bit0 == 1, mask0 == 0  (iota == 0)
	bit1, mask1                           // bit1 == 2, mask1 == 1  (iota == 1)
	_, _                                  //                        (iota == 2, unused)
	bit3, mask3                           // bit3 == 8, mask3 == 7  (iota == 3)
)

最後一個範例運用在隱含重複中的最後一個非空白表達式清單。

類型宣告

類型宣告會結合識別標示(類別名稱)和類型。類型宣告包含兩種形式:別名宣告和類型定義。

TypeDecl = "type" ( TypeSpec | "(" { TypeSpec ";" } ")" ) .
TypeSpec = AliasDecl | TypeDef .

別名宣告

別名宣告會結合識別標示和指定的類型。

AliasDecl = identifier "=" Type .

在識別標示的範圍當中,該識別標示擔任類型的別名

type (
	nodeList = []*Node  // nodeList and []*Node are identical types
	Polar    = polar    // Polar and polar denote identical types
)

類型定義

類型定義會建立一個新的、不同的類型,其具有相同的底層類型和運算,如同指定的類型,並結合識別標示和該類型。

TypeDef = identifier Type .

新類型稱為已定義類型。其與任何其他類型不同,包含其建立時採用的類型。

type (
	Point struct{ x, y float64 }  // Point and struct{ x, y float64 } are different types
	polar Point                   // polar and Point denote different types
)

type TreeNode struct {
	left, right *TreeNode
	value *Comparable
}

type Block interface {
	BlockSize() int
	Encrypt(src, dst []byte)
	Decrypt(src, dst []byte)
}

已定義的類型可能會有方法與其關聯。它不會繼承任何與指定類型結合的方法,但介面類型或複合類型元素的方法組仍維持不變

// A Mutex is a data type with two methods, Lock and Unlock.
type Mutex struct         { /* Mutex fields */ }
func (m *Mutex) Lock()    { /* Lock implementation */ }
func (m *Mutex) Unlock()  { /* Unlock implementation */ }

// NewMutex has the same composition as Mutex but its method set is empty.
type NewMutex Mutex

// The method set of PtrMutex's underlying type *Mutex remains unchanged,
// but the method set of PtrMutex is empty.
type PtrMutex *Mutex

// The method set of *PrintableMutex contains the methods
// Lock and Unlock bound to its embedded field Mutex.
type PrintableMutex struct {
	Mutex
}

// MyBlock is an interface type that has the same method set as Block.
type MyBlock Block

類型定義可協助定義不同布林、數值或字串類型,並與其關聯的方法

type TimeZone int

const (
	EST TimeZone = -(5 + iota)
	CST
	MST
	PST
)

func (tz TimeZone) String() string {
	return fmt.Sprintf("GMT%+dh", tz)
}

變數宣告

變數宣告會建立一個或多個變數,並將對應的識別標示與其結合,同時為每個變數設定類型和初始值。

VarDecl     = "var" ( VarSpec | "(" { VarSpec ";" } ")" ) .
VarSpec     = IdentifierList ( Type [ "=" ExpressionList ] | "=" ExpressionList ) .
var i int
var U, V, W float64
var k = 0
var x, y float32 = -1, -2
var (
	i       int
	u, v, s = 2.0, 3.0, "bar"
)
var re, im = complexSqrt(-1)
var _, found = entries[name]  // map lookup; only interested in "found"

如果給定表達式列表,會使用 指派 的規則初始化變數。否則,每個變數都會初始化為其 零值

如果存在類型,會將每個變數指定該類型。否則,會將每個變數指定為在指派中相應初始化值的類型。如果該值為未設定類型的常數,則會先隱含地將其 轉換 為其 預設類型;如果為未設定類型的布林值,則會先隱含地將其轉換為類型 bool。無法使用預先宣告的值 nil 來初始化沒有明確設定類型的變數。

var d = math.Sin(0.5)  // d is float64
var i = 42             // i is int
var t, ok = x.(T)      // t is T, ok is bool
var n = nil            // illegal

實作限制:編譯器可能會禁止在 函數主體 中宣告一個變數,只要該變數從未使用過。

簡短變數宣告

簡短變數宣告 使用語法

ShortVarDecl = IdentifierList ":=" ExpressionList .

它是一個有初始值表達式,但沒有類型的正規 變數宣告 的簡寫

"var" IdentifierList = ExpressionList .
i, j := 0, 10
f := func() int { return 7 }
ch := make(chan int)
r, w, _ := os.Pipe()  // os.Pipe() returns a connected pair of Files and an error, if any
_, y, _ := coord(p)   // coord() returns three values; only interested in y coordinate

與正規變數宣告不同,簡短變數宣告可能會重新宣告變數,只要它們最初是在同一個區段中較早宣告的(或在區段是函數主體時宣告在參數清單中),且類型相同,且至少有一個非 空白 變數是新的。因此,重新宣告只能出現在多個變數的簡短宣告中。重新宣告不會引入新的變數;它只是將新的值指派給原始值。

field1, offset := nextField(str, 0)
field2, offset := nextField(str, offset)  // redeclares offset
a, a := 1, 2                              // illegal: double declaration of a or no new variable if a was declared elsewhere

簡短變數宣告可能僅出現在函數中。在一些內容中,例如 "if""for""switch" 陳述式的初始值,它們可用于宣告區域暫時變數。

函數宣告

函數宣告會將識別碼,函數名稱繫結到函數。

FunctionDecl = "func" FunctionName Signature [ FunctionBody ] .
FunctionName = identifier .
FunctionBody = Block .

如果函數的 簽章宣告結果參數,函數主體的陳述式清單必須結束於 終止陳述式

func IndexRune(s string, r rune) int {
	for i, c := range s {
		if c == r {
			return i
		}
	}
	// invalid: missing return statement
}

函數宣告可能會省略主體。此類宣告提供 Go 外部實作函數的簽章,例如組合例程。

func min(x int, y int) int {
	if x < y {
		return x
	}
	return y
}

func flushICache(begin, end uintptr)  // implemented externally

方法宣告

方法是具有接收端函數。方法宣告會將識別碼,方法名稱繫結到方法,並將該方法與接收端的基本類型關聯起來。

MethodDecl = "func" Receiver MethodName Signature [ FunctionBody ] .
Receiver   = Parameters .

接收方經由一個額外參數區塊指定,區塊位於方法名稱之前。此參數區塊必須宣告單一非可變動參數,即接收方。其型別必須是定義的型別T或指向定義型別T的指標。T稱為接收方基礎型別。接收方基礎型別不能為指標或介面型別,且必須定義在與方法相同套件中。方法稱為繫結於其接收方基礎型別,而方法名稱僅在型別T*T選擇器中可見。

空白接收方識別碼必須在方法簽署中唯一。如果接收方值未在方法內文中被參考,其識別碼可省略宣告,函數和方法的參數也適用此原則。

對於基礎型別,繫結於其的非空白方法名稱必須唯一。如果基礎型別是結構型別,則非空白方法和欄位名稱必須不同。

給定定義型別Point,宣告

func (p *Point) Length() float64 {
	return math.Sqrt(p.x * p.x + p.y * p.y)
}

func (p *Point) Scale(factor float64) {
	p.x *= factor
	p.y *= factor
}

將方法LengthScale繫結至基礎型別Point,而接收方型別為*Point

方法的型別是具有接收方為第一個參數的函數型別。例如,方法Scale具有型別

func(p *Point, factor float64)

然而,以此方式宣告的函數並非方法。

運算式

運算式透過對運算項套用操作員和函數,指定值的運算。

運算項

運算項表示運算式中的基本值。運算項可能是文字、非空白識別碼(可能經過限定),表示常數變數函數,或括號中的運算式。

空白識別碼只能作為賦值左側的運算項。

Operand     = Literal | OperandName | "(" Expression ")" .
Literal     = BasicLit | CompositeLit | FunctionLit .
BasicLit    = int_lit | float_lit | imaginary_lit | rune_lit | string_lit .
OperandName = identifier | QualifiedIdent .

限定識別碼

限定識別碼是已限定套件名稱字首的識別碼。套件名稱與識別碼都不得為空白

QualifiedIdent = PackageName "." identifier .

限定識別碼存取不同套件中的識別碼,而該套件必須被引入。識別碼必須外銷,並在該套件的套件區塊中宣告。

math.Sin	// denotes the Sin function in package math

複合文字

複合文字建構結構、陣列、切片和映射的值,而且每次評估都建立一個新值。它們包含文字型別,其後接一個以大括號為界的元素清單。每個元素都可以由對應的鍵值選用地先行。

CompositeLit  = LiteralType LiteralValue .
LiteralType   = StructType | ArrayType | "[" "..." "]" ElementType |
                SliceType | MapType | TypeName .
LiteralValue  = "{" [ ElementList [ "," ] ] "}" .
ElementList   = KeyedElement { "," KeyedElement } .
KeyedElement  = [ Key ":" ] Element .
Key           = FieldName | Expression | LiteralValue .
FieldName     = identifier .
Element       = Expression | LiteralValue .

LiteralType 的基礎類型必須是結構、陣列、切片或映射類型(語法會在類型提供為 TypeName 時放棄此限制)。元素和金鑰的類型必須可以 指定 給文字類型的個別欄位、元素和金鑰類型;不會有額外的轉換。金鑰會解譯為結構文字的欄位名稱、陣列和切片文字的索引,以及映射文字的金鑰。對於映射文字,所有元素都必須有金鑰。指定多個具有相同欄位名稱或常數金鑰值的元素是錯誤的。有關非常數映射金鑰,請參閱 評估順序 區段。

下列規則適用於結構文字

給定以下宣告

type Point3D struct { x, y, z float64 }
type Line struct { p, q Point3D }

可以寫入

origin := Point3D{}                            // zero value for Point3D
line := Line{origin, Point3D{y: -4, z: 12.3}}  // zero value for line.q.x

下列規則適用於陣列和切片文字

對複合文字取記憶體位址 會產生一指標,指向一已初始化且具有文字值之 變數

var pointer *Point3D = &Point3D{y: 1000}

請注意,切片或映射類型的 零值 與已初始化但為空值的同類型值不同。因此,取空切片或空映射複合文字的記憶體位址,與使用 new 來配置新的切片或映射值的效果不同。

p1 := &[]int{}    // p1 points to an initialized, empty slice with value []int{} and length 0
p2 := new([]int)  // p2 points to an uninitialized slice with value nil and length 0

陣列文字的長度是文字類型中指定的長度。如果在文字中提供的元素數量少於長度,遺失的元素會設定為陣列元素類型的零值。提供索引值超出陣列索引範圍的元素是錯誤的。符號 ... 指定的陣列長度等於最大元素索引加一。

buffer := [10]string{}             // len(buffer) == 10
intSet := [6]int{1, 2, 3, 5}       // len(intSet) == 6
days := [...]string{"Sat", "Sun"}  // len(days) == 2

切片文字描述整個基礎陣列文字。因此切片文字的長度和容量是最大元素索引加一。切片文字的形式為

[]T{x1, x2, … xn}

且為應用於陣列的切片運算的縮寫

tmp := [n]T{x1, x2, … xn}
tmp[0 : n]

在陣列、切片或映射類型 T 的複合字面量中,元素或映射鍵本身要是複合字面量,如果它與 T 的元素或鍵類型相同,則它可以省略各自的字面量類型。類似地,如果元素或鍵類型為 *T,那麼元素或鍵為複合字面量的位址可以省略 &T

[...]Point{{1.5, -3.5}, {0, 0}}     // same as [...]Point{Point{1.5, -3.5}, Point{0, 0}}
[][]int{{1, 2, 3}, {4, 5}}          // same as [][]int{[]int{1, 2, 3}, []int{4, 5}}
[][]Point{{{0, 1}, {1, 2}}}         // same as [][]Point{[]Point{Point{0, 1}, Point{1, 2}}}
map[string]Point{"orig": {0, 0}}    // same as map[string]Point{"orig": Point{0, 0}}
map[Point]string{{0, 0}: "orig"}    // same as map[Point]string{Point{0, 0}: "orig"}

type PPoint *Point
[2]*Point{{1.5, -3.5}, {}}          // same as [2]*Point{&Point{1.5, -3.5}, &Point{}}
[2]PPoint{{1.5, -3.5}, {}}          // same as [2]PPoint{PPoint(&Point{1.5, -3.5}), PPoint(&Point{})}

當使用字面量類型的 TypeName 形式的複合字面量在「if」、「for」或「switch」語句的關鍵字與起始大括號之間作為一個運算元出現時,且複合字面量沒有包含在圓括號、方括號或大括號中,那麼會引起語法歧義。在這種罕見情況下,字面量的起始大括號會錯誤地被解析為引入語句塊的大括號。要解決歧義,複合字面量必須出現在圓括號中。

if x == (T{a,b,c}[i]) { … }
if (x == T{a,b,c}[i]) { … }

有效的陣列、切片與映射字面量的範例

// list of prime numbers
primes := []int{2, 3, 5, 7, 9, 2147483647}

// vowels[ch] is true if ch is a vowel
vowels := [128]bool{'a': true, 'e': true, 'i': true, 'o': true, 'u': true, 'y': true}

// the array [10]float32{-1, 0, 0, 0, -0.1, -0.1, 0, 0, 0, -1}
filter := [10]float32{-1, 4: -0.1, -0.1, 9: -1}

// frequencies in Hz for equal-tempered scale (A4 = 440Hz)
noteFrequency := map[string]float32{
	"C0": 16.35, "D0": 18.35, "E0": 20.60, "F0": 21.83,
	"G0": 24.50, "A0": 27.50, "B0": 30.87,
}

函數字面量

函數字面量表示匿名 函數

FunctionLit = "func" Signature FunctionBody .
func(a, b int, z float64) bool { return a*b < int(z) }

函數字面量可以指定予變數或直接呼叫。

f := func(x, y int) int { return x + y }
func(ch chan int) { ch <- ACK }(replyChan)

函數字面量是 閉包:它們可能會參考在周圍函數中定義的變數。然後這些變數會在周圍函數和函數字面量之間共用,並且只要它們可以存取就會存在。

主要表達式

主要表達式是一元和二元表達式的運算元。

PrimaryExpr =
	Operand |
	Conversion |
	MethodExpr |
	PrimaryExpr Selector |
	PrimaryExpr Index |
	PrimaryExpr Slice |
	PrimaryExpr TypeAssertion |
	PrimaryExpr Arguments .

Selector       = "." identifier .
Index          = "[" Expression "]" .
Slice          = "[" [ Expression ] ":" [ Expression ] "]" |
                 "[" [ Expression ] ":" Expression ":" Expression "]" .
TypeAssertion  = "." "(" Type ")" .
Arguments      = "(" [ ( ExpressionList | Type [ "," ExpressionList ] ) [ "..." ] [ "," ] ] ")" .
x
2
(s + ".txt")
f(3.1415, true)
Point{1, 2}
m["foo"]
s[i : j + 1]
obj.color
f.p[i].x()

選擇器

對於主要表達式 x,它不是 套件名稱,選擇器表達式

x.f

表示值 x (或有時 *x;請見下文) 的欄位或方法 f。識別碼 f 稱為 (欄位或方法) 選擇器;它不能是 空白識別碼。選擇器表達式的類型為 f 的類型。如果 x 是套件名稱,請參閱 識別碼 一節。

選擇器 f 可能表示類型 T 的欄位或方法 f,或可能參考 T 的巢狀 嵌入式欄位 中的欄位或方法 f。遍歷以到達 f 的嵌入式欄位數稱作它在 T 中的 深度。在 T 中宣告的欄位或方法 f 深度為零。在 T 中的嵌入式欄位 A 中宣告的欄位或方法 f 深度為 fA 中的深度加一。

下列規則適用於選擇器

  1. 對於類型為 T*T 的值 x,其中 T 不是指標或介面類型,x.f 表示在 T 中最淺深度包含此類 f 的欄位或方法。假設深度最淺的 f 並非 唯一個 f,選擇器表達式違法。
  2. 對於類型為 I 的值 x,其中 I 為介面類型,x.f 表示 of x 的動態值的 f 名稱的實際方法。I方法集 中如果沒有名稱為 f 的方法,選擇器表達式違法。
  3. 若作為例外,假設 x 的類型是 已定義 的指標類型,且 (*x).f 是表示欄位(但不是方法)的有效選擇器表達式,x.f(*x).f 的縮寫。
  4. 在所有其他情況下,x.f 違法。
  5. 假設 x 屬於指標類型,且值為 nil,且 x.f 表示結構體欄位,指定或評估 x.f 會導致 執行時間恐慌
  6. 假設 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,其中包含兩個方法:

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:接收者屬於類型 TMv,以及接收者屬於類型 *TMp

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 不是映射

對於 陣列類型 Aa

對於陣列類型的 指標 a

對於 區塊類型 Sa

對於 字串類型 a

對於 字典類型 Ma

否則 a[x] 是非法的。

在特殊形式的 指派 或初始化中使用的類型為 map[K]V 的字典 a 的索引值表示式

v, ok = a[x]
v, ok := a[x]
var v, ok = a[x]

會產生額外的無類型布林值。如果關鍵字 x 存在於字典中,ok 的值為 true,否則為 false

指派給 nil 字典的元素會導致 執行時期驚慌

區塊表示式

區塊表示式由字串、陣列、陣列指標或區塊建置子字串或區塊。有兩個變體:一個指定下限和上限的簡單形式,以及一個也指定容量上限的完整形式。

簡單區塊表示式

對於字串、陣列、陣列指標或區塊 a,主要表示式

a[low : high]

建置子字串或區塊。索引值 lowhigh 選取操作數 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 索引值預設為 0;遺失的 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

完整分割表達式

對於陣列 a、指向陣列的指標或分割資料 (但不包含字串),主要運算式

a[low : high : max]

建構與簡單分割表達式 a[low : high] 同類型的分割資料,且長度和元素也相同。此外,還藉由將其設定為 max - low 來控制產生的分割資料的容量。只能忽略第一個索引;其預設值為 0。分割 a 陣列後

a := [5]int{1, 2, 3, 4, 5}
t := a[1:3:5]

分割 t 的類型為 []int、長度為 2、容量為 4、元素為

t[0] == 2
t[1] == 3

至於簡單分割表達式,如果 a 指向陣列, 則 a[low : high : max] 可簡寫為 (*a)[low : high : max]。如果分割的運算元為陣列,其必須具備 可處理 的性質。

如果 0 <= low <= high <= max <= cap(a),則索引在範圍內,否則則在範圍外。必須可以使用 常數 索引,且其必須是非負值,且可以使用 int 類型值表示;對於陣列,常數索引也必須在範圍內。如果有多個常數索引,出現的常數必須彼此間相對在範圍內。如果索引在執行期間超出範圍,就會發生執行時期的恐慌

類型斷言

對於 介面類型 的運算式 x 以及類型 T,主要運算式

x.(T)

斷言 x 不為 nil,且儲存在 x 中的值為類型 T。表示法 x.(T) 稱為類型斷言

更準確地說,如果 T 不是一個介面類型,x.(T) 斷言動態類型的 x 相同於類型 T。在此情況下,T 必須 實作 (interface) 型態的 x;否則型態斷言將無效,因為 x 不可能儲存類型 T 的值。如果 T 是介面類型,x.(T) 斷言動態類型的 x 實作介面 T

如果型態斷言成立,表達式的值是儲存在 x 的值,其型態是 T。如果類型斷言為假,則會發生 執行階段恐慌。換句話說,即使動態類型的 x 僅在執行階段才知道,但在正確的程式中,x.(T) 的類型已知是 T

var x interface{} = 7          // x has dynamic type int and value 7
i := x.(int)                   // i has type int and value 7

type I interface { m() }

func f(y I) {
	s := y.(string)        // illegal: string does not implement I (missing method m)
	r := y.(io.Reader)     // r has type io.Reader and the dynamic type of y must implement both I and io.Reader
	…
}

指定 或特殊形式初始化中使用的類型斷言

v, ok = x.(T)
v, ok := x.(T)
var v, ok = x.(T)
var v, ok interface{} = x.(T) // dynamic types of v and ok are T and bool

產生額外的未輸入布林值。如果斷言成立,ok 值為 true。否則為 false,並且 v 的值是類型 T零值。在此情況下,不會發生 執行階段恐慌

呼叫

如果函式類型 F 的表達式為 f

f(a1, a2, … an)

使用參數 a1, a2, … an 呼叫 f。除了特殊情況外,參數必須是單值表達式 可指定 至函式的參數類型,並在呼叫函式之前經過評估。表達式的類型是函式的結果類型。方法呼叫類似,但方法本身指定為接收器類型的方法上的選取器。

math.Atan2(x, y)  // function call
var pt *Point
pt.Scale(3.5)     // method call with receiver pt

在函式呼叫中,函式值和參數按照 通常順序 評估。評估後,呼叫的參數會以值傳遞給函式,然後呼叫函式開始執行。函式返回後,函式的回傳參數會以值傳遞回呼叫方。

呼叫 nil 函式值會導致 執行階段恐慌

在特殊情況下,如果函式或方法 g 的傳回值數目相同,且分別可指定至另一函式或方法 f 的參數,則呼叫 f(g(g_參數)) 會依序繫結 g 的傳回值至 f 的參數後才呼叫 f。呼叫 f 時,除了呼叫 g 之外,不得包含其他參數,而 g 至少必須有一個傳回值。如果 f 有最後一個 ... 參數,則會將繫結給一般參數之後剩下的 g 傳回值繫結至該參數。

func Split(s string, pos int) (string, string) {
	return s[0:pos], s[pos:]
}

func Join(s, t string) string {
	return s + t
}

if Join(Split(value, len(value)/2)) != value {
	log.Panic("test fails")
}

如果 x (型別) 的方法集合中包含 m 且引數清單可指定至 m 的參數清單,則方法呼叫 x.m() 是有效的。如果 x 可尋址,且 &x 的方法集合包含 m,則 x.m()(&x).m() 的簡寫。

var p Point
p.Scale(3.5)

沒有獨立的方法型別,也沒有方法文字。

傳遞引數至 ... 參數

如果 f可變引數,其最後一個參數 p 的型別為 ...T,則在 f 中,p 的型別與型別 []T 相同。如果呼叫 f 時沒有提供 p 的實際引數,則傳遞給 p 的值為 nil。否則,傳遞的值會是型別為 []T 的新切片,其基礎陣列包含所有實際引數,且所有引數都必須可指定至 T。因此,切片的長度和容量會等於繫結至 p 的引數數目,且在每個呼叫位置都可能不同。

考慮以下函式與呼叫:

func Greeting(prefix string, who ...string)
Greeting("nobody")
Greeting("hello:", "Joe", "Anna", "Eileen")

Greeting 中,who 在第一次呼叫時會帶有值 nil,而在第二次呼叫時會帶有 ["Joe", "Anna", "Eileen"]

如果最後一個引數可指定至 []T 的切片型別,且後接 ...,則會原封不動地傳遞為 ...T 參數的值。在此情況下,不會建立新切片。

考慮以下切片與呼叫:

s := []string{"James", "Jasmine"}
Greeting("goodbye:", s...)

Greeting 中,who 會帶有與 s 相同的值和基礎陣列。

運算子

運算子會將運算元結合為運算式。

Expression = UnaryExpr | Expression binary_op Expression .
UnaryExpr  = PrimaryExpr | unary_op UnaryExpr .

binary_op  = "||" | "&&" | rel_op | add_op | mul_op .
rel_op     = "==" | "!=" | "<" | "<=" | ">" | ">=" .
add_op     = "+" | "-" | "|" | "^" .
mul_op     = "*" | "/" | "%" | "<<" | ">>" | "&" | "&^" .

unary_op   = "+" | "-" | "!" | "^" | "*" | "&" | "<-" .

有關比較運算子的討論,請參閱其他地方。對其他二元運算子而言,運算元型別必須相同,除非運算包括位移或未鍵入常數。關於僅涉及常數的運算,請參閱常數運算式的部分。

除了位移運算外,如果一個運算元是未鍵入常數而另一個運算元不是,則會將常數隱式轉換為另一個運算元的型別。

移位表達式中的右操作數必須為整數類型,或由類型為 uint 的值 可表示 的非類型常量。如果非常數移位表達式的左操作數為非類型常量,它會先隱式地轉換為移位表達式僅由其左操作數取代後的類型。

var a [1024]byte
var s uint = 33

// The results of the following examples are given for 64-bit ints.
var i = 1<<s                   // 1 has type int
var j int32 = 1<<s             // 1 has type int32; j == 0
var k = uint64(1<<s)           // 1 has type uint64; k == 1<<33
var m int = 1.0<<s             // 1.0 has type int; m == 1<<33
var n = 1.0<<s == j            // 1.0 has type int32; n == true
var o = 1<<s == 2<<s           // 1 and 2 have type int; o == false
var p = 1<<s == 1<<33          // 1 has type int; p == true
var u = 1.0<<s                 // illegal: 1.0 has type float64, cannot shift
var u1 = 1.0<<s != 0           // illegal: 1.0 has type float64, cannot shift
var u2 = 1<<s != 1.0           // illegal: 1 has type float64, cannot shift
var v float32 = 1<<s           // illegal: 1 has type float32, cannot shift
var w int64 = 1.0<<33          // 1.0<<33 is a constant shift expression; w == 1<<33
var x = a[1.0<<s]              // panics: 1.0 has type int, but 1<<33 overflows array bounds
var b = make([]byte, 1.0<<s)   // 1.0 has type int; len(b) == 1<<33

// The results of the following examples are given for 32-bit ints,
// which means the shifts will overflow.
var mm int = 1.0<<s            // 1.0 has type int; mm == 0
var oo = 1<<s == 2<<s          // 1 and 2 have type int; oo == true
var pp = 1<<s == 1<<33         // illegal: 1 has type int, but 1<<33 overflows int
var xx = a[1.0<<s]             // 1.0 has type int; xx == a[0]
var bb = make([]byte, 1.0<<s)  // 1.0 has type int; len(bb) == 0

運算子優先順位

單元運算子有最高的優先順位。由於 ++-- 運算子形成陳述式,而非表達式,它們不屬於運算子階層。因此,陳述式 *p++(*p)++ 相同。

二元運算子有五個優先順位層級。乘法運算子結合力最強,其次為加法運算子、比較運算子、&&(邏輯 AND),最後為 ||(邏輯 OR)。

Precedence    Operator
    5             *  /  %  <<  >>  &  &^
    4             +  -  |  ^
    3             ==  !=  <  <=  >  >=
    2             &&
    1             ||

優先順序相同的二元運算子從左到右結合。例如,x / y * z(x / y) * z 相同。

+x
23 + 3*x[i]
x <= f()
^a >> b
f() || g()
x == y+1 && <-chanInt > 0

算術運算子

算術運算子會作用於數值,並產出與第一個操作數相同類型的結果。四個標準的算術運算子(+-*/)會作用於整數、浮點數和複數類型;+ 也可以作用於字串。按位元邏輯和移位運算子只會作用於整數。

+    sum                    integers, floats, complex values, strings
-    difference             integers, floats, complex values
*    product                integers, floats, complex values
/    quotient               integers, floats, complex values
%    remainder              integers

&    bitwise AND            integers
|    bitwise OR             integers
^    bitwise XOR            integers
&^   bit clear (AND NOT)    integers

<<   left shift             integer << integer >= 0
>>   right shift            integer >> integer >= 0

整數運算子

針對兩個整數值 xy,整數商 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

這個規則只有一個例外,如果被除數 xx 的 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 次一樣。因此,x << 1x*2 相同,而 x >> 1x/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 永遠為真。

浮點運算子

對於浮點數和複數+xx 相同,而 -xx 的負值。浮點數或複數除以零的結果未於 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

在任何比較中,第一個運算元必須可指派為第二個運算元的類型,反之亦然。

相等運算子 ==!= 套用於可比較的運算元。排序運算子 <<=>>= 套用於可排序的運算元。這些術語和比較結果定義如下

如果具有相同動態類型的兩個介面值的比較,且該類型的值不可比較,則會造成執行階段恐慌。此行為不僅會套用於介面值比較,還會套用於介面值陣列或具有介面值欄位的結構比較。

區段、對應和函式值不可比較。不過,區段、對應或函式值可以與預先宣告識別碼 nil 相比較,作為一個特例。允許指標,通道和介面值與 nil 相比較,也遵循上述一般規則。

const c = 3 < 4            // c is the untyped boolean constant true

type MyBool bool
var x, y int
var (
	// The result of a comparison is an untyped boolean.
	// The usual assignment rules apply.
	b3        = x == y // b3 has type bool
	b4 bool   = x == y // b4 has type bool
	b5 MyBool = x == y // b5 has type MyBool
)

邏輯運算子

邏輯運算子套用於布林值,並產生一結果,其類型和運算元相同。會條件性地評估右運算元。

&&    conditional AND    p && q  is  "if p then q else false"
||    conditional OR     p || q  is  "if p then true else q"
!     NOT                !p      is  "not p"

位址運算子

對於一個類型為 T 的操作數 x,地址操作 &x 會產生指向 x、類型為 *T 的指標。操作數必須是 可尋址的,亦即變數、指標間接或切片索引操作;或者是一個可尋址結構化操作數的欄位選擇器;或者是一個可尋址陣列的陣列索引操作。作為可尋址性條件的一個例外,x 也可能是一個(可能有括弧)複合文字。如果對 x 的評估會造成一個< a href="#Run_time_panics">執行期間錯誤,那麼對 &x 的評估也會造成錯誤。

對於一個指標類型為 *T 的操作數 x,指標間接 *x 表示由 x 指向、類型為 T變數。如果 xnil,則嘗試評估 *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

一個非常數值 x 可以轉換成下列狀況的型別 T

結構標籤 在比較轉換的結構型別識別時會被忽略。

type Person struct {
	Name    string
	Address *struct {
		Street string
		City   string
	}
}

var data *struct {
	Name    string `json:"name"`
	Address *struct {
		Street string `json:"street"`
		City   string `json:"city"`
	} `json:"address"`
}

var person = (*Person)(data)  // ignoring tags, the underlying types are identical

特定的規則套用在數字型別或字串型別之間的轉換(非常數)。這些轉換可能會變更 x 的表示,並造成執行時間成本。所有其他轉換只會變更型別,而不變更 x 的表示。

語言機制中沒有指標和整數之間的轉換。unsafe 套件在受限的狀況下實作這項功能。

數字型別之間的轉換

對於非常數數字值的轉換,套用下列規則:

  1. 若在整數型別之間轉換時,如果值為有號整數,它會擴充符號至隱含無限精度;否則它會擴充為零。然後會自動截斷以符合結果型別的大小。例如,如果 v := uint16(0x10F0),則 uint32(int8(v)) == 0xFFFFFFF0。轉換始終會產生有效的值;沒有溢出的跡象。
  2. 當將浮點數轉換成整數時,會捨棄分數(朝向零截斷)。
  3. 將整數或浮點數值轉換成浮點數類型,或將複數轉換成另一種複數類型時,結果值會根據目標類型的精度進行捨入。例如,類型為 float32 變數 x 的值可能儲存的精度超出 IEEE 754 32 位元數字,但 float32(x) 代表將 x 的值捨入為 32 位元精度。類似地,x + 0.1 可能會使用超過 32 位元精度的數值,但 float32(x + 0.1) 卻不會。

在所有涉及浮點數或複數值的非常數轉換中,如果結果類型無法表示該值,轉換會成功,但結果值取決於實作方式。

轉換至字串類型與從字串類型轉換

  1. 將有號或無號整數值轉換成字串類型,會產生一個包含該整數的 UTF-8 表示形式的字串。超出有效 Unicode 編碼點範圍的值會轉換成 "\uFFFD"
    string('a')       // "a"
    string(-1)        // "\ufffd" == "\xef\xbf\xbd"
    string(0xf8)      // "\u00f8" == "ø" == "\xc3\xb8"
    type MyString string
    MyString(0x65e5)  // "\u65e5" == "日" == "\xe6\x97\xa5"
    
  2. 將一組位元組轉換成字串類型,會產生一個字串,其相繼的位元組為該組中的元素。
    string([]byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'})   // "hellø"
    string([]byte{})                                     // ""
    string([]byte(nil))                                  // ""
    
    type MyBytes []byte
    string(MyBytes{'h', 'e', 'l', 'l', '\xc3', '\xb8'})  // "hellø"
    
  3. 將一組符文轉換成字串類型,會產生一個字串,其中包含已轉換成字串的個別符文值。
    string([]rune{0x767d, 0x9d6c, 0x7fd4})   // "\u767d\u9d6c\u7fd4" == "白鵬翔"
    string([]rune{})                         // ""
    string([]rune(nil))                      // ""
    
    type MyRunes []rune
    string(MyRunes{0x767d, 0x9d6c, 0x7fd4})  // "\u767d\u9d6c\u7fd4" == "白鵬翔"
    
  4. 將字串類型值轉換成位元組類型組,會產生一個組,其相繼的元素為該字串的位元組。
    []byte("hellø")   // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}
    []byte("")        // []byte{}
    
    MyBytes("hellø")  // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}
    
  5. 將字串類型值轉換成符文類型組,會產生一個組,其中包含該字串的個別 Unicode 編碼點。
    []rune(MyString("白鵬翔"))  // []rune{0x767d, 0x9d6c, 0x7fd4}
    []rune("")                 // []rune{}
    
    MyRunes("白鵬翔")           // []rune{0x767d, 0x9d6c, 0x7fd4}
    

從組轉換成陣列指標

將組轉換成陣列指標,會產生一個指標,指向該組的底層陣列。如果組的長度小於陣列的長度,將會發生執行時期恐慌

s := make([]byte, 2, 4)
s0 := (*[0]byte)(s)      // s0 != nil
s1 := (*[1]byte)(s[1:])  // &s1[0] == &s[1]
s2 := (*[2]byte)(s)      // &s2[0] == &s[0]
s4 := (*[4]byte)(s)      // panics: len([4]byte) > len(s)

var t []string
t0 := (*[0]string)(t)    // t0 == nil
t1 := (*[1]string)(t)    // panics: len([1]string) > len(t)

u := make([]byte, 0)
u0 := (*[0]byte)(u)      // u0 != nil

常數式

常數式只能包含常數運算元,並且在編譯時期進行評估。

未輸入類型的布林、數字和字串常數可以用在允許使用布林、數字或字串類型運算元的範疇。

一個常數比較會產生一個未輸入類型的布林常數。如果一個常數位移式的左運算元是一個未輸入類型的常數,結果會是一個整數常數;否則,結果會是一個與左運算元具有相同類型的常數,且必須為整數類型

未輸入任何其他操作針對未輸入常數會導致同一種類型的未輸入常數;也就是,布林、整數、浮點、複雜或字串常數。如果二元操作(不含位移)的未輸入運算元具有不同的類型,則結果為此清單中較後出現的運算元類型:整數、符號、浮點、複雜。例如,未輸入整數常數除以未輸入複雜常數會產生未輸入複雜常數。

const a = 2 + 3.0          // a == 5.0   (untyped floating-point constant)
const b = 15 / 4           // b == 3     (untyped integer constant)
const c = 15 / 4.0         // c == 3.75  (untyped floating-point constant)
const Θ float64 = 3/2      // Θ == 1.0   (type float64, 3/2 is integer division)
const Π float64 = 3/2.     // Π == 1.5   (type float64, 3/2. is float division)
const d = 1 << 3.0         // d == 8     (untyped integer constant)
const e = 1.0 << 3         // e == 8     (untyped integer constant)
const f = int32(1) << 33   // illegal    (constant 8589934592 overflows int32)
const g = float64(2) >> 1  // illegal    (float64(2) is a typed floating-point constant)
const h = "foo" > "bar"    // h == true  (untyped boolean constant)
const j = true             // j == true  (untyped boolean constant)
const k = 'w' + 1          // k == 'x'   (untyped rune constant)
const l = "hi"             // l == "hi"  (untyped string constant)
const m = string(k)        // m == "x"   (type string)
const Σ = 1 - 0.707i       //            (untyped complex constant)
const Δ = Σ + 2.0e-4       //            (untyped complex constant)
const Φ = iota*1i - 1/1i   //            (untyped complex constant)

將內建函數 complex 套用至未輸入的整數、符號或浮點常數會產生未輸入複雜常數。

const ic = complex(0, c)   // ic == 3.75i  (untyped complex constant)
const iΘ = complex(0, Θ)   // iΘ == 1i     (type complex128)

常數表達式總是會精確評估;中間值和常數本身可能需要精度上遠比語言中任何預先宣告的類型所支援的高。以下為合法的宣告

const Huge = 1 << 100         // Huge == 1267650600228229401496703205376  (untyped integer constant)
const Four int8 = Huge >> 98  // Four == 4                                (type int8)

常數除法或餘數運算的除數不可為零

3.14 / 0.0   // illegal: division by zero

已輸入 常數的值總是必須能夠準確地表示常數類型的值。以下常數表達式是非法的

uint(-1)     // -1 cannot be represented as a uint
int(3.14)    // 3.14 cannot be represented as an int
int64(Huge)  // 1267650600228229401496703205376 cannot be represented as an int64
Four * 300   // operand 300 cannot be represented as an int8 (type of Four)
Four * 100   // product 400 cannot be represented as an int8 (type of Four)

單元位元運算元 ^ 所使用的遮罩符合非常數的規則:對於未簽名的常數,遮罩為全 1,而對於有簽名的和未輸入常數,遮罩則為 -1。

^1         // untyped integer constant, equal to -2
uint8(^1)  // illegal: same as uint8(-2), -2 cannot be represented as a uint8
^uint8(1)  // typed uint8 constant, same as 0xFF ^ uint8(1) = uint8(0xFE)
int8(^1)   // same as int8(-2)
^int8(1)   // same as -1 ^ int8(1) = -2

實作限制:編譯器在計算未輸入浮點或複雜常數表達式時可能會使用捨入;請參閱 常數 區段中的實作限制。此捨入可能會導致浮點常數表達式在整數情況下無效,即使使用無限精度計算時為整數,反之亦然。

計算順序

在套件層級,初始化相依性 決定 變數宣告 中每個初始化表達式的計算順序。否則,在評估表達式、指定或 回傳陳述式運算元 時,所有函數呼叫、方法呼叫和通訊運算都按由左至右的詞彙順序評估。

例如,在(函數區域)指定中

y[f()], ok = g(h(), i()+x[j()], <-c), k()

函數呼叫和通訊以順序 f()h()i()j()<-cg()k() 發生。但是,與 x 的評估和索引以及 y 的評估相比,這些事件的順序未指定。

a := 1
f := func() int { a++; return a }
x := []int{a, f()}            // x may be [1, 2] or [2, 2]: evaluation order between a and f() is not specified
m := map[int]int{a: 1, a: 2}  // m may be {2: 1} or {2: 2}: evaluation order between the two map assignments is not specified
n := map[int]int{a: f()}      // n may be {2: 3} or {3: 3}: evaluation order between the key and the value is not specified

在套件層級,初始化相依性會覆寫由左至右的規則以針對個別初始化表達式適用,但對於每個表達式內的運算元則不會

var a, b, c = f() + v(), g(), sqr(u()) + v()

func f() int        { return c }
func g() int        { return a }
func sqr(x int) int { return x*x }

// functions u and v are independent of all other variables and functions

函數呼叫以順序 u()sqr()v()f()v()g() 發生。

單一表達式中的浮點運算會依照運算元的結合方式進行評估。明確的括號會透過覆寫預設結合方式來影響評估。在表達式 x + (y + z) 中,會在新增 x 之前執行加法 y + z

陳述式

陳述式會控制執行。

Statement =
	Declaration | LabeledStmt | SimpleStmt |
	GoStmt | ReturnStmt | BreakStmt | ContinueStmt | GotoStmt |
	FallthroughStmt | Block | IfStmt | SwitchStmt | SelectStmt | ForStmt |
	DeferStmt .

SimpleStmt = EmptyStmt | ExpressionStmt | SendStmt | IncDecStmt | Assignment | ShortVarDecl .

終止陳述式

終止敘述 會中斷 區塊 中的控制規則流程。下列敘述會終止

  1. "return""goto" 敘述。
  2. 呼叫內建函式 panic
  3. 區塊 中,敘述清單以終止敘述結尾。
  4. "if" 敘述,其
    • "else" 分支存在,且
    • 兩個分支都是終止敘述。
  5. "for" 敘述,其
    • 沒有任何 "break" 敘述會參照 "for" 敘述,且
    • 迴圈條件不存在,且
    • "for" 敘述沒有使用範圍子句。
  6. "switch" 敘述,其
    • 沒有任何 "break" 敘述會參照 "switch" 敘述,
    • 有一個預設情況,且
    • 每個情況中的敘述清單,包含預設情況,會以終止敘述結尾,或可能會標籤 "fallthrough" 敘述
  7. "select" 敘述,其
    • 沒有任何 "break" 敘述會參照 "select" 敘述,且
    • 每個情況中的敘述清單,包含預設情況(如果存在),會以終止敘述結尾。
  8. 標籤敘述 標籤終止敘述。

所有其他敘述不會終止。

敘述清單 會以終止敘述結尾,如果該清單不是空的,而且其最後一個非空敘述是會終止的。

空敘述

空敘述不會執行任何操作。

EmptyStmt = .

標籤敘述

標籤敘述可以是 gotobreakcontinue 敘述的目標。

LabeledStmt = Label ":" Statement .
Label       = identifier .
Error: log.Panic("error encountered")

表達式敘述

除了特定的內建函式外,函式和方法 呼叫接收操作 都可以在敘述內容中出現。此類敘述可以用括號括起來。

ExpressionStmt = Expression .

以下內建函式不允許在敘述內容中

append cap complex imag len make new real
unsafe.Add unsafe.Alignof unsafe.Offsetof unsafe.Sizeof unsafe.Slice
h(x+y)
f.Close()
<-ch
(<-ch)
len("foo")  // illegal if len is the built-in function

傳送敘述

傳送敘述會透過通道傳送值。通道表達式必須是 通道類型,通道方向必須允許傳送操作,且要傳送的值的類型必須可以 指定 給通道的元素類型。

SendStmt = Channel "<-" Expression .
Channel  = Expression .

頻道和值表達式都會在通訊開始前得到評估。通訊會封鎖,直至傳送可以進行。如果接收器準備就緒,則可以在非緩衝頻道上進行傳送。如果緩衝區中有空間,則可以在緩衝頻道上進行傳送。在封閉頻道上傳送會導致執行期恐慌。在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 == []int{3, 5, 3}

在指派中,每個值都必須 可指派 給它所指派的運算元的類型,並伴隨以下特殊情況

  1. 任何類型值都可以指派給空白識別碼。
  2. 如果未類型化的常數被指派給介面類型的變數或空白識別碼,則常數會先隱式 轉換 為它的 預設類型
  3. 如果未類型化的布林值被指派給介面類型的變數或空白識別碼,則會先隱式將它轉換為 bool 類型。

If 陳述式

"If" 陳述式會根據布林表達式的值來指定根據條件執行的兩個分支。如果布林表達式評估為 true,則會執行「if」分支,否則如果存在「else」分支,則會執行該分支。

IfStmt = "if" [ SimpleStmt ";" ] Expression Block [ "else" ( IfStmt | Block ) ] .
if x > max {
	x = max
}

表達式前面可能會有一個簡單陳述式,這個陳述式會在評估表達式之前執行。

if x := f(); x < y {
	return x
} else if x > z {
	return z
} else {
	return y
}

Switch 陳述式

"Switch" 陳述式提供多重執行方式。會將一個表達式或類型與 "switch" 中的 "cases" 進行比較,以決定要執行哪個分支。

SwitchStmt = ExprSwitchStmt | TypeSwitchStmt .

有兩種形式:表達式 switch 和類型 switch。在表達式 switch 中,case 含有表達式,用來與 switch 表達式的值相比較。在類型 switch 中,case 含有類型,用來與特殊註解 switch 表達式的類型相比較。switch 表達式會在 switch 陳述式中準確地評估一次。

表達式 switch

在表達式 switch 中,switch 表達式會被評估,而 case 表達式(不需要是常數)會由左至右、由上而下被評估;第一個與 switch 表達式相等的 case 表達式會觸發執行相關 case 的陳述式,其它的 cases 會被略過。如果沒有 case 相符且有一個 "default" case,則會執行它的陳述式。最多只可以有一個預設 case,而且它可以出現在 "switch" 陳述式的任何位置。缺失的 switch 表達式等於布林值 true

ExprSwitchStmt = "switch" [ SimpleStmt ";" ] [ Expression ] "{" { ExprCaseClause } "}" .
ExprCaseClause = ExprSwitchCase ":" StatementList .
ExprSwitchCase = "case" ExpressionList | "default" .

如果 switch 表達式評估為未類型化的常數,則會先隱式 轉換 為它的 預設類型。預先宣告的未類型化值 nil 不能用做 switch 表達式。switch 表達式類型必須 可比較的

若條件式沒有指定型別,會先隱性地 轉換 為 switch 式的型別。針對每個(可能已轉換)的條件式 `x` 和 switch 式的值 `t`,`x == t` 必須是有效的 比較運算

換句話說,將 switch 式視為用於宣告並初始化暫時變數 `t`,但沒有明確指定型別;針對此 `t` 的值,針對每一個條件式 `x` 測試是否相等。

在條件句或預設子句中,最後一個非空陳述式可以是(可能 標記 的) "穿透" 陳述式,以表示控制權應該從此子句的結尾流向下一個子句的第一個陳述式。否則,控制權會流向 "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()
}

實作限制:編譯器可能會禁止多個評估為相同常數的條件式。例如,目前的編譯器不允許在條件式中重複整數、浮點數或字串常數。

型別 switch

型別 switch 比較型別,而非值。它在其他方面與表達式 switch 類似。它是透過特別的 switch 式標示的,此 switch 式的外觀形式是用關鍵字 `type`(而非實際型別)進行 型別斷言

switch x.(type) {
// cases
}

條件式接著會比對實際型別 `T` 與表達式 `x` 的動態型別。如同型別斷言,`x` 必需具有 介面型別,而且在條件式中列出的每個非介面型別 `T` 都必須為 `x` 的型別實作。在型別 switch 的條件式中列出的型別都必須 不同

TypeSwitchStmt  = "switch" [ SimpleStmt ";" ] TypeSwitchGuard "{" { TypeCaseClause } "}" .
TypeSwitchGuard = [ identifier ":=" ] PrimaryExpr "." "(" "type" ")" .
TypeCaseClause  = TypeSwitchCase ":" StatementList .
TypeSwitchCase  = "case" TypeList | "default" .
TypeList        = Type { "," Type } .

TypeSwitchGuard 可以在 短變數宣告中包含。在使用該形式時,變數會在 TypeSwitchCase 的最後、在每個子句的 隱性區塊 中宣告。在條件式中僅列出一個型別的子句中,變數具有該型別;否則,變數具有 TypeSwitchGuard 中表達式的型別。

條件式可以使用預先宣告的識別碼 nil,而不使用型別;當 TypeSwitchGuard 中的表達式為 `nil` 介面值時,就會選擇此條件式。最多只有一個 `nil` 條件式。

假設有型別為 `interface{}` 的表達式 `x`,下方的型別 switch

switch i := x.(type) {
case nil:
	printString("x is nil")                // type of i is type of x (interface{})
case int:
	printInt(i)                            // type of i is int
case float64:
	printFloat64(i)                        // type of i is float64
case func(int) float64:
	printFunction(i)                       // type of i is func(int) float64
case bool, string:
	printString("type is bool or string")  // type of i is type of x (interface{})
default:
	printString("don't know the type")     // type of i is type of x (interface{})
}

可以改寫為

v := x  // x is evaluated exactly once
if v == nil {
	i := v                                 // type of i is type of x (interface{})
	printString("x is nil")
} else if i, isInt := v.(int); isInt {
	printInt(i)                            // type of i is int
} else if i, isFloat64 := v.(float64); isFloat64 {
	printFloat64(i)                        // type of i is float64
} else if i, isFunc := v.(func(int) float64); isFunc {
	printFunction(i)                       // type of i is func(int) float64
} else {
	_, isBool := v.(bool)
	_, isString := v.(string)
	if isBool || isString {
		i := v                         // type of i is type of x (interface{})
		printString("type is bool or string")
	} else {
		i := v                         // type of i is type of x (interface{})
		printString("don't know the type")
	}
}

型別 switch 保護裝置之前可以有簡單陳述式,此陳述式會在評估保護裝置之前執行。

在型別 switch 中不允許使用 "穿透" 陳述式。

for 陳述式

「for」的陳述指定重複執行區塊。共有三種形式:反覆的動作可以透過單一條件、一個「for」的子句或「範圍」的子句來控制。

ForStmt = "for" [ Condition | ForClause | RangeClause ] Block .
Condition = Expression .

具備單一條件的 for 陳述

在最簡單的形式,一個「for」的陳述只要透過布林條件運算為真,就指定反覆執行一段區塊。條件會在每次運算前計算。若沒有條件,就等於布林值 true

for a < b {
	a *= 2
}

具備 for 子句的 for 陳述

包含 ForClause 的 for 陳述也會由其條件控制,但它額外可以指定一個 init 和一個 post 陳述,例如賦值、加法或減法陳述。init 陳述可以是一個 簡短的變數宣告,但 post 陳述不行。init 陳述宣告的變數會在每個運算中重複使用。

ForClause = [ InitStmt ] ";" [ Condition ] ";" [ PostStmt ] .
InitStmt = SimpleStmt .
PostStmt = SimpleStmt .
for i := 0; i < 10; i++ {
	f(i)
}

如果非空白,init 陳述會在第一次運算的條件評估前執行一次;post 陳述會在每次執行區塊後執行(只有在區塊被執行時)。ForClause 的任何元素都可能為空白,但除非只有條件,否則 分號 是必需的。若沒有條件,就等於布林值 true

for cond { S() }    is the same as    for ; cond ; { S() }
for      { S() }    is the same as    for true     { S() }

具備 range 子句的 for 陳述

一個包含「範圍」子句的「for」陳述會反覆運算陣列、切片、字串或地圖的所有條目,或是從管道接收到的數值。對於每一個條目,它會將 iteration values 分配給相對應的 iteration variables(如果存在),接著執行區塊。

RangeClause = [ ExpressionList "=" | IdentifierList ":=" ] "range" Expression .

在「範圍」子句中的右側表達式稱為範圍表達式,其可以是陣列、指向陣列的指標、切片、字串、地圖或管道,且允許進行 接收運算。如同指派一樣,如果存在的話,左側的運算元必須是可 尋址的或地圖索引表達式;它們表示運算變數。如果範圍表達式是一個管道,最多允許一個運算變數,否則最多可以有兩個。如果最後一個運算變數是 空白識別碼,範圍子句等於沒有該識別碼的子句。

範圍表達式 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
  1. 對於陣列、指標陣列或切片值 a,會以遞增順序產生 index 迭代值,從元素 index 0 開始。如果只有一個迭代變數,範圍迴圈會產生從 0 到 len(a)-1 的迭代值,且不會將陣列或切片本身編入 index。對 nil 切片來說,迭代次數為 0。
  2. 對於字串值,「range」詞句會從位元組 index 0 開始,迭代字串的 Unicode 編碼點。在後續迭代中,index 值會是字串中後續 UTF-8 編碼編碼點的第一個字元組的 index,而第二個值,型別為 rune,會是對應編碼點的值。如果迭代遇到無效的 UTF-8 順序,第二個值會是 0xFFFD,Unicode 取代字元,而下次迭代會將字串中的單一位元組前進。
  3. 對映射的迭代順序未指定且不保證在每次迭代後保持相同。如果在迭代期間移除一個尚未到達的映射項目,對應的迭代值不會產生。如果在迭代期間建立一個映射項目,該項目可能會在迭代期間產生,但也有可能被略過。這項選擇可能會因每次建立的項目而異,且在每次迭代後都不同。如果映射為 nil,迭代次數為 0。
  4. 對通道來說,產生的迭代值會是通道傳送的後續值,直到通道關閉。如果通道為 nil,範圍表示式會永遠遮蔽。

迭代值會指定給各自的迭代變數,就像指定語句一樣。

迭代變數可以用簡短變數宣告格式 (:=) 由「range」詞句宣告。在此情況下,其型別會設定為對應迭代值的型別,其範圍是「for」語句的區塊;其在每次迭代中會重新使用。如果迭代變數在「for」語句外宣告,執行其值後將會變為最後一次迭代的值。

var testdata *struct {
	a *[7]int
}
for i, _ := range testdata.a {
	// testdata.a is never evaluated; len(testdata.a) is constant
	// i ranges from 0 to 6
	f(i)
}

var a [10]string
for i, s := range a {
	// type of i is int
	// type of s is string
	// s == a[i]
	g(i, s)
}

var key string
var val interface{}  // element type of m is assignable to val
m := map[string]int{"mon":0, "tue":1, "wed":2, "thu":3, "fri":4, "sat":5, "sun":6}
for key, val = range m {
	h(key, val)
}
// key == last map key encountered in iteration
// val == map[key]

var ch chan Work = producer()
for w := range ch {
	doWork(w)
}

// empty a channel
for range ch {}

Go 語句

「go」語句會在同一個位址空間中以一個非同步控制執行緒,或稱goroutine,作為獨立的並發執行緒,來開始執行函式呼叫。

GoStmt = "go" Expression .

這個表示式必須是一個函式或方法呼叫,不能加括號。內建函式的呼叫受到表示式語句的限制。

函數值和參數會在呼叫 go 常式中依照慣例評估,但不同於一般呼叫,程式執行不會等到被呼叫的函數完成。而函數將會在一個新的 go 常式中獨立執行。當函數終止時,它的 go 常式也會終止。如果函數有任何回傳值,這些值會在函數完成時放棄。

go Server()
go func(ch chan<- bool) { for { sleep(10); ch <- true }} (c)

選擇陳述式

一個「選擇」陳述式會選出一個可能的傳送接收運算式。它看來類似一個「switch」陳述式,但案例都指涉通訊運算。

SelectStmt = "select" "{" { CommClause } "}" .
CommClause = CommCase ":" StatementList .
CommCase   = "case" ( SendStmt | RecvStmt ) | "default" .
RecvStmt   = [ ExpressionList "=" | IdentifierList ":=" ] RecvExpr .
RecvExpr   = Expression .

一個有 RecvStmt 的案例可能會針對一個或兩個變數指定一個 RecvExpr 的結果,這些變數可以使用簡短變數宣告宣告。RecvExpr 必須是一個(可能具有括號的)接收運算式。預設案例最多只有一個,且它可以出現在案例清單中的任何地方。

一個「選擇」陳述式的執行會進行幾個步驟

  1. 針對陳述式中的所有案例,接收運算式的通道運算元,以及傳送陳述式的通道和右側表達式會在進入「選擇」陳述式時依照來源順序評估一次。結果會是一組要接收或傳送的通道,以及相對應要傳送的值。該評估中的任何副作用都將發生,無關乎選擇進行哪一個(如果有的話)通訊運算。在 RecvStmt 左側,有一個簡短變數宣告或指定,這些表達式尚未評估。
  2. 如果一個或多個通訊可以進行,會經由一個統一的偽亂數選擇方式,選出其中一個可以進行的通訊。否則,如果有一個預設案例,則會選擇該案例。如果沒有預設案例,「選擇」陳述式會封鎖,直到至少有一個通訊可以進行。
  3. 除非選定的案例是預設案例,否則會執行個別的通訊運算。
  4. 如果選定的案例是 RecvStmt 和簡短變數宣告或指派,則會評估左側表達式,並指派接收到的值(或值)。
  5. 執行選定案例的陳述式清單。

因為在nil通道上進行通訊永遠無法進行,所以只有nil通道且沒有預設案例的選擇會永久封鎖。

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
}

有三個方式可以從一個有結果類型的函數傳回值

  1. 回傳值或值可以明列在「回傳」陳述句中。每個運算式必須為單值,並且可指定為函式結果類型的對應元素。
    func simpleF() int {
    	return 2
    }
    
    func complexF1() (re float64, im float64) {
    	return -7.0, -4.0
    }
    
  2. 「回傳」陳述句中的運算式清單可以是多值函式的單一呼叫。效果就好像從該函式回傳的每個值都指定給一個具有對應值的類型的暫時變數,接著是一個列出這些變數的「回傳」陳述句,此時套用前一個案例的規則。
    func complexF2() (re float64, im float64) {
    	return complexF1()
    }
    
  3. 如果函式結果類型為其結果參數指定名稱,則運算式清單可以是空的。結果參數扮演一般區域變數,函式可以視需要指派值給它們。「回傳」陳述句會回傳這些變數的值。
    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
    }
    

不論宣告方式為何,所有結果值在函式進入時都初始化為其類型的零值。「回傳」陳述句指定結果會在執行任何遞延函式之前設定結果參數。

實作限制:如果在回傳的地方,另一個具有與結果參數相同名稱的實體(常數、類型或變數)อยู่ใน範圍內,則編譯器可能會不允許「回傳」陳述句中出現空的運算式清單。

func f(n int) (res int, err error) {
	if _, err := f(n-1); err != nil {
		return  // invalid return statement: err is shadowed
	}
	return
}

中斷陳述句

一個「中斷」陳述句會終止相同函式中內層的"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
			}
		}
	}

繼續陳述句

一個「繼續」陳述句會開始最內層"for"迴圈的下一輪迭代,在它的 post 陳述句中。「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)
		}
	}

跳至陳述句

一個「跳至」陳述句會將控制權轉移到相同函式中具有對應標籤的陳述句。

GotoStmt = "goto" Label .
goto Error

執行「跳至」陳述句不得導致任何變數進入範圍,這些變數在跳至點時尚未在範圍內。例如,這個範例

	goto L  // BAD
	v := 3
L:

是錯誤的,因為跳至標籤L會跳過v的建立。

一個 <a href="#Blocks">區塊</a>外部的「goto」陳述式無法跳轉至區塊內的一個標籤。例如,這個範例

if n%2 == 1 {
	goto L1
}
for n > 0 {
	f()
	n--
L1:
	f()
	n--
}

是有錯誤的,因為標籤 L1 在「for」陳述式的區塊內,但 goto 不是。

直通陳述式

一個「直通」陳述式將控制權轉移到

FallthroughStmt = "fallthrough" .

遞延陳述式

一個「遞延」陳述式呼叫一個函式,此函式的執行遞延到圍繞的函式傳回的時刻,原因可能是圍繞的函式執行了一個

DeferStmt = "defer" Expression .

這個表示式必須是一個函式或方法呼叫,不能加括號。內建函式的呼叫受到表示式語句的限制。

每次一個「遞延」陳述式執行時,函式的值與呼叫的參數 nil,則執行會在函式呼叫時

例如,如果遞延函式是一個

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 類型,因此它們只能出現在

結束

對於頻道 c,內建函數 close(c) 記錄頻道不會再傳送更多值。如果 c 是唯讀頻道,則會產生錯誤。傳送至或關閉已關閉的頻道會導致 執行時期恐慌。關閉零值頻道也會導致 執行時期恐慌。在呼叫 close 後,以及在先前提出的任何值都已接收後,接收作業將傳回頻道類型的零值,且不會阻斷。多值 接收作業 會傳回接收的值以及頻道是否關閉的指示。

長度和容量

內建函數 lencap 使用不同類型的參數,並傳回 int 類型的結果。實作保證結果總是符合 int

Call      Argument type    Result

len(s)    string type      string length in bytes
          [n]T, *[n]T      array length (== n)
          []T              slice length
          map[K]T          map length (number of defined keys)
          chan T           number of elements queued in channel buffer

cap(s)    [n]T, *[n]T      array length (== n)
          []T              slice capacity
          chan T           channel buffer capacity

切片的容量是內部陣列中配置給元素的數量。在任何時候,都會有以下關係成立

0 <= len(s) <= cap(s)

nil 切片、映射或管道的長度為 0。nil 切片或管道的容量為 0。

如果 s 是字串常數,則運算式 len(s)常數。如果 s 的類型是陣列或陣列指標且運算式 s 不包含 管道接收 或(非常數)函式呼叫,則運算式 len(s)cap(s) 為常數;在這情況下不會評估 s。否則,呼叫 lencap 並非常數,並且會評估 s

const (
	c1 = imag(2i)                    // imag(2i) = 2.0 is a constant
	c2 = len([10]float64{2})         // [10]float64{2} contains no function calls
	c3 = len([10]float64{c1})        // [10]float64{c1} contains no function calls
	c4 = len([10]float64{imag(2i)})  // imag(2i) is a constant and no function call is issued
	c5 = len([10]float64{imag(z)})   // invalid: imag(z) is a (non-constant) function call
)
var z complex128

配置

內建函數 new 採用類型 T,在執行時期配置該類型 變數 的儲存空間,並傳回指向它的 *T 指標。變數初始化如 初始值 一節所述。

new(T)

例如

type S struct { a int; b float64 }
new(S)

會配置類型為 S 的變數儲存空間,對其初始化(a=0b=0.0),並傳回包含位置位址的 *S 類型值。

製作切片、映射和管道

內建函數 make 採用類型 T,必須是切片、映射或管道類型,接著視情況採用型別特定的運算式清單。它傳回類型為 T 的值(非 *T)。記憶體初始化如 初始值 一節所述。

Call             Type T     Result

make(T, n)       slice      slice of type T with length n and capacity n
make(T, n, m)    slice      slice of type T with length n and capacity m

make(T)          map        map of type T
make(T, n)       map        map of type T with initial space for approximately n elements

make(T)          channel    unbuffered channel of type T
make(T, n)       channel    buffered channel of type T, buffer size n

大小引數 nm 各必須是整數型或未分型的 常數。常數大小引數必須是非負數並且對於種類型 int 的值 可表示;如果常數未分型,它會賦予種類型為 int。如果 nm 都已提供且為常數,則 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 個地圖元素。精確行為取決於實作。

附加到切片和複製切片

內建函式 appendcopy 可協助執行常見的切片操作。兩個函式中,結果與引數所引用的記憶體是否重疊無關。

可變參數 函式 append 會將零個或多個 x 值附加到種類型為 Ss,必須是切片類型並傳回結果切片,種類型也是 Sx 值會傳給 ...T 類型的參數,其中 TS元素類型,且會套用 參數傳遞規則append 也會接受一個可指派給種類型為 []byte 的第一個引數,後接第二個種類型為字串的引數和 ...,這是一個特例。此形式會附加該字串位元組。

append(s S, x ...T) S  // T is the element type of S

如果 s 的容量不足以放入其他值,append 會配置一個新的、足夠大的底層陣列,能容納現有的切片元素和其他值。否則,append 會重複使用底層陣列。

s0 := []int{0, 0}
s1 := append(s0, 2)                // append a single element     s1 == []int{0, 0, 2}
s2 := append(s1, 3, 5, 7)          // append multiple elements    s2 == []int{0, 0, 2, 3, 5, 7}
s3 := append(s2, s0...)            // append a slice              s3 == []int{0, 0, 2, 3, 5, 7, 0, 0}
s4 := append(s3[3:6], s3[2:]...)   // append overlapping slice    s4 == []int{3, 5, 7, 2, 3, 5, 7, 0, 0}

var t []interface{}
t = append(t, 42, 3.1415, "foo")   //                             t == []interface{}{42, 3.1415, "foo"}

var b []byte
b = append(b, "bar"...)            // append string contents      b == []byte{'b', 'a', 'r' }

函式 copy 會複製切片元素,從來源 src 到目標 dst,並傳回已複製的元素數量。兩個引數都必須具有 相同 元素類型 T,而且必須可指派成種類型為 []T 的切片。已複製的元素數量是 len(src)len(dst) 中最小的那個。作為特例,copy 也會接受一個可指派給種類型為 []byte 的目標引數,並帶有種類型為字串的來源引數。此形式會將位元組從這個字串複製到位元組切片中。

copy(dst, src []T) int
copy(dst []byte, src string) int

範例

var a = [...]int{0, 1, 2, 3, 4, 5, 6, 7}
var s = make([]int, 6)
var b = make([]byte, 5)
n1 := copy(s, a[0:])            // n1 == 6, s == []int{0, 1, 2, 3, 4, 5}
n2 := copy(s, s[2:])            // n2 == 4, s == []int{2, 3, 4, 5, 4, 5}
n3 := copy(b, "Hello, World!")  // n3 == 5, b == []byte("Hello")

刪除地圖元素

內建函式 delete 會從 地圖 m 中移除具有鍵值 k 的元素。k 的種類型必須可 指派m 的鍵類型。

delete(m, k)  // remove element m[k] from map m

如果地圖 mnil 或元素 m[k] 不存在,則 delete 無動作。

處理複數

有三個函式用於組合與拆卸複數。內建函式 complex 會從浮點數實數部分和虛數部分建構一個複數值,而 realimag 則會萃取出複數值的實數部分和虛數部分。

complex(realPart, imaginaryPart floatT) complexT
real(complexT) floatT
imag(complexT) floatT

引數和傳回值的類型會對應。對於 complex,兩個引數必須是同一個浮點數類型,傳回值則是對應浮點數成分的複雜類型:float32 引數為 complex64float64 引數為 complex128。如果其中一個引數評估為非類型常數,它會先被隱式地轉換為另一個引數的類型。如果兩個引數都評估為非類型常數,它們必須是非複雜數或其虛數部分必須為零,而函數的傳回值是非類型複雜常數。

對於 realimag,引數必須是複雜類型,傳回值則是對應的浮點數類型:complex64 引數為 float32complex128 引數為 float64。如果引數評估為非類型,它必須是一個數,函數的傳回值則是非類型浮點數常數。

realimag 函數共同形成 complex 的反函數,因此,對於一個複雜類型的值 z Zz == 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

處理程序恐慌

兩個內建函數 panicrecover,協助回報並處理執行時期的程序恐慌和程式定義錯誤條件。

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,發生恐慌的序列就會停止。在這種情況下,會捨棄已呼叫 Gpanic 之間的函式狀態,並繼續執行常規動作。接著執行 GD 之前延遲的所有函式,而且 G 的執行會透過回傳給它的呼叫方來終止。

如果符合下列條件中的任何一個,recover 的回傳值會是 nil

範例中 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

實作限制:printprintln 不一定能接受隨意引數類型,但必須支援布林值、數字和字串 類型 的列印。

套件

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 的詮釋會依實作而定,但它通常是已編譯套件的完整檔名的子字串,而且可能是相對於安裝套件的儲存庫。s

實作限制:編譯器可以將 ImportPaths 限制為只能使用屬於Unicode的 L、M、N、P 和 S 主要類別(不含空白的圖形字元)的非空字串,而且可能會排除字元 !"#$%&'()*,:;<=>?[\]^`{|} 和 Unicode 取代字元 U+FFFD。

假設我們已經編譯一個含有套件子句package math的套件,它匯出函數 Sin,而且將已編譯的套件安裝到由"lib/math"識別的檔案中。這個表格說明在各種類型的匯入宣告之後,如何於匯入套件的檔案中存取 Sin

Import declaration          Local name of Sin

import   "lib/math"         math.Sin
import m "lib/math"         m.Sin
import . "lib/math"         Sin

匯入宣告會宣告匯入套件與匯出套件之間的相依關係。套件不允許直接或間接匯入自己,或者直接匯入套件而不參考它的任何匯出識別字。要僅為了副作用 (初始化) 匯入套件,請使用空白識別字作為明確套件名稱

import _ "lib/math"

套件範例

以下是實作並行質數篩選器的完整 Go 套件。

package main

import "fmt"

// Send the sequence 2, 3, 4, … to channel 'ch'.
func generate(ch chan<- int) {
	for i := 2; ; i++ {
		ch <- i  // Send 'i' to channel 'ch'.
	}
}

// Copy the values from channel 'src' to channel 'dst',
// removing those divisible by 'prime'.
func filter(src <-chan int, dst chan<- int, prime int) {
	for i := range src {  // Loop over values received from 'src'.
		if i%prime != 0 {
			dst <- i  // Send 'i' to channel 'dst'.
		}
	}
}

// The prime sieve: Daisy-chain filter processes together.
func sieve() {
	ch := make(chan int)  // Create a new channel.
	go generate(ch)       // Start generate() as a subprocess.
	for {
		prime := <-ch
		fmt.Print(prime, "\n")
		ch1 := make(chan int)
		go filter(ch, ch1, prime)
		ch = ch1
	}
}

func main() {
	sieve()
}

程式初始化與執行

零值

當透過宣告或呼叫new變數配置儲存空間,或者當建立新值(透過複合型別或呼叫 make),而且沒有提供明確的初始化,會將預設值提供給變數或值。這些變數或值中的每個元素會設定成其類型的零值:布林值為 false,數字類別為 0,字串為 "",指標、函數、介面、切片、通道和映射為 nil。這個初始化會遞迴進行,例如陣列中每個結構的元素如果沒有指定值,它的欄位都會歸零。

這兩個簡單宣告是一樣的

var i int
var i int = 0

在以下情況下

type T struct { i int; f float64; next *T }
t := new(T)

成立以下事實

t.i == 0
t.f == 0.0
t.next == nil

相同的內容也適用於

var t T

封裝初始化

在一個封裝內,封裝層級變數的初始化分階段進行,每個階段都選取在宣告順序中最早的變數,而該變數未與未初始化之變數有關聯性。

更準確地來說,如果封裝層級變數尚未初始化但沒有初始化表示法,或其初始化表示法對於已初始化之變數沒有任何相關性,那麼該變數視為已準備好要初始化。初始化重複進行,先初始化在宣告順序中最早且已準備好要初始化的封裝層級變數,直到沒有任何變數已準備好要初始化為止。

如果此程序結束時仍有任何變數尚未初始化,則表示那些變數為一個或多個初始化循環的一部分,而程式無效。

如果單一(多值)表示法初始化了變數宣告中左邊的許多變數,那麼這些變數同時初始化:若左邊的任何變數已初始化,那麼所有那些變數都在同一個階段中初始化。

var x = a
var a, b = f() // a and b are initialized together, before x is initialized

針對封裝初始化,空白變數在宣告中視同任何其他變數。

在多個檔案中宣告的變數其宣告順序由提供給編譯器的檔案順序來決定:在第一個檔案中宣告的變數在宣告於第二個檔案中的任何變數之前宣告,依此類推。

相依性分析不依賴於變數的實際值,只依賴於來源中對它們的字彙分析參照,以暫態分析。例如,如果變數 x 的初始化表示法參照了一項函式,而該函式的本體參照了變數 y,則 x 相依於 y。特別是

例如,在聲明中

var (
	a = c + b  // == 9
	b = f()    // == 4
	c = f()    // == 5
	d = 3      // == 5 after initialization has finished
)

func f() int {
	d++
	return d
}

初始化順序是 dbca。請注意,初始化表示法中子表示法的順序無關緊要:在本範例中,a = c + ba = 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 之前、ba 之間或在 a 之後初始化,以及 sideEffect() 的呼叫時機(在 x 初始化之前或之後)因此而未指定。

也可以使用套件區塊中宣告的名為 init 的函式來初始化變數,不含參數且無結果參數。

func init() { … }

每個套件可以定義多個此類函式,甚至在單一來源檔案中也可以。在套件區塊中,init 識別碼只能用來宣告 init 函式,但識別碼本身不能 宣告。因此 init 函式無法在程式中的任何地方參照。

未匯入任何內容的套件會先將初始化值指定給所有套件層級變數,再按其在來源中出現的順序列入考量(有可能橫跨多個檔案,如呈現在編譯器中),呼叫所有 init 函式。如果套件有匯入內容,則會先初始化匯入的套件,然後再初始化套件本身。如果多個套件匯入某個套件,則匯入的套件僅會初始化一次。建構套件的匯入方式可確保不存在任何循環初始化的相依性。

套件初始化(變數初始化與 init 函式呼叫)會在單一 goroutine 中按順序進行,一次只會執行一個套件。init 函式可以啟動其他 goroutine,這些 goroutine 可與初始化程式碼同時執行。然而,初始化時會按順序排列 init 函式:在先前的函式返回之前,不會呼叫下一個函式。

為確保初始化行為可重複,建議建置系統以字彙檔案名稱順序提供屬於相同套件的多個檔案給編譯器。

程式執行

完整程式是透過連結單一未匯入的套件(稱為主套件)以及其所有以循序漸進方式匯入的套件來建立的。主套件的套件名稱必須為 main,並宣告不帶任何參數且不傳回任何值的函式 main

func main() { … }

程式執行會先初始化主套件,然後再呼叫函式 main。當該函式呼叫傳回時,程式便會結束。程式不會等到其他非 main goroutine 完成。

錯誤

預先宣告的類型 error 定義為

type error interface {
	Error() string
}

這是一個表示錯誤狀況的標準介面,其中 nil 值表示沒有錯誤。例如,可以用來從檔案讀取資料的函式可以定義:

func Read(f *File, b []byte) (n int, err error)

執行時期的驚嚇

執行錯誤,例如嘗試對超出邊界的陣列進行索引,會引發與內建函式 panic 的呼叫等效的執行時期的驚嚇,其值為實作定義的介面類型 runtime.Error。該類型滿足預先宣告的介面類型 error。表示不同執行時期錯誤狀況的確切錯誤值未指定。

package runtime

type Error interface {
	error
	// and perhaps other methods
}

系統考量

套件 unsafe

內建的套件 unsafe,已知於編譯器且可透過 匯入路徑 "unsafe" 存取,提供低階程式設計的設施,包括違反類型系統的運算。使用 unsafe 的套件必須手動審查類型安全性,且可能不可移植。套件提供下列介面

package unsafe

type ArbitraryType int  // shorthand for an arbitrary Go type; it is not a real type
type Pointer *ArbitraryType

func Alignof(variable ArbitraryType) uintptr
func Offsetof(selector ArbitraryType) uintptr
func Sizeof(variable ArbitraryType) uintptr

type IntegerType int  // shorthand for an integer type; it is not a real type
func Add(ptr Pointer, len IntegerType) Pointer
func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType

Pointer指標類型,但是 Pointer 值可能不會 解除參考。任何指標或 基礎類型 uintptr 的值都可以轉換為基礎類型 Pointer 的類型,反之亦然。在 Pointeruintptr 之間轉換的效果由實作定義。

var f float64
bits = *(*uint64)(unsafe.Pointer(&f))

type ptr unsafe.Pointer
bits = *(*uint64)(ptr(&f))

var p ptr = nil

函式 AlignofSizeof 取用任何類型的表達式 x,並分別傳回假設變數 v 的對齊或大小,就好像 v 是透過 var v = x 宣告的。

函式 Offsetof 取用一個可能的已加上括號的 選擇器 s.f,表示由 s*s 表示的結構的欄位 f,並傳回以位元組表示的,相對於結構位址的欄位偏移量。如果 f 是一個 內嵌欄位,它必須可在不透過結構的欄位進行指標間接參考的情況下到達。對於包含欄位 f 的結構 s

uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.f) == uintptr(unsafe.Pointer(&s.f))

電腦架構可能要求記憶體位址對齊;也就是說,用於變數的位址必須是某個因數的倍數,也就是變數類型的對齊。函式 Alignof 取用表示任何類型變數的表達式,並傳回(該)變數的類型的對齊,以位元組為單位。對於變數 x

uintptr(unsafe.Pointer(&x)) % unsafe.Alignof(x) == 0

AlignofOffsetofSizeof 的呼叫是類型為 uintptr 的編譯時期常設表達式。

函式 Add 會將 len 加到 ptr 上,並回傳更新後的指標 unsafe.Pointer(uintptr(ptr) + uintptr(len))len 這個參數必須是整數型態,或是未定型的 常數。若 len 為常數參數,則必須 可以表示int 型態的值;若為未定型常數,則其型態會被設成 int。仍然適用於 Pointer有效使用 規則。

函式 Slice 回傳的切片其底層陣列由 ptr 開始,其長度和容量皆為 lenSlice(ptr, len) 相當於

(*[len]ArbitraryType)(unsafe.Pointer(ptr))[:]

特別注意的是,如果 ptrnillen 為零,則 Slice 會回傳 nil

len 這個參數必須是整數型態,或是未定型的 常數。若 len 為常數參數,則必須是非負數,且 可以表示int 型態的值;若為未定型常數,則其型態會被設成 int。在執行時期,如果 len 為負數,或是 ptrnillen 不為零,則會發生 執行時期的 panic

大小和對齊保證

對於 數字型別,保證下列大小

type                                 size in bytes

byte, uint8, int8                     1
uint16, int16                         2
uint32, int32, float32                4
uint64, int64, float64, complex64     8
complex128                           16

保證以下最小對齊屬性

  1. 對於任何型別的變數 xunsafe.Alignof(x) 至少為 1。
  2. 對於 struct 型別的變數 xunsafe.Alignof(x)x 的每個欄位 f 的所有值 unsafe.Alignof(x.f) 中最大的,但至少為 1。
  3. 對於陣列型別的變數 xunsafe.Alignof(x) 與陣列元素型別變數的对齐相同。

如果 struct 或陣列型別未包含任何大於 0 的欄位(或元素),則其大小為 0。在記憶體中,兩個不同大小為 0 的變數可能位於同一個地址。