介紹#
這是一個 Go 語言的參考手冊,你也可以訪問golang.org獲取更多信息和其他文檔。
Go 是在設計時考慮了系統編程的通用型編程語言。它是強類型,有垃圾回收機制並原生支持並發編程。Go 程序由一個或多個 package 組成,這樣可以高效的管理依賴。
Go 的語法簡潔且有規則,這讓自動化工具可以很容易的分析代碼,例如:集成開發環境。
標記#
語法採用擴展巴科斯範式。
Production = production_name "=" [ Expression ] "." .
Expression = Alternative { "|" Alternative } .
Alternative = Term { Term } .
Term = production_name | token [ "…" token ] | Group | Option | Repetition .
Group = "(" Expression ")" .
Option = "[" Expression "]" .
Repetition = "{" Expression "}" .
產生式是由詞法單元和以下操作符構成的表達式(優先級依次遞增):
| 或
() 分組
[] 可選 (出現 0 或 1 次)
{} 可重複 (出現 0 到 n 次)
小寫的產生式名稱用來與詞法單元區分。非終結符採用駝峰式。詞法單元由雙引號或反引號組成。
a...b
表示從 a
到 b
之間的任意字符。省略號 ...
也可以在規範中表示對更詳細的枚舉和代碼片段的省略。字符 ...
不是 Go 語言的詞法單元。
源碼表示法#
Go 的源代碼使用 UTF-8 編碼的 Unicode 文本。不過它並不是完全規範化的,單重音的代碼點與由相同字符和音標組成的代碼點是不同的;前者我們認為它是兩個代碼點。簡單來講,文檔會在源代碼文本中使用非規範的術語字符來表示一個 Unicode 代碼點。
每個代碼點都是不同的;相同字符的大寫和小寫形式表示不同的字符。
實現限制:為了兼容其他工具,編譯器不允許出現 Utf-8 編碼的源文本中的 NUL 字符(U+0000)。
實現限制:為了兼容其他工具,如果源文本中是以 Utf-8 編碼的字節序標記(U+FEFF)為起始代碼點。編譯器會忽略它。字節序標記不應出現在源文本的任何位置。
字符#
這些單詞表示 Unicode 字符的類別:
newline = /* Unicode 代碼點 U+000A */ .
unicode_char = /* 排除換行以外的任意 Unicode 代碼點 */ .
unicode_letter = /* 一個字母("Letter")類型的 Unicode 代碼點 */ .
unicode_digit = /* 一個數字("Number, decimal digit")類型的 Unicode 代碼點 */ .
在 Unicode8.0 標準中,第 4.5 章節 “一般類別” 中定義了字符的類別。Go 能夠處理任何字符集,包括 Lu,Li,Lt,Lm 或 Lo 作為 Unicode 字母,還可以把數字字符集 Nd 當作 Unicode 數字處理。
字母和數字#
我們認為下劃線 _
(U+005F)是一個字母:
letter = unicode_letter | "_" .
decimal_digit = "0" … "9" .
octal_digit = "0" … "7" .
hex_digit = "0" … "9" | "A" … "F" | "a" … "f" .
詞彙元素#
註釋#
註釋是程序的說明文檔。在 Go 中有兩種形式:
- 單行註釋從
//
開始直到行末結束。 - 通用註釋從
/*
開始直到*/
結束。
註釋不能嵌套在其他註釋、字符串和 rune 的字面值中。不包含換行符的通用註釋之間通過空格符連接,其他情況下每段註釋都會另起一行。
詞彙元素#
詞彙元素構成了 Go 語言的詞彙表。它有四種類型:標識符、關鍵字、操作符 / 標點符號、字面值。空白符可以是空格(U+0020)、水平制表符(U+0009)、換行符(U+000D)或換行符(U+000A)。它本身會被忽略,一般用來區分不同的詞彙元素。換行符或文件終止符(EOF)還可能觸發編譯程序在源代碼的行末或文件末尾追加分號。在分解源代碼的詞彙元素的過程中,會把當前可以形成有效詞彙元素的最長字符序列作為下一個詞彙元素。
分號#
正規語法在很多產生式中使用分號 ";" 作為終結符。Go 程序中遵循下面兩條規則省略了大部分的分號:
- 當某行的最後一個詞彙元素是以下元素時自動補全分號:
-
一個標識符。
-
一個整數,浮點數,虛數,rune 或字符串字面值。
-
關鍵字
break
、continue
、fallthrough
和return
其中之一。 -
操作符 / 標點符號
++
,--
,)
,]
和}
其中之一。
- 為了支持獨占一行的複雜語句,會省略與 ")" 或 "}" 相鄰的分號。
為了反映慣用用途,本篇文檔的所有例子都基於以上規則省略分號。
標識符#
標識符表示程序實體單元,例如:變量、類型。一個標識符由一個或多個字母和數字組成。標識符的首字符必須為字母。
identifier = letter { letter | unicode_digit } .
a
_x9
ThisVariableIsExported
αβ
Go 已經預定義了一些標識符。
關鍵字#
以下關鍵字是預留的,它們不能作為標識符:
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
操作符和標點符號#
以下字符序列用於表示操作符(包括賦值運算符)和標點符號:
+ & += &= && == != ( )
- | -= |= || < <= [ ]
* ^ *= ^= <- > >= { }
/ << /= <<= ++ = := , ;
% >> %= >>= -- ! ... . :
&^ &^=
整型字面值#
整型字面值是一個數字序列,相當於整型常量。可以使用前綴指定非小數進制:0 表示八進制,0x/0X 表示十六進制。在十六進制字面值中,字母 a-f 和 A-F 都表示數字 10-15。
int_lit = decimal_lit | octal_lit | hex_lit .
decimal_lit = ( "1" … "9" ) { decimal_digit } .
octal_lit = "0" { octal_digit } .
hex_lit = "0" ( "x" | "X" ) hex_digit { hex_digit } .
42
0600
0xBadFace
170141183460469231731687303715884105727
浮點字面值#
浮點字面值是一個小數,相當於浮點數常量。它由整數部分,小數點,小數部分和指數部分構成。整數部分和小數部分用小數點鏈接;指數部分由 e
/ E
字符後接一個有符號指數構成。整數部分和小數部分可以省略其一;小數點和指數部分可以省略其一。
float_lit = decimals "." [ decimals ] [ exponent ] |
decimals exponent |
"." decimals [ exponent ] .
decimals = decimal_digit { decimal_digit } .
exponent = ( "e" | "E" ) [ "+" | "-" ] decimals .
0.
72.40
072.40 // == 72.40
2.71828
1.e+0
6.67428e-11
1E6
.25
.12345E+5
虛數字面值#
虛數字面值是一個小數,相當於複數常量中的虛數部分。它由浮點數或者整數後接小寫字母 i 構成。
imaginary_lit = (decimals | float_lit) "i" .
0i
011i // == 11i
0.i
2.71828i
1.e+0i
6.67428e-11i
1E6i
.25i
.12345E+5i
Rune 字面值#
rune 類型字面值相當於一個 rune 常量。它是一個表示 Unicode 代碼點的整數。rune 類型字面值表示為用單引號包裹的一個或多個字符,像 'x' 或 '\n'。在單引號中除了換行符和未轉義的單引號其他的字符都可以直接顯示。單引號包裹的字符的值和字符在 Unicode 編碼中的值相等,而以反斜線開頭的多字符序列會把值翻譯成多種格式。
使用引號表示單字符是最簡單的方式;因為 Go 的源文本是 UTF-8 編碼,一個整數可能代表多個 UTF-8 字節。例如, 'a' 可以使用單字節表示字符 a,Unicode 編碼 U+0061,值 0x61,而 'ä' 是兩字節表示分音符的 a,Unicode 編碼 U+00E4,值 0xe4。
反斜線能將任意值編碼成 ASCII 文本。有四種方式將整數值表示為數字常量:\x
後接兩個十六進制數;\u
後接四個十六進制數;\U
後接八個十六進制數。 \
後接三個八進制數。每種情況下都使用相應進制來表示字面量的整數值。
雖然這四種方式都以整數表示,但它們的有效區間並不相同。八進制只能表示 0 - 255 以內的整數。十六進制滿可以滿足需求。\u
和 \U
都可以表示 Unicode 代碼點,不過其中的一些值是無效的,特別是 0x10FFFF 以上的值。
反斜線結合以下字符具有特殊含義:
\a U+0007 alert or bell
\b U+0008 退格符
\f U+000C form feed
\n U+000A line feed or newline
\r U+000D carriage return
\t U+0009 水平制表符
\v U+000b 垂直制表符
\\ U+005c 反斜線
\' U+0027 單引號 (只在 rune 字面值中有效)
\" U+0022 雙引號 (只在字符串字面值中有效)
其他所有以反斜線開頭的序列在 rune 的規則中都是非法的。
rune_lit = "'" ( unicode_value | byte_value ) "'" .
unicode_value = unicode_char | little_u_value | big_u_value | escaped_char .
byte_value = octal_byte_value | hex_byte_value .
octal_byte_value = `\` octal_digit octal_digit octal_digit .
hex_byte_value = `\` "x" hex_digit hex_digit .
little_u_value = `\` "u" hex_digit hex_digit hex_digit hex_digit .
big_u_value = `\` "U" hex_digit hex_digit hex_digit hex_digit
hex_digit hex_digit hex_digit hex_digit .
escaped_char = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | "'" | `"` ) .
'a'
'ä'
'本'
'\t'
'\000'
'\007'
'\377'
'\x07'
'\xff'
'\u12e4'
'\U00101234'
'\'' // 包含單引號的 rune 字面值
'aa' // 無效: 太多字符
'\xa' // 無效: 缺少十六進制數
'\0' // 無效: 缺少八進制數
'\uDFFF' // 無效: surrogate half
'\U00110000' // 無效: 非法的 Unicode 代碼點
字符串字面量#
字符串字面量表示從字符序列中獲取的字符串常量。它有兩種格式:原始字符串字面量和解釋型字符串字面量。
原始字符串是由反引號包裹(foo
)。字符串中除反引號以外的其他字符都會顯示出來。原生字符串由反引號之間的(默認 UTF-8 編碼)的字符組成。它的值為引號內未經解釋(默認 UTF-8 編碼)所有字符;尤其是,反斜線在字符串中沒有特殊意義並且字符串中保留換行符。在原始字符串的值中會丟弃回車鍵返回 '\r' 字符。
解釋型字符串由雙引號之間的字符組成("bar")。除了換行符和雙引號其他字符都會顯示出來。雙引號之間的文本組成字面量的值。反斜線的轉義規則與 rune 字面值基本相同(不同的是 \’ 非法,而 " 合法)。三位八進制數(\nnn)和兩位十六進制數(\xnn)換碼符的值表示相應字符串的字節。其他的換碼符都表示字符各自的 UTF-8 編碼(可能是多字節)。因此字符串 \377 和 \xFF 都表示值為 0xFF=255 的單個字節,而 ÿ
, \u00FF
, \U000000FF
和 \xc3\xbf
表示 UTF-8 編碼字符 U+00FF 的兩個字節 0xc3 0xbf。
string_lit = raw_string_lit | interpreted_string_lit .
raw_string_lit = "`" { unicode_char | newline } "`" .
interpreted_string_lit = `"` { unicode_value | byte_value } `"` .
`abc` // 等價於 "abc"
`\n
\n` // 等價於 "\\n\n\\n"
"\n"
"\"" // 等價於 `"`
"Hello, world!\n"
"日本語"
"\u65e5本\U00008a9e"
"\xff\u00FF"
"\uD800" // 無效: surrogate half
"\U00110000" // 無效: 無效的 Unicode 代碼點
這些例子都表示相同的字符串:
"日本語" // UTF-8 文本
`日本語` // UTF-8 文本作為原生字面值
"\u65e5\u672c\u8a9e" // 確定的 Unicode 代碼點
"\U000065e5\U0000672c\U00008a9e" // 確定的 Unicode 代碼點
"\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e" // 確定的 UTF-8 字節
如果源代碼中使用兩個代碼點表示一個字符,例如帶音標的字母,把它放在 rune 中會報錯(它不是單代碼點)。並且在字符串中會顯示兩個代碼點。
常量#
常量分為:布爾型,rune 型,整型,浮點型,複數型,字符串型。其中 rune,整型,浮點型,複數型統稱為數字常量。
常量的值可以表示為一個 rune 字面量,整數字面量,浮點數字面量,虛數字面量,字符串字面量,表示常量的標識符,常量表達式,一個轉換結果為常量的類型轉換,和一些返回值為常量的內置函數 (接受任何值的unsafe.Sizeof
,接受部分表達式的cap
或 len
,接受虛數常量的real
和 imag
,接受數字常量的 complex
)。布爾類型的值為預定義常量 true
或 false
,預定義的標識符 iota
表示一個整型常量。
一般情況下複數常量是常量表達式的一種形式。會在常量表達式章節詳細討論。
數字常量可以表示任意精度的確定值而且不會溢出。因此,沒有常量可以表示非 0,無窮大和非數字值。
常量可以指定類型也可以不指定類型。字面值常量,true
,false
,iota
,和只包含無類型常量操作的常量表達式是無類型的。
常量可以通過常量聲明和轉換時顯式的指定具體類型,也可以隱式的在變量聲明、賦值或作為表達式操作元時隱式的指定具體類型。如果常量的值和他的類型不匹配,會報錯。
無類型常量由一個默認的類型,這個類型會根據使用常量時的上下文進行隱式轉換。例如:短變量聲明 i := 0
沒有指定 i 的類型。無類型常量的默認類型可以是:bool
,rune
,int
,float64
,complex128
或者 string
,具體選擇哪種類型由常量的值決定。
實現限制:雖然數字常量在 Go 中是任意精度,不過編譯器在實現時會在內部限制精度。這意味著每個編譯器實現都要:
-
至少保證整形常量有 256 位
-
浮點數常量(包括複數常量)都要保證至少 256 位的主體部分和至少 16 位的有符號指數部分
-
如果不能表示給定整數的精度拋出錯誤
-
如果浮點數或複數溢出拋出錯誤
-
如果由於精度限制不能表示浮點數或者複數進行舍入
這些要求同時作用於字面量常量額和常量表達式的結果。
變量#
變量是一個用來儲存值的位置。根據不同的變量類型,可以保存不同的值。
變量聲明,函數參數和返回值,聲明的函數簽名,函數字面值都會為命名變量預留儲存空間。調用內置的 new
函數或獲取複合字面值的地址都會在運行時為變量分配存儲空間。這種匿名變量是通過(可能是隱式的)指針間接引用的。
像數組,切片和結構體類型的變量,它們內部都包含很多元素或字段,而且這些元素和字段都可以直接被訪問。數組和切片中的每個元素的行為和單獨的變量基本相同。
變量的靜態類型可以通過變量聲明、提供給 new
的類型、複合字面值、結構體變量聲明的元素類型以上幾種方式確定。通過 new 或者類型初始化。接口類型的變量也有一個明確的動態類型,這個動態類型是在運行時賦值給變量的具體值類型(特例:預聲明的 nil 是無類型的)。動態類型在程序的執行過程中可能並不相同,但是接口變量的值是可以分配給相同靜態類型的變量。
var x interface{} // x 的靜態類型為 interface{} 值為 nil
var v *T // v 的靜態類型為 *T 值為 nil
x = 42 // x 的動態類型為 int 值為 42
x = v // x 動態類型為 *T 值為 (*T)(nil)
在表達式中使用變量可以取出變量的值;這個值就是變量最近一次被賦予的值。如果沒有對變量賦過值,那麼他的值是該類型的零值。
類型#
類型是一個集合,集合包括值和針對值的操作 & 方法。一個類型可以使用類型名來表示。類型有多種表現形式:如果存在類型名,可以使用類型名表示,或者也可以使用根據已有類型組合成的類型字面值。
Type = TypeName | TypeLit | "(" Type ")" .
TypeName = identifier | QualifiedIdent .
TypeLit = ArrayType | StructType | PointerType | FunctionType | InterfaceType |
SliceType | MapType | ChannelType .
Go 已經預先聲明了某些類型的名稱。並引入了類型聲明。複合類型(數組、結構體、指針、函數、接口、切片、map、channel)可以使用他們的類型字面值。
每個類型 T 都有一個底層類型。如果 T 是預定義類型或者類型字面值。那么底層類型就是他自身。否則,T 的底層類型就是它再類型聲明時引用到的類型。
type (
A1 = string
A2 = A1
)
type (
B1 string
B2 B1
B3 []B1
B4 B3
)
string
,A1
,A2
,B1
,B2
的底層類型是 string
。[]B1
,B3
,B4
的下游類型是 [] B1。
方法集#
類型可能會有一個與之關聯的方法集。接口類型的方法集就可以使用自身表示。對於其他類型,類型 T 的方法集由所有接收者類型為 T 的方法組成。而對應指針類型 *T 的方法集由所有接收者類型為 T 或 *T 的方法組成。如果是結構體類型且含有嵌入字段,那麼方法集中可能還會包含更多的方法,具體請看結構體類型章節。其他類型的方法集都為空。方法集中的每個方法都有唯一且不為空的方法名。
類型的方法集用來確定類型實現的接口和以類型作為接收者能夠調用的方法。
布爾類型#
布爾類型表示預定義常量 true
和 false
表示布爾真實值的集合。預定義的布爾類型為 bool
;它是通過類型聲明創建的。
數字類型#
一個數字類型相當於整型和浮點型的所有值的集合。預定義的數字類型包括:
uint8 8 位無符號整數集合 (0 to 255)
uint16 16 位無符號整數集合 (0 to 65535)
uint32 32 位無符號整數集合 (0 to 4294967295)
uint64 64 位無符號整數集合 (0 to 18446744073709551615)
int8 8 位有符號整數集合 (-128 to 127)
int16 16 位有符號整數集合 (-32768 to 32767)
int32 32 位有符號整數集合 (-2147483648 to 2147483647)
int64 64 位有符號整數集合 (-9223372036854775808 to 9223372036854775807)
float32 IEEE-754 32 位浮點數集合
float64 IEEE-754 64 位浮點數集合
complex64 實部虛部都為 float32 的複數集合
complex128 實部虛部都為 float64 的複數集合
byte uint8 的別名
rune int32 的別名
n 位整數的值具有 n 比特的寬度並用補碼表示。
以下幾種預定義類型由具體平台實現指定長度:
uint 32 或 64 位
int 和 uint 位數相同
uintptr 能夠容納指針值的無符號整數
為了避免移植性問題,除了被 uint8 的別名 byte 和 int32 的別名 rune,其他所有的數字類型都是通過類型聲明定義。當在表達式中使用不同的數字類型需要進行類型轉換。例如:int32 和 int 不是相同的類型,即使他們在指定的平台上是相等的。
字符串類型#
字符串類型表示字符串的值類型。字符串的值是一個字節序列(有可能為空)。字符串一旦創建就無法修改它的值。預定義的字符串類型是 string
,它是通過類型聲明定義的。
可以使用內置函數 len
獲取字符串長度。如果字符串是常量那麼它的長度在編譯時也為常量。可以通過數字下標 0~len (s)-1 訪問字符串字節。獲取字符串的地址是非法操作;如果 s[i]
是字符串的第 i 個字節,那麼 &s[i]
是無效的。
數組類型#
數組是一個一定數量的單一類型元素序列,而這個單一類型叫做元素類型。元素的個數表示元素的長度,它永遠不是負數。
ArrayType = "[" ArrayLength "]" ElementType .
ArrayLength = Expression .
ElementType = Type .
長度是數組類型的一部分;它是一個類型為 int 的非負常量。可以用內置函數 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 .
與數組一樣,切片的可以使用索引訪問並且有長度,切片的長度可以通過內置的 len
函數獲取;與數組不同的是它的長度在運行時是可以變化的。我們可以通過下標 0~len(s)-1
來訪問切片內的元素。切片的索引可能會小於相同元素再底層數組的索引。
切片一旦初始化,那麼就有一個與之對應的底層數組保存切片中的元素。切片和底層的數組還有其他指向該數組的切片共享相同的儲存空間;而不同的數組總是有著不同的存儲空間。
切片的底層數組可能會延伸到切片末尾以外,切片的容積等於切片現在的長度加上數組中切片還沒使用的長度;可以從原始切片中切出一個長度與容量相等的切片。切片的容量可以通過內置的 cap(a)
函數來獲取。可以通過函數make
來創建一個 T 類型的新切片。
使用內置函數 make
可以出實話給定元素類型 T 的切片。make
函數接收三個參數:切片類型、切片長度、切片容積,其中切片容積是可選參數。make
創建的切片會在底層分配一個切片所引用的新數組。
make([]T, length, capacity)
make
的作用就是創建新數組並切分它,所以下面兩種寫法是等價的:
make([]int, 50, 100)
new([100]int)[0:50]
與數組相同,切片一般是一維的,不過也可以複合成多維。數組中的數組都必須是相同的長度,但是切片中的切片長度是動態變化的,不過切片中的切片需要單獨初始化。
結構體類型#
結構體是一個命名元素序列,命名元素也叫做字段,每個字段都對應一個名稱和類型,字段的名字可以是顯式指定的(標識符列表)也可以是隱式的(嵌入字段)。在結構體中非空字段具有唯一性。
StructType = "struct" "{" { FieldDecl ";" } "}" .
FieldDecl = (IdentifierList Type | EmbeddedField) [ Tag ] .
EmbeddedField = [ "*" ] TypeName .
Tag = string_lit .
// 空結構體.
struct {}
// 6個字段的結構體.
struct {
x, y int
u float32
_ float32 // padding
A *[]int
F func()
}
一個指定了類型而沒有指定名稱的字段叫做嵌入字段,嵌入字段必須指定類型名 T 或指向非接口類型的指針類型 *T,其中 T 不能為指針類型。或者一個非接口類型的指針。並且 T 本身不能為指針類型。這種情況下會把類型名作為字段的名字。
// 一個包含 4 個嵌入字段 T1, *T2, P.T3 和 *P.T4 的結構體
struct {
T1 // 字段名為 T1
*T2 // 字段名為 T2
P.T3 // 字段名為 T3
*P.T4 // 字段名為 T4
x, y int // 字段名為 x 和 y
}
以下聲明是錯誤的因為字段名稱必須唯一。
struct {
T // 嵌入字段 *T 與 *P.T 衝突
*T // 嵌入字段 T 與 *P.T 衝突
*P.T // 嵌入字段 T 與 *T 衝突
}
如果 x.f
是表示該字段或方法 f
的合法選擇器,則會調用結構 x
中嵌入字段的字段或方法 f
。
從嵌入字段組合來的字段與結構體原來的字段行為基本相同,只是不能在結構體的複合字面值中直接使用。
給定一個結構體 S 和一個類型 T,依據以下規則生成組合後的方法集:
- 如果 S 包含嵌入字段 T,則 S 和 *S 的方法集包括接收者為 T 的方法集,而 *S 包括 接收者為 *T 的方法集。
- 如果 S 包含字段 T。那麼 S 和S 均包含接收者為 T 和 *T 的所有方法集。
聲明字段時可以給該字段添加一個字符串的 tag。這個 tag 將會成為它所對應字段的一個屬性。空 tag 和缺省 tag 是相同的。tag 的值可以通過反射的接口獲取,可以作為類型結構體的類型定義的一部分,也可以忽略。
struct {
x, y float64 "" // 空 tag 和缺省 tag 相同
name string "any string is permitted as a tag"
_ [4]byte "ceci n'est pas un champ de structure"
}
// 結構體對應一個 TimeStamp 的 protocol buffer.
// tag 字符串中定義了 protocol buffer 字段對應的數字;
// 一般使用 reflect 包读取他們.
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 .
在參數和返回值列表中,標識符列表必須同時存在或缺省。如果存在,那麼每個名字都表示指定類型的一個參數 / 返回值,這些標識符必須非空並且不能重複。如果缺省,指定類型的參數 / 返回值使用對應的類型表示。參數列表和返回值列表一般都是需要加括號,不過在只有一個缺省返回值時,它可以不使用括號。
函數的最後一個參數可以添加前綴 ...
。包含這種參數的函數叫做變參函數,它可以接收零個或多個參數。
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 ";" } "}" .
MethodSpec = MethodName Signature | InterfaceTypeName .
MethodName = identifier .
InterfaceTypeName = TypeName .
在接口類型的方法集中,每個方法的名稱必須是非空且唯一。
// A simple File interface
interface {
Read(b Buffer) bool
Write(b Buffer) bool
Close()
}
接口可以由多個類型實現,例如:類型 S1
和類型 S2
都有以下方法集:
func (p T) Read(b Buffer) bool { return … }
func (p T) Write(b Buffer) bool { return … }
func (p T) Close() { … }
(這裡的類型 T 可以表示 S1
也可以表示 S2
) S1
和 S2
都實現了接口 File
,而不用管類型是否還有其他方法。
一個類型實現了任何方法集的為其子集的接口。因此它可能實現了多個不同接口。例如:所有的類型都實現了空接口:
interface{}
與之相似,思考下面這個定義為 Locker
的接口:
type Locker interface {
Lock()
Unlock()
}
如果 S1
和 S2
也實現了它:
func (p T) Lock() { … }
func (p T) Unlock() { … }
那它們就實現了兩個接口 Locker
和 File
。
一個接口 T 可以使用另一個接口 E 來指定方法。這種方式叫做將接口 E 嵌入進接口 T。它把 E 中所有的方法(包括導出和未導出的方法)全部添加進接口 T。
type ReadWriter interface {
Read(b Buffer) bool
Write(b Buffer) bool
}
type File interface {
ReadWriter // 與添加 ReadWriter 接口中的方法是等價的
Locker // 與添加 Locker 接口中的方法是等價的
Close()
}
type LockedFile interface {
Locker
File // 無效: Lock, Unlock 不是唯一的
Lock() // 無效: Lock 不是唯一的
}
接口 T 不能遞歸的嵌入進自己或已經嵌入過它的接口。
// 無效: Bad 不能嵌入它自己
type Bad interface {
Bad
}
// 無效: Bad1 不能嵌入已經引用它的 Bad2
type Bad1 interface {
Bad2
}
type Bad2 interface {
Bad1
}
Map 類型#
map 類型是一種以唯一值作為鍵的無序集合。
MapType = "map" "[" KeyType "]" ElementType .
KeyType = Type .
map 的鍵類型必須能使用比較運算符 ==
和 !=
進行比較。因此它的鍵類型不能是函數,map,或者切片。如果鍵是接口類型,那麼比較運算符必須能比較他的動態值。如果不能會拋出一個運行時錯誤。
map[string]int
map[*T]struct{ x, y float64 }
map[string]interface{}
map 中元素的個數叫做它的長度。對於一個 map m
。它的長度可以通過內置函數 len
獲得,而且它的長度可能再運行時發生變化。map 可以再運行時添加和取回元素,頁可以使用內置函數 delete
移除元素。
可以使用內置函數 make
初始化一個新的且為空的 map。它能指定 map 的類型和預留的空間:
make(map[string]int)
make(map[string]int, 100)
map 的預留空間不會固定住 map 的長度;它可以通過添加一定數量的元素來增加自己的長度(nil map 不能添加元素)。nil map 和空 map 是相等的,只是 nil map 不能添加元素。
Channel 類型#
channel 提供一種手段在並發執行的函數間發送和接收指定類型的值。沒有初始化的 channel 是 nil。
ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .
操作符 <-
可以指定 channel 的數據流動方向。如果沒有指定方向,channel 默認是雙向的。channel 可以通過轉換和賦值來限制只讀和只寫。
chan T // 可以接收和發送 T 類型的數據
chan<- float64 // 只能發送 float64 類型的值
<-chan int // 只能接收
<-
與最左側的 chan
關聯:
chan<- chan int // 等價於 chan<- (chan int)
chan<- <-chan int // 等價於 chan<- (<-chan int)
<-chan <-chan int // 等價於 <-chan (<-chan int)
chan (<-chan int)
可以通過內置的 make
函數初始化 channel。make
函數可以指定 channel 的類型和容量。
make(chan int, 100)
容量是設置了最大能緩存元素的數量。如果沒有設置容量或值為 0,channel 就是沒有緩存的,這時只有當發送者和接收者都準備好後才會傳輸數據。而帶緩存的 channel 在緩存沒有滿的時候依然可以成功發送數據,當緩存不為空的時候可以成功接收到數據,值為 nil 的 channel 不能傳輸數據。
可以通過內置函數 close
關閉 channel。在接收端的第二個返回值可以用來提示接收者在關閉的 channel 是否還包含數據。
channel 可以在發送語句,接收操作中使用。可以不考慮同步性直接在多個 goroutine 中對 channel 調用內置函數 len
和 cap
。channel 的行為和 FIFO 隊列相同。舉個例子,一個 goruntine 發送數據,另一個 goruntine 接收他們,接收數據的順序和發送數據的順序是相同的。
類型的屬性和值#
類型標識#
兩個類型可能相同也可能不同。
定義的類型都是不同類型。如果兩個類型的底層類型在結構上是相同的,那它們也是相等的。總的來說:
-
2 個數組的長度和元素類型相同,那麼它們就是相同類型。
-
如果兩個切片的元素類型相同那麼它們就是相同類型。
-
如果兩個結構體字段順序相同,並且字段名稱、字段類型和 tag 都相同那麼它們就是相等的。非導出字段的字段名在不同的包中總是不同的。
-
如果兩個指針的基礎類型相同那麼他們具有相同類型。
-
如果兩個函數具有相同的參數和返回值列表,並且他們的類型相同那麼他們就是相同的,參數的名稱不一定要相同。
-
如果兩個接口的方法集完全相同(方法的順序)。
-
如果兩個 map 類型的鍵類型和值類型相同那它們就是相等的。
-
如果兩個 channel 類型包含的對象類型和 channel 的方向都是相同的那它們就是相同的。
給出下列聲明:
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, 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
B0 和 B1 不是一種類型因為它們是通過類型定義方式分別定義的;func(int, float64) *B0
和 func(x int, y float64) *[]string
是不同的,因為 B0 和 [] string 不是相同類型。
可分配性#
在以下情況下,可以將 x 分配給類型為 T 的變量(把 x 分配給 T):
-
x 的類型為 T
-
x 的類型 V 和 T 有相同的底層類型並且類型 T 或 V 至少一個定義的類型
-
T 是一個接口類型並且 x 實現了 T
-
x 是一個 channel,並且 T 是 channel 類型,類型 V 和類型 T 有相同的元素類型,並且 2 種類型至少有一種不是定義的類型
-
x 等於 nil 並且 T 是一個指針,函數,切片,map,channel 或接口類型
-
x 是一個可以表示 T 類型值的無類型常量
代表性#
滿足以下條件時可以用 T 類型的值表示常量 x:
-
T 值的集合包括 x
-
T 是浮點型,而 x 在沒有溢出的情況下能夠近似成 T 類型。近似規則使用
IEEE 754 round-to-even
,負零和無符號的零相同。需要注意的是,常量的值不會為負零,NaN,或無限值。 -
T 為複數類型,並且 x 的
real(x)
和imag(x)
部分由複數類型對應的浮點類型(float32
或float64
)組成。
x T x 可以表示 T 的值,因為:
'a' byte 97 在 byte 類型值的集合中
97 rune rune 是 int32 的別名,97 在 32 位整型值的集合中
"foo" string "foo" 在字符串值的集合中
1024 int16 1024 在 16 位整型值的集合中
42.0 byte 42 在 8 位無符號整型值的集合中
1e10 uint64 10000000000 在 64 位無符號整型值的集合中
2.718281828459045 float32 2.718281828459045 的近似值 2.7182817 在 float32 類型值的集合中
-1e-1000 float64 -1e-1000 的近視值 IEEE -0.0,等於 0
0i int 0 是整型值
(42 + 0i) float32 42.0 (0 虛部) 在 float32 類型值的集合中
x T x 不能表示 T 的值,因為:
0 bool 0 不在布爾值的集合中
'a' string 'a' 是 rune 類型, 它不在字符串類型的值集合中
1024 byte 1024 不在 8 位無符號整型值的集合中
-1 uint16 -1 不在 16 位無符號整型值的集合中
1.1 int 1.1 不是整型值
42i float32 (0 + 42i) 不在 float32 類型值的集合中
1e1000 float64 1e1000 取近似值時會溢出成 IEEE
代碼塊#
代碼塊是用大括號括起來的聲明和語句。
Block = "{" StatementList "}" .
StatementList = { Statement ";" } .
除了源碼中顯式的代碼塊,也有一些隱式的代碼塊。
-
包含所有的 Go 代碼的全局代碼塊。
-
包含所有包的代碼的包代碼塊。
-
包含文件內的所有代碼的文件代碼塊。
-
每個 if,switch 和 for 的範圍都會形成隱式的塊。
-
每個 switch 和 select 條件都有自己的代碼塊。
代碼塊可以嵌套並且影響作用域。
聲明和作用域#
一段聲明可以給常量,類型,變量,函數,標籤,和包綁定標識符。程序中每個標識符都需要聲明。相同標識符不能在同一個代碼塊中聲明 2 次。並且相同標識符不能同時在文件和 package 代碼塊中聲明。
空標識符可以和其他標識符一樣在聲明中使用。不過它不綁定標識符,等於沒有聲明。在 package 代碼塊中 init
標識符只能用做 init
函數的標識符,就像空標識符一樣,它不會引入新的綁定。
Declaration = ConstDecl | TypeDecl | VarDecl .
TopLevelDecl = Declaration | FunctionDecl | MethodDecl .
聲明過的標識符的作用域就是聲明標識符所在的作用域。
go 使用塊來規定詞彙的方位:
-
預定義的標識符具有全局作用域。
-
所有定義的頂級標識符具有包作用域。
-
import 進來的包的名字標識符具有文件作用域。
-
方法的接收者,函數參數,返回值變量具有函數作用域。
-
函數內定義的參量和變量標識符的作用域是標識符被聲明到容納他的塊結束。
一個代碼塊中聲明的標識符可以在它內部的代碼塊中重新聲明。在內部代碼塊的作用域中標識符表示在內部代碼塊中聲明的實體。
pakcage 語句不屬於聲明。包名不會出現在任何的作用域中。它的作用只是用來標識屬於相同包的多個文件並在導入時指定默認包名。
標籤的作用域#
可以使用標籤語句來聲明標籤,並且可以在 break
,continue
,goto
語法中使用。如果只聲明但沒有使用標籤時非法的。標籤的作用域只有定義時的函數體,早遞歸函數體中沒有作用。
空標識符#
空標識符使用下劃線 _
代表。與一般的非空標識符不同,它作為匿名標識符在聲明,運算元和賦值語句中都有特殊含義。
預定義的標識符#
以下標識符已經在全局作用域中預先聲明:
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
導出標識符#
標識符可以導出供其他包使用。在以下兩種情況同時滿足時標識符是導出的:
- 標識符的首字母是大寫(Unicode 的
Lu
類) - 標識符聲明在包作用域或者它是字段名 / 方法名。
其他任何標識符都不是導出的。
標識符的唯一性#
給定一個標識符集合,一個標識符與集合中的每個標識符都不相同,那就認為這個標識符是唯一的。假設有兩個標識符,如果它們的拼寫不同,或者它們在不同的包中並沒有導出,那它們就是不同標識符。相反,其他情況下都認為標識符是相同的。
常量聲明#
常量聲明使用常量表達式綁定一系列標識符。標識符的數量必須等於表達式的數量。左側第 n 個標識符綁定右側第 n 個表達式的值。
ConstDecl = "const" ( ConstSpec | "(" { ConstSpec ";" } ")" ) .
ConstSpec = IdentifierList [ [ Type ] "=" ExpressionList ] .
IdentifierList = identifier { "," identifier } .
ExpressionList = Expression { "," Expression } .
如果給定類型,常量會指定類型,並且表達式的值必須能對這個類型進行賦值。
如果沒有給定類型。常量會轉換成相應的表達式類型。如果表達式的值是無類型常量,那麼聲明的常量也是無類型的,並且常量的標識符代表常量的值。例如:即使小數部分是 0,只要表達式是浮點數字面值,常量標識符也表示為浮點數常量。
const Pi float64 = 3.14159265358979323846
const zero = 0.0 // 無類型浮點數常量
const (
size int64 = 1024
eof = -1 // 無類型整型常量
)
const a, b, c = 3, 4, "foo" // a = 3, b = 4, c = "foo", 無類型整型和字符串常量
const u, v float32 = 0, 3 // u = 0.0, v = 3.0
括號內的常量聲明列表的表達式除了第一個必須聲明其他表達式可以不寫。空的表達式列表的值和類型都和前面的非空表達式相同。缺省的表達式列表等價於重複之前的表達式。標識符的數量必須等於表達式的數量。iota
常量生成器是一個可以快速生成序列值的機制。
const (
Sunday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Partyday
numberOfDays // 非導出常量
)
Iota#
在常量聲明中,預定義的標識符 iota
表示連續的無類型整型常量。它的值為常量聲明中每個常量定義的位置(從零開始)。它能夠用來生成一個關聯常量集合:
const ( // iota is reset to 0
c0 = iota // c0 == 0
c1 = iota // c1 == 1
c2 = iota // c2 == 2
)
const ( // iota is reset to 0
a = 1 << iota // a == 1
b = 1 << iota // b == 2
c = 3 // c == 3 (沒有使用 iota 不過它的值依然遞增)
d = 1 << iota // d == 8
)
const ( // iota is reset to 0
u = iota * 42 // u == 0 (無類型整型常量)
v float64 = iota * 42 // v == 42.0 (float64 類型常量)
w = iota * 42 // w == 84 (無類型整型常量)
)
const x = iota // x == 0 (iota 被重置)
const y = iota // y == 0 (iota 被重置)
根據定義,在同一個常量定義中多次使用 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)
)
最後一個例子利用了最後一個非空表達式列表的隱式重複。
類型聲明#
類型聲明為類型綁定一個標識符。類型聲明有 2 種方式:類型聲明和別名聲明。
TypeDecl = "type" ( TypeSpec | "(" { TypeSpec ";" } ")" ) .
TypeSpec = AliasDecl | TypeDef .
Alias 聲明#
別名聲明給指定類型綁定一個標識符名稱。
AliasDecl = identifier "=" Type .
在標識符作用域內,它作為類型的別名。
type (
nodeList = []*Node // nodeList 和 []*Node 是相同類型
Polar = polar // Polar 和 polar 表示相同類型
)
Type 定義#
類型定義會創建一個新類型並綁定一個標識符,新類型與給定類型具有相同的底層類型和操作。
TypeDef = identifier Type .
這個類型叫做定義類型,它和其他所有類型都不相同,包括創建它的類型。
type (
Point struct{ x, y float64 } // Point 和 struct{ x, y float64 } 是不同類型
polar Point // polar 和 Point 表示不同類型
)
type TreeNode struct {
left, right *TreeNode
value *Comparable
}
type Block interface {
BlockSize() int
Encrypt(src, dst []byte)
Decrypt(src, dst []byte)
}
定義類型可以關聯該類型的方法。它不會繼承原來類型的任何方法。但是接口類型的方法集和類型的結構沒有改變。
// Mutex 是一個擁有 Lock 和 Unlock 兩個方法的數據類型。
type Mutex struct { /* Mutex fields */ }
func (m *Mutex) Lock() { /* Lock implementation */ }
func (m *Mutex) Unlock() { /* Unlock implementation */ }
// NewMutex 與 Mutex 結構相同不過方法集為空。
type NewMutex Mutex
// PtrMutex 的底層類型 *Mutex 的方法集沒有改變,
// 但是 PtrMutex 的方法集為空。
type PtrMutex *Mutex
// *PrintableMutex 包含嵌入字段 Mutex 的 Lock 和 Unlock 方法。
type PrintableMutex struct {
Mutex
}
// MyBlock 是與 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(fd) // os.Pipe() 返回兩個值
_, y, _ := coord(p) // coord() 返回三個值,我們只關注 y
和常規變量聲明不同,即使之前在相同代碼塊中聲明過的變量,也可以在短變量重新聲明相同類型的變量,並且保證至少會有一個新的非空變量。總之,只應該在多變量短聲明的時候重新聲明變量,重新聲明並不會使用新的變量,而是給變量分配新值。
field1, offset := nextField(str, 0)
field2, offset := nextField(str, offset) // 重新聲明 offset
a, a := 1, 2 // 非法:聲明了 a 兩次並且沒有新的變量
短變量聲明只能在函數中使用,例如在 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
}
}
// 無效:缺少 return 語句
}
函數聲明可以沒有函數體。這樣的聲明提供一個函數聲明,並由其他外部實現,例如匯編腳本。
func min(x int, y int) int {
if x < y {
return x
}
return y
}
func flushICache(begin, end uintptr) // 由外部實現
方法聲明#
方法是一個帶接收者的函數,方法聲明為方法綁定標識符作為方法名並指定方法對應的接收者類型。
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
}
為類型 *Point
綁定了 2 個方法 Length
和 Scale
。
方法的類型就是以接收者作為第一個參數的函數類型,例如 Scale
方法:
func(p *Point, factor float64)
但是以這種方式聲明的函數並不是方法。
表達式#
表達式通過針對運算元使用運算符和函數來獲取計算值。
運算元#
運算元代表表達式中的一個簡單的。運算元可以是字面值,非空標識符。或括號表達式。
空標識符只能出現在賦值聲明的左側。
Operand = Literal | OperandName | MethodExpr | "(" Expression ")" .
Literal = BasicLit | CompositeLit | FunctionLit .
BasicLit = int_lit | float_lit | imaginary_lit | rune_lit | string_lit .
OperandName = identifier | QualifiedIdent.
修飾標識符#
修飾標識符是以包名作為前綴修飾的標識符。包名和標識符都不能為空。
QualifiedIdent = PackageName "." identifier .
修飾標識符可以用來訪問不同包(需要先導入)中的標識符。標識符必須是導出的並在包級代碼塊聲明才能夠被訪問。
math.Sin // 表示 math 包中的 Sin 函數
複合字面值#
複合字面值能為結構體、數組、切片和 map 初始化值。它每次只能創建一個值。字面值由一個字面值類型和使用括號括起來的元素列表組成。元素前也可以聲明元素對應的鍵。
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 .
字面值類型的底層類型必須是一个結構體,數組,切片或 map 類型(如果沒有指定類型名就會強制執行這個約束)。元素的類型和鍵都必須能夠分配給相應的字段的元素和鍵類型;沒有額外的類型轉換。鍵可以表示結構體的字段名,切片和數組的索引,map 類型的鍵。對於 map 字面值,所有的元素都必須有鍵。如果相同字段名或常量值的鍵對應多個元素就會報錯。如果 map 類型的鍵為非常量類型,請看求值順序章節。
結構體字面值遵循以下規則:
-
在結構體中,鍵必須是它的字段名。
-
不包含任何鍵的元素列表的順序需要與結構體字段的聲明順序相同。
-
如果一個元素指定了鍵,那麼所有的元素都必須指定鍵。
-
包含鍵的元素列表不需要指定結構體的每個字字段,缺省字段會使用字段類型