中文字幕精品亚洲无线码二区,国产黄a三级三级三级看三级,亚洲七七久久桃花影院,丰满少妇被猛烈进入,国产小视频在线观看网站

go~基本知(zhi)識參考

介紹

這是一個(ge) Go 語(yu)言的參考手冊,你(ni)也可以(yi)訪問獲取更多信息(xi)和其他文檔。

Go 是在設計時考慮了系統編程(cheng)(cheng)的(de)通用型編程(cheng)(cheng)語言。它是強類型,有(you)垃圾(ji)回(hui)收機制并原生支持并發編程(cheng)(cheng)。Go 程(cheng)(cheng)序(xu)由一個(ge)或多個(ge) package 組(zu)成(cheng),這(zhe)樣可以高效(xiao)的(de)管(guan)理依賴。

Go 的語法簡(jian)潔且有(you)規則(ze),這讓自動化工具可以很(hen)容易(yi)的分析代(dai)碼,例如(ru):集成開發環境(jing)。

標記

語法采用(yong)擴展巴科斯(si)范式。

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

產(chan)生(sheng)式(shi)是由詞法單元和以下操作符構成的(de)表達式(shi)(優先級依次遞增):

|   或
()  分組
[]  可選 (出現 0 或 1 次)
{}  可重復 (出現 0 到 n 次)

小寫的產生式名(ming)稱用來與詞法單(dan)元區分。非終結符采(cai)用駝(tuo)峰式。詞法單(dan)元由(you)雙(shuang)引(yin)號或反(fan)引(yin)號組成(cheng)。

a...b 表示從 ab 之間的任意字符。省略號 ... 也可以在規范中表示對更詳細的枚舉和代碼片段的省略。字符 ... 不是 Go 語言的詞法單元(yuan)。

源碼表示法

Go 的源(yuan)代(dai)碼使(shi)用 UTF-8 編碼的 Unicode 文(wen)本。不過它(ta)并不是完全規(gui)(gui)范化的,單重音(yin)的代(dai)碼點(dian)與由相同字符和音(yin)標組成的代(dai)碼點(dian)是不同的;前者我們認(ren)為它(ta)是兩個(ge)代(dai)碼點(dian)。簡單來講,文(wen)檔會在(zai)源(yuan)代(dai)碼文(wen)本中(zhong)使(shi)用非規(gui)(gui)范的術語字符來表示一個(ge) Unicode 代(dai)碼點(dian)。

每個代碼點都(dou)是不同(tong)的(de);相同(tong)字符的(de)大寫和小(xiao)寫形(xing)式表示(shi)不同(tong)的(de)字符。

實現限制:為了兼容其他(ta)工(gong)具,編(bian)譯器不允許(xu)出現 Utf-8 編(bian)碼的(de)源文本中的(de) NUL 字符(U+0000)。

實現限制:為了兼容其他工具,如果源文本(ben)(ben)中(zhong)是以Utf-8 編碼的(de)(de)字(zi)節(jie)序(xu)(xu)標(biao)記(ji)(U+FEFF)為起始代碼點。編譯器會忽略(lve)它。字(zi)節(jie)序(xu)(xu)標(biao)記(ji)不應出現在源文本(ben)(ben)的(de)(de)任何(he)位置(zhi)。

字符

這(zhe)些單(dan)詞(ci)表示 Unicode 字(zi)符的(de)類(lei)別(bie):

newline        = /* Unicode 代碼點 U+000A */ .
unicode_char   = /* 排除換行以外的任意 Unicode 代碼點 */ .
unicode_letter = /* 一個字母("Letter")類型的 Unicode 代碼點  */ .
unicode_digit  = /* 一個數字("Number, decimal digit")類型的 Unicode 代碼點  */ .

在 Unicode8.0 標準中,第 4.5 章節 “一般類(lei)別” 中定義(yi)了字(zi)(zi)(zi)符(fu)(fu)的類(lei)別。Go 能夠處理任何字(zi)(zi)(zi)符(fu)(fu)集,包括(kuo) Lu,Li,Lt,Lm 或 Lo 作為 Unicode 字(zi)(zi)(zi)母,還可(ke)以把(ba)數字(zi)(zi)(zi)字(zi)(zi)(zi)符(fu)(fu)集 Nd 當作 Unicode 數字(zi)(zi)(zi)處理。

字母和數字

我們認為下劃線 _ (U+005F)是一個字(zi)母:

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

詞匯元素

注釋

注(zhu)釋(shi)是程序的說明文檔(dang)。在 Go 中有(you)兩(liang)種(zhong)形(xing)式(shi):

  • 單行注釋從 // 開始直到行末結束。
  • 通用注釋從 /* 開始直到 */ 結束。

注釋不(bu)能嵌套在其(qi)他注釋、字(zi)符(fu)串和(he) rune 的字(zi)面值中。不(bu)包含換行(xing)(xing)符(fu)的通用注釋之間通過空格符(fu)連接,其(qi)他情(qing)況下每段注釋都(dou)會另起(qi)一行(xing)(xing)。

詞匯元素

詞匯(hui)元(yuan)素(su)構成了(le) Go 語言的(de)(de)詞匯(hui)表(biao)。它有(you)四種(zhong)類(lei)型:標(biao)識符(fu)(fu)、關鍵(jian)字、操作符(fu)(fu)/標(biao)點符(fu)(fu)號、字面(mian)值。空白(bai)符(fu)(fu)可(ke)以(yi)是空格(U+0020)、水平制(zhi)表(biao)符(fu)(fu)(U+0009)、換行(xing)符(fu)(fu)(U+000D)或換行(xing)符(fu)(fu)(U+000A)。它本(ben)身會被忽略,一般用來區分(fen)(fen)不同的(de)(de)詞匯(hui)元(yuan)素(su)。換行(xing)符(fu)(fu)或文件終止符(fu)(fu)(EOF)還可(ke)能觸發(fa)編譯程(cheng)序(xu)在源代碼(ma)的(de)(de)行(xing)末(mo)或文件末(mo)尾追加(jia)分(fen)(fen)號。在分(fen)(fen)解源代碼(ma)的(de)(de)詞匯(hui)元(yuan)素(su)的(de)(de)過程(cheng)中,會把當(dang)前可(ke)以(yi)形成有(you)效詞匯(hui)元(yuan)素(su)的(de)(de)最長字符(fu)(fu)序(xu)列作為下一個詞匯(hui)元(yuan)素(su)。

分號

正(zheng)規(gui)語法在很多產生(sheng)式(shi)中使用分號(hao) ";" 作為終(zhong)結符。Go 程序中遵循下面兩(liang)條規(gui)則省略了大部分的分號(hao):

  1. 當某行的最后一個詞匯元素是以下元素時自動補全分號:
  • 一個標識符。

  • 一(yi)個整(zheng)數(shu),浮(fu)點數(shu),虛數(shu),rune 或字符串字面值。

  • 關鍵字 breakcontinuefallthroughreturn 其中之一。

  • 操作符/標點符號 ++--)]} 其中之一。

  1. 為了支持獨占一行的復雜語句,會省略與 ")" 或 "}" 相鄰的分號。

為了反應慣用(yong)用(yong)途,本篇文檔(dang)的所有例(li)子都基于(yu)以上規則省略分號。

標識符

標(biao)識符(fu)表(biao)示程序實體(ti)單元(yuan),例如:變量、類型。一(yi)個(ge)標(biao)識符(fu)由一(yi)個(ge)或(huo)多個(ge)字(zi)母和(he)數(shu)字(zi)組(zu)成。標(biao)識符(fu)的首字(zi)符(fu)必須為字(zi)母。

identifier = letter { letter | unicode_digit } .
a
_x9
ThisVariableIsExported
αβ

Go 已經預定義了(le)一些標識符。

關鍵字

以下關(guan)鍵字是(shi)預留(liu)的(de),它們不能作為標識(shi)符:

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

操作符和標點符號

以下字符(fu)(fu)序列用于表(biao)示操(cao)作符(fu)(fu)(包(bao)括賦值(zhi)運算符(fu)(fu))和標點符(fu)(fu)號(hao):

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

整型字面值

整(zheng)型(xing)字(zi)面值是一個(ge)數字(zi)序列,相當(dang)于(yu)整(zheng)型(xing)常量。可以(yi)使用前綴指定非小數進(jin)制(zhi):0 表示八進(jin)制(zhi),0x/0X 表示十(shi)六(liu)進(jin)制(zhi)。在十(shi)六(liu)進(jin)制(zhi)字(zi)面值中(zhong),字(zi)母 a-f 和 A-F 都(dou)表示數字(zi) 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 字符(fu)后(hou)接(jie)一個有符(fu)號指數(shu)構成。整(zheng)數(shu)部(bu)分(fen)和小(xiao)數(shu)部(bu)分(fen)可以(yi)省略其(qi)一;小(xiao)數(shu)點和指數(shu)部(bu)分(fen)可以(yi)省略其(qi)一。

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

虛數字面值

虛(xu)數(shu)字面值是一個小(xiao)數(shu),相(xiang)當(dang)于復數(shu)常量中的虛(xu)數(shu)部(bu)分。它由浮點數(shu)或者整數(shu)后(hou)接(jie)小(xiao)寫字母(mu) i 構(gou)成。

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 類(lei)型(xing)字(zi)(zi)面值(zhi)(zhi)(zhi)相(xiang)當于一個 rune 常量。它是(shi)一個表(biao)示(shi)(shi)(shi) Unicode 代碼點(dian)的(de)(de)(de)整數。rune 類(lei)型(xing)字(zi)(zi)面值(zhi)(zhi)(zhi)表(biao)示(shi)(shi)(shi)為(wei)用單(dan)引號(hao)包(bao)裹的(de)(de)(de)一個或(huo)多(duo)個字(zi)(zi)符,像(xiang) 'x' 或(huo) '\n'。在單(dan)引號(hao)中除了(le)換行符和(he)未轉義(yi)的(de)(de)(de)單(dan)引號(hao)其(qi)他(ta)的(de)(de)(de)字(zi)(zi)符都可以(yi)(yi)直接(jie)顯示(shi)(shi)(shi)。單(dan)引號(hao)包(bao)裹的(de)(de)(de)字(zi)(zi)符的(de)(de)(de)值(zhi)(zhi)(zhi)和(he)字(zi)(zi)符在 Unicode 編碼中的(de)(de)(de)值(zhi)(zhi)(zhi)相(xiang)等,而(er)以(yi)(yi)反(fan)斜線開頭的(de)(de)(de)多(duo)字(zi)(zi)符序(xu)列會把值(zhi)(zhi)(zhi)翻譯成多(duo)種格式。

使用引號表示單字(zi)符是(shi)最(zui)簡單的(de)(de)方式;因為 Go 的(de)(de)源文(wen)本(ben)是(shi) UTF-8 編(bian)碼,一個整(zheng)數可(ke)能代(dai)表多個 UTF-8 字(zi)節。例如, 'a' 可(ke)以使用單字(zi)節表示字(zi)符 a,Unicode 編(bian)碼 U+0061,值(zhi) 0x61,而 '?' 是(shi)兩字(zi)節表示分音符的(de)(de) a,Unicode 編(bian)碼 U+00E4,值(zhi) 0xe4。

反斜線能將任意值編碼成 ASCII 文本。有四種方式將整數值表示為數字常量:\x 后接兩個十六進制數;\u 后接四個十六進制數;\U 后接八個十六進制數。 \ 后接三(san)個(ge)八進制(zhi)數。每種情況下都使用相(xiang)應進制(zhi)來表(biao)示字面量(liang)的整數值。

雖然這四種方式都以整數表示,但它們的有效區間并不相同。八進制只能表示 0 - 255 以內的整數。十六進制滿可以滿足需求。\u\U 都可(ke)以表示 Unicode 代(dai)碼點,不過其中(zhong)的(de)一些(xie)值(zhi)是無(wu)效的(de),特別是 0x10FFFF 以上的(de)值(zhi)。

反斜(xie)線結合(he)以下字符具有特殊(shu)含義:

\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 雙引號  (只在字符串字面值中有效)

其他所有以反斜(xie)線開頭(tou)的序列在(zai) rune 的規則中都是非法(fa)的。

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 代碼點

字符串字面量

字(zi)(zi)(zi)符(fu)串(chuan)(chuan)(chuan)字(zi)(zi)(zi)面量表示從(cong)字(zi)(zi)(zi)符(fu)序列中獲取的字(zi)(zi)(zi)符(fu)串(chuan)(chuan)(chuan)常量。它有兩種格式:原始字(zi)(zi)(zi)符(fu)串(chuan)(chuan)(chuan)字(zi)(zi)(zi)面量和解釋型(xing)字(zi)(zi)(zi)符(fu)串(chuan)(chuan)(chuan)字(zi)(zi)(zi)面量。

原始字符串是由反引號包裹(foo)。字(zi)(zi)(zi)符(fu)(fu)串(chuan)中除反引(yin)號以外的(de)其他(ta)字(zi)(zi)(zi)符(fu)(fu)都(dou)會顯示出來。原(yuan)生(sheng)字(zi)(zi)(zi)符(fu)(fu)串(chuan)由反引(yin)號之間的(de)(默(mo)認 UTF-8 編碼)的(de)字(zi)(zi)(zi)符(fu)(fu)組成。它的(de)值為(wei)引(yin)號內未經解釋(默(mo)認 UTF-8 編碼)所有(you)字(zi)(zi)(zi)符(fu)(fu);尤(you)其是,反斜線再字(zi)(zi)(zi)符(fu)(fu)串(chuan)中沒(mei)有(you)特殊意(yi)義并(bing)且字(zi)(zi)(zi)符(fu)(fu)串(chuan)中保(bao)留換行(xing)符(fu)(fu)。在原(yuan)始(shi)字(zi)(zi)(zi)符(fu)(fu)串(chuan)的(de)值中會丟棄回車鍵返回 '\r' 字(zi)(zi)(zi)符(fu)(fu)。

解釋型字符串由雙引號之間的字符組成("bar")。除了換行符和雙引號其他字符都會顯示出來。雙引號之間的文本組成字面量的值。反斜線的轉義規則與 rune 字面量基本相同(不同的是 \’ 非法,而 " 合法)。三位八進制數(\nnn)和兩位十六進制數(\xnn)換碼符的值表示相應字符串的字節。其他的換碼符都表示字符各自的 UTF-8 編碼(可能是多字節)。因此字符串 \377 和 \xFF 都表示值為 0xFF=255 的單個字節,而 ?, \u00FF, \U000000FF\xc3\xbf 表示 UTF-8 編碼字符 U+00FF 的兩(liang)個(ge)字節 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 代碼點

這些例子都表(biao)示相同的字符串:

"日本語"                                 // UTF-8 文本
`日本語`                                 // UTF-8 文本作為原生字面值
"\u65e5\u672c\u8a9e"                    // 確定的 Unicode 代碼點 
"\U000065e5\U0000672c\U00008a9e"        // 確定的 Unicode 代碼點
"\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"  // 確定的 UTF-8 字節

如果源代碼中使用兩個代碼點(dian)(dian)表示一個字符(fu),例(li)如帶(dai)音標的字母(mu),把它(ta)放在(zai) rune 中會報(bao)錯(它(ta)不(bu)是單代碼點(dian)(dian))。并且在(zai)字符(fu)串(chuan)中會顯示兩個代碼點(dian)(dian)。

常量

常(chang)(chang)量(liang)分為:布爾型(xing),rune型(xing),整型(xing),浮點型(xing),復數(shu)(shu)型(xing),字(zi)符串型(xing)。其中 rune,整型(xing),浮點型(xing),復數(shu)(shu)型(xing)統稱為數(shu)(shu)字(zi)常(chang)(chang)量(liang)。

常量的值可以表示為一個 rune字面量,整數字面量,浮點數字面量,虛數字面量,字符串字面量,表示常量的標識符,常量表達式,一個轉換結果為常量的類型轉換,和一些返回值為常量的內置函數(接受任何值的unsafe.Sizeof,接受部分表達式的caplen,接受虛數常量的realimag,接受數字常量的 complex)。布爾類型的值為預定義常量 truefalse,預定義的標識符 iota 表示一個(ge)整型(xing)常(chang)量。

一般(ban)情況下(xia)復數常(chang)量是常(chang)量表(biao)達式的一種(zhong)形式。會在常(chang)量表(biao)達式章(zhang)節詳細討(tao)論。

數(shu)字常量可以表示任意精度(du)的確定值(zhi)而且不(bu)會溢(yi)出(chu)。因此(ci),沒有常量可以表示非 0,無窮(qiong)大和非數(shu)字值(zhi)。

常量可以指定類型也可以不指定類型。字面值常量,truefalseiota,和只包含無類型常(chang)量操作的常(chang)量表達式是無類型的。

常(chang)(chang)量(liang)(liang)可以通過常(chang)(chang)量(liang)(liang)聲(sheng)明(ming)和(he)轉換時(shi)顯式(shi)的(de)指(zhi)定具體(ti)類型,也可以隱(yin)式(shi)的(de)在變量(liang)(liang)聲(sheng)明(ming)、賦值或作為表達式(shi)操(cao)作元(yuan)時(shi)隱(yin)式(shi)的(de)指(zhi)定具體(ti)類型。如果常(chang)(chang)量(liang)(liang)的(de)值和(he)他(ta)的(de)類型不匹配,會報錯。

無類型常量由一個默認的類型,這個類型會根據使用常量時的上下文進行隱式轉換。例如:短變量聲明 i := 0 沒有指定 i 的類型。無類型常量的默認類型可以是:boolruneintfloat64complex128 或者 string,具體選擇哪種類型由常量的值決定。

實現(xian)限制:雖(sui)然數(shu)字常量在 Go 中(zhong)是任意(yi)精度,不過編譯(yi)器在實現(xian)時會在內部限制精度。這意(yi)味著每個編譯(yi)器實現(xian)都要(yao):

  • 至(zhi)少保證整(zheng)形常量(liang)有 256 位

  • 浮點(dian)數常量(包括(kuo)復數常量)都要保(bao)證至(zhi)少 256 位的主體部分(fen)(fen)和至(zhi)少 16 位的有(you)符號指數部分(fen)(fen)

  • 如果不(bu)能表示(shi)給定整數的精度拋出錯誤

  • 如果浮點數或復數溢出拋出錯誤

  • 如果由(you)于精度限制不(bu)能表(biao)示(shi)浮(fu)點(dian)數(shu)或者復(fu)數(shu)進行舍入

這些要求同(tong)時(shi)作用(yong)于字面量常量額和常量表達式的結(jie)果(guo)。

變量

變量是一個用來(lai)儲存值的(de)位(wei)置。根據不同的(de)變量類型,可(ke)以保存不同的(de)值。

變量聲明,函數參數和返回值,聲明的函數簽名,函數字面值都會為命名變量預留儲存空間。調用內置的 new 函數(shu)或獲取復合(he)字面值的地址(zhi)都會在運(yun)行時(shi)為變量(liang)分配存(cun)儲(chu)空間(jian)(jian)。這(zhe)種匿名變量(liang)是(shi)通過(可能(neng)是(shi)隱式的)指(zhi)針(zhen)間(jian)(jian)接引用的。

像數組,切片(pian)和(he)結(jie)構(gou)體類型的(de)變量,它們內部都包含很(hen)多(duo)元素或字段,而且這些元素和(he)字段都可以(yi)直接(jie)被訪(fang)問。數組和(he)切片(pian)中的(de)每個(ge)元素的(de)行為和(he)單獨的(de)變量基(ji)本相同。

變量的靜態類型可以通過變量聲明、提供給 new 的(de)類(lei)(lei)型(xing)(xing)(xing)、復合字面(mian)值、結構體變(bian)量(liang)聲(sheng)明(ming)的(de)元素類(lei)(lei)型(xing)(xing)(xing)以(yi)上幾種方式確(que)定。通過(guo)new或者(zhe)類(lei)(lei)型(xing)(xing)(xing)初始化。接(jie)口(kou)類(lei)(lei)型(xing)(xing)(xing)的(de)變(bian)量(liang)也有一個明(ming)確(que)的(de)動(dong)態類(lei)(lei)型(xing)(xing)(xing),這個動(dong)態類(lei)(lei)型(xing)(xing)(xing)是(shi)(shi)在運行時(shi)賦值給變(bian)量(liang)的(de)具體值類(lei)(lei)型(xing)(xing)(xing)(特例(li):預聲(sheng)明(ming)的(de) nil 是(shi)(shi)無(wu)類(lei)(lei)型(xing)(xing)(xing)的(de))。動(dong)態類(lei)(lei)型(xing)(xing)(xing)在程序的(de)執行過(guo)程中(zhong)可能并不相同,但是(shi)(shi)接(jie)口(kou)變(bian)量(liang)的(de)值是(shi)(shi)可以(yi)分配(pei)給相同靜態類(lei)(lei)型(xing)(xing)(xing)的(de)變(bian)量(liang)。

var x interface{}  // x 的靜態類型為 interface{} 值為 nil
var v *T           // v 的靜態類型為 *T 值為 nil
x = 42             // x 的動態類型為 int 值為 42
x = v              // x 動態類型為 *T 值為 (*T)(nil)

在(zai)表達(da)式中(zhong)使用(yong)變(bian)量可以(yi)取出變(bian)量的值(zhi)(zhi);這(zhe)個值(zhi)(zhi)就是變(bian)量最近一次(ci)被賦予(yu)的值(zhi)(zhi)。如果(guo)沒(mei)有對變(bian)量賦過值(zhi)(zhi),那么他的值(zhi)(zhi)是該類型的零值(zhi)(zhi)。

類型

類型(xing)(xing)是一(yi)個(ge)集(ji)合,集(ji)合包括值(zhi)和針對(dui)值(zhi)的操作&方法。一(yi)個(ge)類型(xing)(xing)可(ke)以使用類型(xing)(xing)名來表(biao)示。類型(xing)(xing)有多種表(biao)現(xian)形式:如果存在類型(xing)(xing)名,可(ke)以使用類型(xing)(xing)名表(biao)示,或者也可(ke)以使用根據已有類型(xing)(xing)組合成的類型(xing)(xing)字面(mian)值(zhi)。

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

Go 已經預先聲(sheng)明(ming)了(le)某些類(lei)型(xing)的名稱(cheng)。并引入了(le)類(lei)型(xing)聲(sheng)明(ming)。復(fu)合類(lei)型(xing)(數(shu)組、結構體、指針、函數(shu)、接口、切片、map、channel)可(ke)以使(shi)用(yong)他們(men)的類(lei)型(xing)字(zi)面值。

每個類(lei)(lei)型(xing)(xing)T都有一個底層類(lei)(lei)型(xing)(xing)。如果T是(shi)預定(ding)義(yi)類(lei)(lei)型(xing)(xing)或(huo)者類(lei)(lei)型(xing)(xing)字面值。那么底層類(lei)(lei)型(xing)(xing)就是(shi)他(ta)自身。否則,T的底層類(lei)(lei)型(xing)(xing)就是(shi)它(ta)再類(lei)(lei)型(xing)(xing)聲明時引用到的類(lei)(lei)型(xing)(xing)。

type (
	A1 = string
	A2 = A1
)

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

stringA1A2B1B2 的底層類型是 string[]B1B3B4 的下游類型是[]B1。

方法集

類(lei)(lei)型(xing)(xing)可(ke)能(neng)會(hui)有(you)(you)一個(ge)與之(zhi)關聯的(de)方(fang)(fang)(fang)法(fa)(fa)(fa)(fa)集(ji)。接(jie)口(kou)類(lei)(lei)型(xing)(xing)的(de)方(fang)(fang)(fang)法(fa)(fa)(fa)(fa)集(ji)就可(ke)以使用自身表示。對(dui)于其(qi)他類(lei)(lei)型(xing)(xing),類(lei)(lei)型(xing)(xing) T 的(de)方(fang)(fang)(fang)法(fa)(fa)(fa)(fa)集(ji)由所有(you)(you)接(jie)收(shou)者類(lei)(lei)型(xing)(xing)為(wei) T 的(de)方(fang)(fang)(fang)法(fa)(fa)(fa)(fa)組成(cheng)。而對(dui)應指針類(lei)(lei)型(xing)(xing) *T 的(de)方(fang)(fang)(fang)法(fa)(fa)(fa)(fa)集(ji)由所有(you)(you)接(jie)收(shou)者類(lei)(lei)型(xing)(xing)為(wei) T 或(huo) *T 的(de)方(fang)(fang)(fang)法(fa)(fa)(fa)(fa)組成(cheng)。如果(guo)是結構體類(lei)(lei)型(xing)(xing)且(qie)含有(you)(you)嵌入(ru)字段,那么方(fang)(fang)(fang)法(fa)(fa)(fa)(fa)集(ji)中可(ke)能(neng)還會(hui)包含更多的(de)方(fang)(fang)(fang)法(fa)(fa)(fa)(fa),具體請(qing)看結構體類(lei)(lei)型(xing)(xing)章節。其(qi)他類(lei)(lei)型(xing)(xing)的(de)方(fang)(fang)(fang)法(fa)(fa)(fa)(fa)集(ji)都(dou)為(wei)空。方(fang)(fang)(fang)法(fa)(fa)(fa)(fa)集(ji)中的(de)每個(ge)方(fang)(fang)(fang)法(fa)(fa)(fa)(fa)都(dou)有(you)(you)唯一且(qie)不為(wei)空的(de)方(fang)(fang)(fang)法(fa)(fa)(fa)(fa)名。

類型的方(fang)法(fa)集用來確定類型實現的接(jie)口和以類型作(zuo)為接(jie)收者能夠調(diao)用的方(fang)法(fa)。

布爾類型

布爾類型表示預定義常量 truefalse 表示布爾真實值的集合。預定義的布爾類型為 bool;它是通過類型聲明(ming)創建(jian)的。

數字類型

一個數字(zi)類型相當于整型和浮點型的所有(you)值(zhi)的集(ji)合。預(yu)定(ding)義的數字(zi)類型包括:

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 位整(zheng)數的(de)值(zhi)具有 n 比特的(de)寬度并用補碼表示。

以(yi)下幾種預定義(yi)類型(xing)由具體平(ping)臺實(shi)現指(zhi)定長度:

uint     32 或 64 位
int      和 uint 位數相同
uintptr  能夠容納指針值的無符號整數

為了(le)避免移植性問題,除了(le)被 uint8 的(de)(de)(de)(de)別名(ming) byte 和(he) int32 的(de)(de)(de)(de)別名(ming) rune,其他(ta)所有的(de)(de)(de)(de)數字類型都(dou)是(shi)通過類型聲明定義(yi)。當在表(biao)達式(shi)中使(shi)用不同(tong)的(de)(de)(de)(de)數字類型需(xu)要進(jin)行類型轉(zhuan)換。例如:int32 和(he) int 不是(shi)相(xiang)同(tong)的(de)(de)(de)(de)類型,即(ji)使(shi)他(ta)們在指定的(de)(de)(de)(de)平臺(tai)上是(shi)相(xiang)等的(de)(de)(de)(de)。

字符串類型

字符串類型表示字符串的值類型。字符串的值是一個字節序列(有可能為空)。字符串一旦創建就無法修改它的值。預定義的字符串類型是 string,它是通過類型聲明(ming)定義的。

可以使用內置函數 len 獲取字符串長度。如果字符串是常量那么它的長度在編譯時也為常量。可以通過數字下標 0~len(s)-1 訪問字符串字節。獲取字符串的地址是非法操作;如果 s[i] 是字符串的第 i 個字節,那么 &s[i] 是無效的。

數組類型

數(shu)組是一(yi)定數(shu)量(liang)的(de)單(dan)(dan)一(yi)類型(xing)元(yuan)素(su)序列,而(er)這(zhe)個單(dan)(dan)一(yi)類型(xing)叫做元(yuan)素(su)類型(xing)。元(yuan)素(su)的(de)個數(shu)表示元(yuan)素(su)的(de)長度,它永遠不是負數(shu)。

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

長度是數組類型的一部分;它是一個類型為 int 的非負常量。可以用內置函數 len 獲取數組的長度。元素可以通過下標 0~len(a)-1 訪問。數組(zu)一般都是(shi)一維的,不過也可(ke)以是(shi)多維的。

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

切片類型

切(qie)片(pian)描述了底(di)層數(shu)組(zu)(zu)的(de)(de)一(yi)個(ge)連續片(pian)段并提(ti)供對連續片(pian)段內(nei)元(yuan)素的(de)(de)訪問(wen)。切(qie)片(pian)類型表示(shi)(shi)元(yuan)素類型的(de)(de)數(shu)組(zu)(zu)的(de)(de)所有(you)切(qie)片(pian)的(de)(de)集合。沒有(you)被初始化的(de)(de)切(qie)片(pian)用 nil 表示(shi)(shi)。

SliceType = "[" "]" ElementType .

與數組一樣,切片的可以使用索引訪問并且有長度,切片的長度可以通過內置的 len 函數獲取;與數組不同的是它的長度在運行時是可以變化的。我們可以通過下標 0~len(s)-1 來(lai)訪(fang)問切(qie)片(pian)內的元素(su)。切(qie)片(pian)的索(suo)(suo)引可能會小(xiao)于相同(tong)元素(su)再底層數組(zu)的索(suo)(suo)引。

切(qie)片(pian)一(yi)旦初始(shi)化,那么就有(you)一(yi)個(ge)與(yu)之對應的(de)底層數組(zu)保存(cun)切(qie)片(pian)中(zhong)的(de)元素。切(qie)片(pian)和底層的(de)數組(zu)還有(you)其他指向該數組(zu)的(de)切(qie)片(pian)共(gong)享相同(tong)(tong)的(de)儲存(cun)空間;而不(bu)同(tong)(tong)的(de)數組(zu)總是有(you)著不(bu)同(tong)(tong)的(de)存(cun)儲空間。

切片的底層數組可能會延伸到切片末尾以外,切片的容積等于切片現在的長度加上數組中切片還沒使用的長度;可以從原始切片中切出一個長度與容量相等的切片。切片的容量可以通過內置的 cap(a) 函數來獲取。可以通過函數make來(lai)創建一(yi)個T類(lei)型的新切片。

使用內置函數 make 可以出實話給定元素類型 T 的切片。make 函數接收三個參數:切片類型、切片長度、切片容積,其中切片容積是可選參數。make 創建的切片(pian)會在底層分(fen)配一個切片(pian)所引(yin)用(yong)的新數(shu)組。

make([]T, length, capacity)

make 的作用就是創建(jian)新數組并(bing)切分它,所(suo)以下面(mian)兩種寫法是等價的:

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

與數(shu)組(zu)相同,切(qie)片(pian)一般是(shi)一維的,不(bu)過(guo)也(ye)可以復合成多維。數(shu)組(zu)中(zhong)(zhong)的數(shu)組(zu)都必(bi)須是(shi)相同的長度,但是(shi)切(qie)片(pian)中(zhong)(zhong)的切(qie)片(pian)長度是(shi)動態變化的,不(bu)過(guo)切(qie)片(pian)中(zhong)(zhong)的切(qie)片(pian)需要單獨初始(shi)化。

結構體類型

結(jie)(jie)構體是一個(ge)命名(ming)(ming)元素序列,命名(ming)(ming)元素也叫做字(zi)(zi)(zi)段,每個(ge)字(zi)(zi)(zi)段都對應(ying)一個(ge)名(ming)(ming)稱(cheng)和類型,字(zi)(zi)(zi)段的名(ming)(ming)字(zi)(zi)(zi)可(ke)以(yi)是顯式(shi)指定的(標識(shi)符列表)也可(ke)以(yi)是隱式(shi)的(嵌入字(zi)(zi)(zi)段)。在結(jie)(jie)構體中(zhong)非空字(zi)(zi)(zi)段具(ju)有(you)唯(wei)一性。

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()
}

一個指(zhi)定(ding)(ding)了類(lei)(lei)型(xing)而沒有(you)指(zhi)定(ding)(ding)名(ming)稱(cheng)的(de)(de)字(zi)段(duan)叫做嵌(qian)入字(zi)段(duan),嵌(qian)入字(zi)段(duan)必須指(zhi)定(ding)(ding)類(lei)(lei)型(xing)名(ming) T 或指(zhi)向非接口(kou)(kou)類(lei)(lei)型(xing)的(de)(de)指(zhi)針(zhen)類(lei)(lei)型(xing) *T,其中 T 不(bu)能(neng)為指(zhi)針(zhen)類(lei)(lei)型(xing)。或者一個非接口(kou)(kou)類(lei)(lei)型(xing)的(de)(de)指(zhi)針(zhen)。并且T本身不(bu)能(neng)為指(zhi)針(zhen)類(lei)(lei)型(xing)。這種(zhong)情況(kuang)下會(hui)把類(lei)(lei)型(xing)名(ming)作為字(zi)段(duan)的(de)(de)名(ming)字(zi)。

// 一個包含 4 個嵌入字段 T1, *T2, P.T3 和 *P.T4 的結構體
struct {
	T1        // 字段名為 T1
	*T2       // 字段名為 T2
	P.T3      // 字段名為 T3
	*P.T4     // 字段名為 T4
	x, y int  // 字段名為 x 和 y
}

以(yi)下聲明是錯誤(wu)的(de)因為字段(duan)名稱(cheng)必須唯一(yi)。

struct {
	T     // 嵌入字段 *T 與 *P.T 沖突
	*T    // 嵌入字段 T 與 *P.T 沖突
	*P.T  // 嵌入字段 T 與 *T 沖突
}

如果 x.f 是表示該字段或方法 f 的合法選擇器,則會調用結構 x 中嵌入字段的字段或方法 f

從嵌(qian)入字(zi)段(duan)組合來的(de)字(zi)段(duan)與結(jie)構體(ti)原來的(de)字(zi)段(duan)行為基本相(xiang)同,只是不(bu)能在(zai)結(jie)構體(ti)的(de)復合字(zi)面值(zhi)中直接(jie)使用。

給(gei)定一個結構體 S 和一個類型 T,依據以下規則(ze)生成組合后(hou)的(de)方法集:

  • 如果 S 包含嵌入字段 T,則 S 和 *S 的方法集包括接收者為 T 的方法集,而 *S 包括 接收者為 *T 的方法集。
  • 如果 S 包含字段 T。那么S和S均包含接收者為 T 和 *T 的所有方法集。

聲明字段(duan)時可(ke)以給該(gai)字段(duan)添加一個(ge)字符(fu)串(chuan)的(de) tag。這個(ge) tag 將會成為它所對應字段(duan)的(de)一個(ge)屬性。空 tag 和缺省(sheng) tag 是相(xiang)同的(de)。tag 的(de)值可(ke)以通(tong)過反射的(de)接(jie)口獲取,可(ke)以作為類型(xing)結構體(ti)的(de)類型(xing)定(ding)義的(de)一部分,也可(ke)以忽略。

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"`
}

指針類型

指(zhi)(zhi)針(zhen)類(lei)型表示所有(you)指(zhi)(zhi)向給(gei)定(ding)(ding)類(lei)型變(bian)量(liang)的(de)(de)指(zhi)(zhi)針(zhen)集合。這個指(zhi)(zhi)定(ding)(ding)的(de)(de)類(lei)型叫做指(zhi)(zhi)針(zhen)的(de)(de)基礎類(lei)型。沒有(you)初始化(hua)的(de)(de)指(zhi)(zhi)針(zhen)值(zhi)為nil。

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

函數類型

函數(shu)類(lei)型(xing)可以(yi)表示所有(you)(you)具有(you)(you)相同(tong)參數(shu)類(lei)型(xing)和返(fan)回值類(lei)型(xing)的(de)函數(shu)。未初始化的(de)函數(shu)類(lei)型(xing)值為 nil。

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

在參數(shu)(shu)和返(fan)回(hui)值列(lie)表(biao)(biao)中(zhong),標識符(fu)列(lie)表(biao)(biao)必須同時存在或缺省(sheng)。如果存在,那么每個(ge)(ge)名(ming)字都(dou)表(biao)(biao)示指(zhi)(zhi)定類(lei)型的(de)一(yi)個(ge)(ge)參數(shu)(shu)/返(fan)回(hui)值,這(zhe)些標識符(fu)必須非空并且不(bu)能重復。如果缺省(sheng),指(zhi)(zhi)定類(lei)型的(de)參數(shu)(shu)/返(fan)回(hui)值使用(yong)(yong)對(dui)應的(de)類(lei)型表(biao)(biao)示。參數(shu)(shu)列(lie)表(biao)(biao)和返(fan)回(hui)值列(lie)表(biao)(biao)一(yi)般都(dou)是(shi)需要加括號(hao),不(bu)過在只(zhi)有一(yi)個(ge)(ge)缺省(sheng)返(fan)回(hui)值時,它可(ke)以不(bu)使用(yong)(yong)括號(hao)。

函數的最后一個參數可以添加前綴 ...。包(bao)含這種參數(shu)的函數(shu)叫做變參函數(shu),它可以(yi)接收零個(ge)或多(duo)個(ge)參數(shu)。

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)

接口類型

接(jie)口類型指定了一個(ge)(ge)方法(fa)集。一個(ge)(ge)接(jie)口類型變量(liang)可以(yi)保存任何方法(fa)集是該接(jie)口超集的類型。我們可以(yi)認為(wei)類型實現了接(jie)口。沒(mei)有初始化(hua)的接(jie)口類型值為(wei) nil。

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

在接口類型(xing)的方法(fa)(fa)集中(zhong),每(mei)個方法(fa)(fa)的名稱(cheng)必須是非空且唯一。

// 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 也可以表示 S2S1S2 都實現了接口 File,而不用管類型是否(fou)還有其(qi)他方(fang)法。

一個類(lei)型(xing)實現了任何方法集的為其(qi)子集的接口(kou)(kou)。因此它可能(neng)實現了多個不同接口(kou)(kou)。例如:所(suo)有的類(lei)型(xing)都實現了空接口(kou)(kou):

interface{}

與之相似,思考下面這個定義為 Locker 的接口:

type Locker interface {
	Lock()
	Unlock()
}

如果 S1S2 也實現了它:

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

那它們就實現了兩個接口 LockerFile

一(yi)個接(jie)口(kou) T 可以使用另一(yi)個接(jie)口(kou) E 來指(zhi)定方(fang)法(fa)。這種方(fang)式叫做將接(jie)口(kou) E 嵌入進接(jie)口(kou) T。它把 E 中所有的方(fang)法(fa)(包括導(dao)(dao)出和未導(dao)(dao)出的方(fang)法(fa))全(quan)部(bu)添(tian)加進接(jie)口(kou) 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 不(bu)能遞歸的嵌入進自己或已經嵌入過(guo)它(ta)的接口。

// 無效: Bad 不能嵌入它自己
type Bad interface {
	Bad
}

// 無效: Bad1 不能嵌入已經引用它的 Bad2
type Bad1 interface {
	Bad2
}
type Bad2 interface {
	Bad1
}

Map類型

map 類型是一(yi)種以唯一(yi)值(zhi)作為鍵的無序集合。

MapType     = "map" "[" KeyType "]" ElementType .
KeyType     = Type .

map的鍵類型必須能使用比較運算符 ==!= 進行(xing)比較。因此(ci)它的(de)鍵類型(xing)不能是函數(shu),map,或者(zhe)切片。如(ru)果鍵是接口類型(xing),那么比較運(yun)算符必須能比較他的(de)動態值(zhi)。如(ru)果不能會(hui)拋出一個運(yun)行(xing)時錯(cuo)誤。

map[string]int
map[*T]struct{ x, y float64 }
map[string]interface{}

map中元素的個數叫做它的長度。對于一個map m。它的長度可以通過內置函數 len 獲得,而且它的長度可能再運行時發生變化。map 可以再運行時添加和取回元素,頁可以使用內置函數 delete移除元素。

可以使用內置函數 make 初始(shi)化一個新(xin)的且為空的 map。它能(neng)指定 map 的類型和預留的空間:

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

map 的(de)(de)預(yu)留空間(jian)不(bu)會固定住 map 的(de)(de)長度;它可以通過添加一定數(shu)量的(de)(de)元素來增加自己(ji)的(de)(de)長度(nil map 不(bu)能(neng)添加元素)。nil map 和空 map 是相等的(de)(de),只是 nil map 不(bu)能(neng)添加元素。

Channel類型

channel提供(gong)一種手段在并發執行的函數間發送(song)和接收指定類型的值。沒有(you)初始化(hua)的 channel 是nil。

ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .

操作符 <- 可以指(zhi)定 channel 的數據流(liu)動方向。如果(guo)沒有指(zhi)定方向,channel 默認是(shi)雙向的。channel 可以通過轉換和(he)賦(fu)值來限(xian)制(zhi)只讀和(he)只寫(xie)。

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 函數可(ke)以指定(ding)channel的類型和容量。

make(chan int, 100)

容量是(shi)設置了最大(da)能緩(huan)存(cun)元素的(de)數量。如果沒有(you)(you)設置容量或(huo)值為(wei)(wei) 0,channel 就是(shi)沒有(you)(you)緩(huan)存(cun)的(de),這時只有(you)(you)當(dang)發送(song)者和接(jie)收者都準備好后才會(hui)傳(chuan)輸(shu)數據。而帶緩(huan)存(cun)的(de) channel 在(zai)緩(huan)存(cun)沒有(you)(you)滿的(de)時候依(yi)然可以成(cheng)功發送(song)數據,當(dang)緩(huan)存(cun)不(bu)為(wei)(wei)空的(de)時候可以成(cheng)功接(jie)收到(dao)數據,值為(wei)(wei) nil 的(de) channel 不(bu)能傳(chuan)輸(shu)數據。

可以通過內置函數 close 關閉 channel。在(zai)接(jie)收(shou)端的第二個(ge)返(fan)回值可以用來(lai)提示接(jie)收(shou)者在(zai)關閉的 channel 是(shi)否還(huan)包含數據。

channel 可以在發送語句,接收操作中使用。可以不考慮同步性直接在多個 goroutine 中對 channel 調用內置函數 lencap 。channel 的(de)(de)行為和 FIFO 隊列相同。舉個例(li)子,一個 goruntine 發(fa)(fa)送(song)數(shu)據(ju),另(ling)一個 goruntine 接(jie)收他們,接(jie)收數(shu)據(ju)的(de)(de)順序和發(fa)(fa)送(song)數(shu)據(ju)的(de)(de)順序是相同的(de)(de)。

類型的屬性和值

類型標識

兩個類型可能相同也可能不同。

定義的(de)類(lei)(lei)型都是不同(tong)(tong)類(lei)(lei)型。如(ru)果兩個類(lei)(lei)型的(de)底層類(lei)(lei)型在結構上是相同(tong)(tong)的(de),那它們也是相等的(de)。總的(de)來說:

  • 2 個數組的長度(du)和元素類型相同,那么它們就(jiu)是相同類型。

  • 如果兩個切片的元素類(lei)型相同(tong)那么它們就是相同(tong)類(lei)型。

  • 如果兩個結構體字(zi)(zi)(zi)段順序相(xiang)(xiang)同(tong)(tong),并且(qie)字(zi)(zi)(zi)段名稱、字(zi)(zi)(zi)段類型和 tag 都相(xiang)(xiang)同(tong)(tong)那么它們就是相(xiang)(xiang)等的(de)。非(fei)導出(chu)字(zi)(zi)(zi)段的(de)字(zi)(zi)(zi)段名在不同(tong)(tong)的(de)包中總是不同(tong)(tong)的(de)。

  • 如(ru)果兩個指針的基礎類型(xing)相(xiang)同那(nei)么他(ta)們具有相(xiang)同類型(xing)。

  • 如果(guo)兩個函數具有相(xiang)同的參(can)數和返回值列表(biao),并且(qie)他們(men)的類型相(xiang)同那(nei)么他們(men)就是相(xiang)同的,參(can)數的名稱不一定要相(xiang)同。

  • 如果兩個接口的(de)方(fang)法集完全相同(方(fang)法的(de)順序(xu))。

  • 如果(guo)兩個 map 類型的鍵類型和值(zhi)類型相(xiang)同那(nei)它們就是相(xiang)等的。

  • 如(ru)果(guo)兩個 channel 類(lei)型(xing)包含的(de)對象(xiang)類(lei)型(xing)和 channel 的(de)方向都是相(xiang)同的(de)那它們就是相(xiang)同的(de)。

給出下列聲明:

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) *B0func(x int, y float64) *[]string 是不(bu)同(tong)(tong)的,因為(wei) B0 和 []string 不(bu)是相(xiang)同(tong)(tong)類(lei)型。

可分配性

在以下情況下,可以將 x 分配給類型為 T 的(de)變量(把 x 分配給 T):

  • x 的類型為 T

  • x 的類型(xing) V 和 T 有相同的底層(ceng)類型(xing)并且類型(xing) T 或 V 至(zhi)少一個定義的類型(xing)

  • T 是一(yi)個接口類型并且 x 實(shi)現(xian)了 T

  • x 是(shi)一個(ge) channel,并且(qie) T 是(shi)channel類(lei)型,類(lei)型V和類(lei)型T有相(xiang)同的(de)元素類(lei)型,并且(qie) 2 種類(lei)型至少有一種不(bu)是(shi)定義的(de)類(lei)型

  • x 等于(yu) nil 并且 T 是一(yi)個指(zhi)針,函數(shu),切片,map,channel 或接口類型

  • x 是一個(ge)可以表示 T 類型值的無類型常量

代表性

滿足以下條(tiao)件時可以用 T 類型的(de)值表(biao)示常量 x:

  • T 值(zhi)的(de)集(ji)合包括 x

  • T 是浮點型,而 x 在沒有溢出的情況下能夠近似成 T 類型。近似規則使用 IEEE 754 round-to-even,負零和無符(fu)號(hao)的(de)(de)零相同。需要注意的(de)(de)是,常(chang)量的(de)(de)值不會為負零,NaN,或無限值。

  • T 為復數類型,并且 x 的 real(x)imag(x) 部分由復數類型對應的浮點類型(float32float64 )組成。

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

代碼塊

代(dai)碼(ma)塊是用(yong)大括號括起來的聲(sheng)明和語句。

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

除了源碼中顯式(shi)的代(dai)(dai)碼塊(kuai)(kuai),也(ye)有一些隱式(shi)的代(dai)(dai)碼塊(kuai)(kuai)。

  • 包含所有的(de)(de)Go代碼(ma)的(de)(de)全局代碼(ma)塊(kuai)。

  • 包(bao)含(han)所有包(bao)的(de)代碼的(de)包(bao)代碼塊。

  • 包含文件內的(de)所有(you)代碼的(de)文件代碼塊。

  • 每個 if,switch和(he) for 的范圍都會形成(cheng)隱式的塊。

  • 每個 switch 和(he) select 條件都有(you)自己的代(dai)碼塊(kuai)。

代(dai)碼塊(kuai)可以嵌套并且影響作(zuo)用(yong)域(yu)。

聲明和作用域

一(yi)段聲(sheng)明可以給常量,類型,變量,函數,標(biao)簽,和(he)包綁定標(biao)識(shi)符。程序中每個(ge)標(biao)識(shi)符都需要聲(sheng)明。相同(tong)(tong)標(biao)識(shi)符不能在同(tong)(tong)一(yi)個(ge)代碼(ma)塊(kuai)(kuai)中聲(sheng)明2次。并且(qie)相同(tong)(tong)標(biao)識(shi)符不能同(tong)(tong)時在文件和(he) package 代碼(ma)塊(kuai)(kuai)中聲(sheng)明。

空標識符可以和其他標識符一樣在聲明中使用。不過它不綁定標識符,等于沒有聲明。在 package 代碼塊中 init 標識符只能用做 init 函數(shu)的標(biao)識(shi)符,就像空標(biao)識(shi)符一樣,它不會(hui)引入新的綁定。

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

聲(sheng)明過的(de)標(biao)識符的(de)作用域就是聲(sheng)明標(biao)識符所在的(de)作用域。

go使用塊來規定詞匯(hui)的方位(wei):

  • 預定義的標識符具有全局(ju)作用域。

  • 所(suo)有定義的(de)頂級標(biao)識符具有包作用域。

  • import進來的(de)包的(de)名字(zi)標識符具有文件(jian)作用域。

  • 方法的接收者(zhe),函數參數,返回值變量具(ju)有函數作用(yong)域。

  • 函數(shu)內定義的(de)參量(liang)和變(bian)量(liang)標(biao)(biao)識符(fu)的(de)作(zuo)用域是標(biao)(biao)識符(fu)被聲明(ming)到容納(na)他的(de)塊結束。

一個代(dai)碼(ma)塊(kuai)中(zhong)聲明的標識符可以(yi)在它內(nei)部的代(dai)碼(ma)塊(kuai)中(zhong)重(zhong)新聲明。在內(nei)部代(dai)碼(ma)塊(kuai)的作(zuo)用域中(zhong)標識符表示在內(nei)部代(dai)碼(ma)塊(kuai)中(zhong)聲明的實體。

pakcage 語句不屬于聲明(ming)。包(bao)名(ming)不會(hui)出(chu)現在(zai)任何的作(zuo)用(yong)(yong)域中。它的作(zuo)用(yong)(yong)只(zhi)是用(yong)(yong)來標識(shi)屬于相同包(bao)的多(duo)個(ge)文件并在(zai)導入時(shi)指定默認包(bao)名(ming)。

標簽的作用域

可以使用標簽語句來聲明標簽,并且可以在 breakcontinuegoto 語法中使用。如果只聲明但(dan)沒(mei)有(you)使用標(biao)簽時非(fei)法的。標(biao)簽的作用域只有(you)定義(yi)時的函數體,早遞歸函數體中沒(mei)有(you)作用。

空標識符

空標識符使用下劃線 _ 代表。與一般的非空標識(shi)符不同,它作為匿(ni)名標識(shi)符在聲明,運算元和(he)賦值語句中都有特(te)殊含義。

預定義的標識符

以下標識符已經在全局作用域(yu)中預先聲明:

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

導出標識符

標識(shi)符可以(yi)導出供其他包使用(yong)。在以(yi)下兩種(zhong)情況同(tong)時滿(man)足時標識(shi)符是導出的:

  • 標識符的首字母是大寫(Unicode 的 Lu 類)
  • 標識符聲明在包作用域或者它是字段名/方法名。

其他任何標(biao)識(shi)符都(dou)不是導(dao)出的。

標識符的唯一性

給定一個(ge)標(biao)(biao)(biao)識(shi)(shi)(shi)符集(ji)合,一個(ge)標(biao)(biao)(biao)識(shi)(shi)(shi)符與集(ji)合中的(de)每個(ge)標(biao)(biao)(biao)識(shi)(shi)(shi)符都不(bu)相同(tong),那就認(ren)為(wei)這個(ge)標(biao)(biao)(biao)識(shi)(shi)(shi)符是唯(wei)一的(de)。假設有兩個(ge)標(biao)(biao)(biao)識(shi)(shi)(shi)符,如果(guo)它(ta)們(men)(men)的(de)拼寫(xie)不(bu)同(tong),或者它(ta)們(men)(men)在不(bu)同(tong)的(de)包(bao)中并沒有導出,那它(ta)們(men)(men)就是不(bu)同(tong)標(biao)(biao)(biao)識(shi)(shi)(shi)符。相反,其(qi)他情(qing)況下都認(ren)為(wei)標(biao)(biao)(biao)識(shi)(shi)(shi)符是相同(tong)的(de)。

常量聲明

常量(liang)聲(sheng)明使用常量(liang)表達(da)式(shi)綁定(ding)一系列(lie)標(biao)(biao)識符(fu)。標(biao)(biao)識符(fu)的(de)數量(liang)必須等于表達(da)式(shi)的(de)數量(liang)。左側第 n 個(ge)標(biao)(biao)識符(fu)綁定(ding)右側第 n 個(ge)表達(da)式(shi)的(de)值。

ConstDecl      = "const" ( ConstSpec | "(" { ConstSpec ";" } ")" ) .
ConstSpec      = IdentifierList [ [ Type ] "=" ExpressionList ] .

IdentifierList = identifier { "," identifier } .
ExpressionList = Expression { "," Expression } .

如果給定類(lei)型(xing),常量會指定類(lei)型(xing),并且(qie)表達式(shi)的值(zhi)必須能(neng)對這個類(lei)型(xing)進行賦值(zhi)。

如果沒(mei)有給(gei)定(ding)類(lei)(lei)型。常(chang)(chang)(chang)量(liang)會轉換(huan)成相(xiang)應的(de)(de)(de)表達(da)式(shi)類(lei)(lei)型。如果表達(da)式(shi)的(de)(de)(de)值是(shi)無(wu)類(lei)(lei)型常(chang)(chang)(chang)量(liang),那么聲(sheng)明的(de)(de)(de)常(chang)(chang)(chang)量(liang)也(ye)是(shi)無(wu)類(lei)(lei)型的(de)(de)(de),并且常(chang)(chang)(chang)量(liang)的(de)(de)(de)標(biao)識(shi)符代(dai)表常(chang)(chang)(chang)量(liang)的(de)(de)(de)值。例如:即使小數部分是(shi) 0,只要表達(da)式(shi)是(shi)浮點(dian)數字面值,常(chang)(chang)(chang)量(liang)標(biao)識(shi)符也(ye)表示為(wei)浮點(dian)數常(chang)(chang)(chang)量(liang)。

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常(chang)量生(sheng)成器是(shi)一個(ge)可以快速(su)生(sheng)成序列值的(de)機制(zhi)。

const (
	Sunday = iota
	Monday
	Tuesday
	Wednesday
	Thursday
	Friday
	Partyday
	numberOfDays  // 非導出常量
)

Iota

在常量聲明中,預定義的標識符 iota 表示連(lian)續的無類(lei)型整型常量(liang)(liang)。它的值為常量(liang)(liang)聲明中(zhong)每個常量(liang)(liang)定義的位置(zhi)(從零開(kai)始(shi))。它能夠用來生成一(yi)個關聯常量(liang)(liang)集合(he):

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)
)

最(zui)后一個(ge)例子利用了最(zui)后一個(ge)非空表達式列表的隱式重(zhong)復。

類型聲明

類型(xing)聲(sheng)明(ming)(ming)(ming)為類型(xing)綁定一個標識符。類型(xing)聲(sheng)明(ming)(ming)(ming)有(you)2種方式:類型(xing)聲(sheng)明(ming)(ming)(ming)和別名聲(sheng)明(ming)(ming)(ming)。

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

別名聲明(ming)給指定(ding)(ding)類型綁定(ding)(ding)一個標(biao)識符名稱。

AliasDecl = identifier "=" Type .

在標識符作用域內,它作為類型的別名。

type (
	nodeList = []*Node  // nodeList 和 []*Node 是相同類型
	Polar    = polar    // Polar 和 polar 表示相同類型
)
Type 定義

類型定(ding)義(yi)會創建一個(ge)新類型并綁(bang)定(ding)一個(ge)標識符(fu),新類型與給定(ding)類型具(ju)有(you)相同的(de)底層類型和操作。

TypeDef = identifier Type .

這個類(lei)型叫做定義類(lei)型,它和其他所有(you)類(lei)型都不(bu)相同,包括創建它的類(lei)型。

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)
}

定義類型(xing)(xing)可以關聯該(gai)類型(xing)(xing)的(de)方(fang)法(fa)。它不會繼承原來類型(xing)(xing)的(de)任何方(fang)法(fa)。但(dan)是接(jie)口類型(xing)(xing)的(de)方(fang)法(fa)集和類型(xing)(xing)的(de)結構(gou)沒(mei)有(you)改變。

// 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

類(lei)型定義可(ke)以定義方法(fa)集(ji)不同(tong)的布爾(er)值、數字和字符串類(lei)型:

type TimeZone int

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

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

變量聲明

變量聲明可以創建一(yi)個(ge)或多個(ge)變量,并綁定(ding)對應的標識符、指定(ding)類型和初始值。

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"

如(ru)果給定一個表(biao)達式(shi)列表(biao)。變量會(hui)根據賦值規則使用表(biao)達式(shi)進(jin)行初始化(hua)。否則,每個變量都會(hui)初始化(hua)成(cheng)變量類型(xing)的零值。

如果指定類型,變量會為指定類型。如果沒有指定類型,變量會使用分配的初始值類型。如果初始值為無類型常量,它會轉換成初始值的默認類型。如果是一個無類型布爾值,那么變量的類型就是 bool。值 nil 不能給沒有指定類型的(de)變量賦值(zhi)。

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

實現的限(xian)制(zhi):在函數(shu)體內聲明的變量如果沒有使(shi)用過編譯器需要報錯。

短變量聲明

短變量聲明的語法:

ShortVarDecl = IdentifierList ":=" ExpressionList .

它比正常使用初始(shi)化表達式(shi)進(jin)行變量(liang)聲(sheng)明的(de)方式(shi)要短,而且不(bu)指定類型(xing):

"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

和常(chang)規變(bian)(bian)量(liang)(liang)(liang)聲(sheng)(sheng)明不同,即使(shi)之(zhi)(zhi)前在(zai)相同代(dai)碼塊中聲(sheng)(sheng)明過的(de)(de)(de)變(bian)(bian)量(liang)(liang)(liang),也可以在(zai)短變(bian)(bian)量(liang)(liang)(liang)重新(xin)聲(sheng)(sheng)明相同類型的(de)(de)(de)變(bian)(bian)量(liang)(liang)(liang),并(bing)且保證至少(shao)會有一個新(xin)的(de)(de)(de)非空變(bian)(bian)量(liang)(liang)(liang)。總之(zhi)(zhi),只應該在(zai)多變(bian)(bian)量(liang)(liang)(liang)短聲(sheng)(sheng)明的(de)(de)(de)時(shi)候重新(xin)聲(sheng)(sheng)明變(bian)(bian)量(liang)(liang)(liang),重新(xin)聲(sheng)(sheng)明并(bing)不會使(shi)用新(xin)的(de)(de)(de)變(bian)(bian)量(liang)(liang)(liang),而是給變(bian)(bian)量(liang)(liang)(liang)分(fen)配新(xin)值。

field1, offset := nextField(str, 0)
field2, offset := nextField(str, offset)  // 重新聲明 offset
a, a := 1, 2                              // 非法:聲明了 a 兩次并且沒有新的變量

短變量聲明只能在函數中使用,例如在 ifforswitch語句的(de)上下文中聲明臨時變量。

函數聲明

函數聲(sheng)明為函數綁定(ding)標識符。

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

如果函(han)數(shu)指定(ding)了返回參數(shu)。函(han)數(shu)體的語句必(bi)須以終止(zhi)語句結束。

func IndexRune(s string, r rune) int {
   for i, c := range s {
   	if c == r {
   		return i
   	}
   }
   // 無效:缺少 return 語句
}

函(han)數聲明可(ke)以沒有函(han)數體。這(zhe)樣的(de)聲明提供一個函(han)數聲明,并由其他外部(bu)實(shi)現(xian),例如(ru)匯編腳本(ben)。

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

func flushICache(begin, end uintptr)  // 由外部實現

方法聲明

方法(fa)是一個帶接收者的函數,方法(fa)聲明為方法(fa)綁定標識符作(zuo)為方法(fa)名并(bing)指定方法(fa)對(dui)應的接收者類型。

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

接(jie)收(shou)者(zhe)通過在方法增(zeng)加一(yi)個額外的(de)參(can)(can)數來指定。這個參(can)(can)數必須是一(yi)個非可變參(can)(can)數。它的(de)類型(xing)必須是 T 或(huo)(huo)者(zhe) T 的(de)指針(可能(neng)(neng)包(bao)含括(kuo)號(hao))。T 被(bei)稱(cheng)作接(jie)收(shou)者(zhe)的(de)基(ji)礎(chu)類型(xing);它不能(neng)(neng)是指針或(huo)(huo)接(jie)口類型(xing),并(bing)且只能(neng)(neng)在同一(yi)個包(bao)中(zhong)定義方法。聲明后,我們認為方法綁(bang)定了基(ji)礎(chu)類型(xing),并(bing)且可以通過 T 或(huo)(huo) *T 選擇器(qi)訪問(wen)方法名。

非空的(de)接(jie)收(shou)者標識(shi)符在(zai)方(fang)(fang)法簽名(ming)中(zhong)必須是(shi)唯一(yi)(yi)的(de)。如果接(jie)收(shou)者的(de)值沒有在(zai)該方(fang)(fang)法中(zhong)使(shi)用,那么接(jie)收(shou)者標識(shi)符可以省(sheng)略(lve)。函數和方(fang)(fang)法的(de)參數也是(shi)一(yi)(yi)樣。

對于一個(ge)基礎(chu)類型。綁定的(de)非(fei)(fei)空的(de)方法名必須是唯一的(de)。如果(guo)基礎(chu)類型是一個(ge)結構體,非(fei)(fei)空的(de)方法名也不(bu)能與(yu)結構體字段(duan)重復。

給定一個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個方法 LengthScale

方法的類型就是以接收者作為第一個參數的函數類型,例如 Scale 方法:

func(p *Point, factor float64)

但是以這種方式聲明的函數并不是方法。

表達式

表(biao)達式通過(guo)針對運算(suan)元使用運算(suan)符和函數來獲(huo)取計算(suan)值。

運算元

運算元代(dai)表表達式中的一個簡單的。運算元可(ke)以是字面值,非(fei)空(kong)標識符(fu)。或括號(hao)表達式。

空標識符(fu)只(zhi)能出現(xian)在賦值聲明的(de)左側(ce)。

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

修飾標識符

修飾標(biao)識(shi)符是以包(bao)名(ming)作為前(qian)綴修飾的標(biao)識(shi)符。包(bao)名(ming)和標(biao)識(shi)符都不能為空(kong)。

QualifiedIdent = PackageName "." identifier .

修飾標識符可(ke)以用來訪(fang)問(wen)不同包(bao)(需(xu)要先導入(ru))中的(de)標識符。標識符必(bi)須是導出的(de)并在(zai)包(bao)級代碼塊聲(sheng)明(ming)才能夠被訪(fang)問(wen)。

math.Sin	// 表示 math 包中的 Sin 函數

復合字面值

復合(he)字面(mian)值(zhi)能為結構體、數組(zu)、切片和 map 初始化值(zhi)。它每(mei)次只(zhi)能創建(jian)一個值(zhi)。字面(mian)值(zhi)由一個字面(mian)值(zhi)類型(xing)和使用括號(hao)括起來的元(yuan)素(su)列表(biao)組(zu)成。元(yuan)素(su)前(qian)也可(ke)以(yi)聲明元(yuan)素(su)對應的鍵。

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 .

字面值類(lei)(lei)(lei)型(xing)(xing)的(de)(de)底層類(lei)(lei)(lei)型(xing)(xing)必須是一個(ge)結構(gou)體(ti),數(shu)組,切片或(huo) map 類(lei)(lei)(lei)型(xing)(xing)(如(ru)果(guo)(guo)沒有(you)指定(ding)類(lei)(lei)(lei)型(xing)(xing)名就(jiu)(jiu)會(hui)強制執行這個(ge)約束)。元(yuan)素的(de)(de)類(lei)(lei)(lei)型(xing)(xing)和(he)鍵(jian)都必須能夠分配給相(xiang)應的(de)(de)字段(duan)(duan)(duan)的(de)(de)元(yuan)素和(he)鍵(jian)類(lei)(lei)(lei)型(xing)(xing);沒有(you)額(e)外的(de)(de)類(lei)(lei)(lei)型(xing)(xing)轉換。鍵(jian)可以表示結構(gou)體(ti)的(de)(de)字段(duan)(duan)(duan)名,切片和(he)數(shu)組的(de)(de)索引,map 類(lei)(lei)(lei)型(xing)(xing)的(de)(de)鍵(jian)。對于 map 字面值,所(suo)有(you)的(de)(de)元(yuan)素都必須有(you)鍵(jian)。如(ru)果(guo)(guo)相(xiang)同字段(duan)(duan)(duan)名或(huo)常量(liang)值的(de)(de)鍵(jian)對應多個(ge)元(yuan)素就(jiu)(jiu)會(hui)報錯。如(ru)果(guo)(guo) map 類(lei)(lei)(lei)型(xing)(xing)的(de)(de)鍵(jian)為非常量(liang)類(lei)(lei)(lei)型(xing)(xing),請看求值順序章節。

結構體字(zi)面值遵(zun)循以下(xia)規則:

  • 在結構(gou)體中,鍵必(bi)須(xu)是它的字段(duan)名(ming)。

  • 不包(bao)含任何鍵的元素(su)列(lie)表的順序需要與結構體字(zi)段(duan)的聲(sheng)明順序相同(tong)。

  • 如(ru)果一個元素(su)指定了鍵,那么所有的元素(su)都必須(xu)指定鍵。

  • 包含(han)鍵(jian)的元素列表不需要指定結構體的每(mei)個字(zi)字(zi)段(duan),缺(que)省字(zi)段(duan)會使用(yong)字(zi)段(duan)類型的零(ling)值。

  • 字面(mian)值(zhi)可(ke)以不指定(ding)元素;這(zhe)樣的(de)字面(mian)值(zhi)等于(yu)該類型的(de)零值(zhi)。

  • 指(zhi)定非本包(bao)的(de)非導出(chu)字段會報錯。

給定聲明:

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

我們可以使用(yong)這(zhe)種寫法:

origin := Point3D{}                            // Point3D 的零值
line := Line{origin, Point3D{y: -4, z: 12.3}}  // line.q.x 的零值

數(shu)組和切(qie)片(pian)遵(zun)循以下規則:

  • 每個(ge)元素(su)都(dou)關(guan)聯一個(ge)數字索引標記元素(su)再(zai)數組中的位(wei)置。

  • 給元素指定的鍵會作為它的索引。鍵必須是能夠表示非負的 int 類型(xing)值的常(chang)量;如(ru)果(guo)是指定類型(xing)的常(chang)量,那么常(chang)量必(bi)須是整型(xing)。

  • 元(yuan)(yuan)素沒有(you)(you)指(zhi)定鍵時會使用之前的索(suo)引(yin)加一(yi)(yi)。如果第一(yi)(yi)個元(yuan)(yuan)素沒有(you)(you)指(zhi)定鍵,它的索(suo)引(yin)為(wei)零。

對(dui)復合字面值取(qu)址(zhi)會生成指(zhi)向由字面量(liang)初(chu)始化的變量(liang)的指(zhi)針。

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

數組字面值需要在類型中指定數組的長度。如果提供的元素少于數組的長度,那么缺少元素的位置將會使用元素類型的零值替代。如果索引超過數組的長度會報錯。 表示數組(zu)的長(chang)度等于最大元素(su)索引(yin)加一。

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

切片(pian)字(zi)面(mian)值底層其實就是(shi)(shi)數組字(zi)面(mian)值。因(yin)此它(ta)的長度和容量都是(shi)(shi)元素的最大索引加一。切片(pian)字(zi)面(mian)值的格式為:

[]T{x1, x2, … xn}

可以在數組(zu)上(shang)進行切片操作從而獲得切片:

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

在一個數組、切片或 map 類型 T 中。元素或者 map 的鍵可能有自己的字面值類型,如果字面值類型和元素或者鍵類型相同,那么對應的類型標識符可以省略。與之類似,如果元素或鍵的類型為 *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{})}

當復合字面值使用字面值類型的類型名格式出現在 ifforswitch 語句(ju)的(de)(de)(de)關鍵字(zi)和括(kuo)號(hao)之間(jian)并且沒(mei)有使用(yong)(yong)圓括(kuo)號(hao)包裹的(de)(de)(de)時候,會引(yin)發(fa)語法歧(qi)義。在這種特殊的(de)(de)(de)情況(kuang)下字(zi)面值的(de)(de)(de)括(kuo)號(hao)會被認(ren)為是語句(ju)的(de)(de)(de)代碼塊。為了避免歧(qi)義,復合字(zi)面值必須用(yong)(yong)括(kuo)號(hao)括(kuo)起來。

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

下面是合(he)法的數(shu)組、切(qie)片和(he) map 的例子:

// 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,
}

函數字面值

函數字面值(zhi)表示一個匿名函數。

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

函數字面值能分配給變量或直接調用。

函數(shu)字(zi)面值是一個閉包。它可(ke)以引用包裹函數(shu)中的變量,這些變量在(zai)包裹函數(shu)和(he)函數(shu)字(zi)面值之間是共享的。并且它會一直存在(zai)直到生(sheng)命周期結束。

主要表達式

主要表(biao)達式(shi)是一元和二元表(biao)達式(shi)的(de)運算元。

PrimaryExpr =
	Operand |
	Conversion |
	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 不是包名的主要表達(da)(da)式(shi),選(xuan)擇器表達(da)(da)式(shi):

x.f

表示 x 的(de)字段或方法 f(有時為 *x)。標(biao)識符(fu) f 叫做(zuo)(字段/方法)選擇器。它不(bu)能是(shi)空標(biao)識符(fu)。選擇器表達式的(de)類型(xing)就是(shi) f 的(de)類型(xing)。如果 x 是(shi)包名。請參考修飾標(biao)識符(fu)。

選擇(ze)器 f 可(ke)以表示類(lei)(lei)型 T 的方(fang)法或(huo)字(zi)段 f。也可(ke)以表示類(lei)(lei)型 T 的嵌入方(fang)法或(huo)字(zi)段 f。訪問 f 所需穿過的嵌套(tao)層數叫做它(ta)在(zai)類(lei)(lei)型 T 中的深(shen)(shen)度。聲(sheng)明(ming)在(zai) T 中的字(zi)段或(huo)方(fang)法的深(shen)(shen)度為 0。聲(sheng)明(ming)在(zai) T 的嵌入字(zi)段 A 中的方(fang)法或(huo)字(zi)段的深(shen)(shen)度等于 f 在(zai) A 中的深(shen)(shen)度加一(yi)。

選擇器遵循以下原則:

  • 對(dui)于(yu)非(fei)指針/接口(kou)類(lei)型 T/*T 的(de)值 x,x.f 表示第一層的(de)方法/字段。如果在第一層沒有對(dui)應的(de) f,選擇器(qi)表達式(shi)就是非(fei)法的(de)。

  • 對于接口類型 I 的值 x,x.f表示動態(tai)值 x 的(de)(de)方(fang)法(fa)名 f。如(ru)果接口 I 的(de)(de)方(fang)法(fa)集中沒有(you) f 方(fang)法(fa),選擇器就是(shi)非(fei)法(fa)的(de)(de)。

  • 作為例外,如果 x 是一個指針類型并且 (x).f 是合法的選擇器表達式(只能表示字段,不能表示方法)。那么(x).f 可(ke)以(yi)簡寫成(cheng) x.f。

  • 在其他情(qing)況下,x.f 都(dou)是(shi)非法的(de)。

  • 如果x是(shi)指針類型,并且值(zhi)為 nil,其中 f 為結構體字段(duan)。賦值(zhi)或取值(zhi) x.f 會引起(qi)運行(xing)時恐慌。

  • 如果x是接口類型,并且值為 nil。調用 x.f 會引(yin)起運行時恐慌。

例如給定聲明:

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

但(dan)是(shi)下面(mian)這種方式(shi)是(shi)不合法的:

q.M0()       // (*q).M0 is valid but not a field selector

方法表達式

如果 M 在類型(xing) T 的方(fang)法集中。那么 T.M 就是能夠正常調(diao)用的函數。使(shi)用與(yu) M 相同的參(can)數只(zhi)是在參(can)數列表的最前(qian)面增(zeng)加了(le)接收者參(can)數。

MethodExpr    = ReceiverType "." MethodName .
ReceiverType  = TypeName | "(" "*" TypeName ")" | "(" ReceiverType ")" .

假設結構體 T 有兩(liang)個方法。接(jie)收(shou)(shou)者(zhe)類(lei)型(xing)為 T 的(de) Mv 方法和接(jie)收(shou)(shou)者(zhe)類(lei)型(xing)為 *T 的(de) Mp 方法:

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

將(jiang)會生成一個等價于 Mv 方法只是第一個參數(shu)顯式聲明(ming)接受(shou)者的(de)函數(shu)。它的(de)簽(qian)名為(wei):

func(tv T, a int) int

這個函數能夠通(tong)過接收者正常調用,以下5種方(fang)式(shi)是等價的(de):

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

對于一個(ge)把值作為(wei)接(jie)收(shou)者(zhe)(zhe)的方法,我們可以顯(xian)式的從(cong)指針接(jie)收(shou)者(zhe)(zhe)獲得函(han)數(shu):

(*T).Mv

生成表示 Mv 的函(han)數簽(qian)名:

func(tv *T, a int) int

這樣(yang)的(de)(de)(de)函(han)數(shu)會通過接(jie)(jie)收者間接(jie)(jie)的(de)(de)(de)創建一個值(zhi)作為接(jie)(jie)收者傳(chuan)入底層方法中。方法內不能(neng)修改接(jie)(jie)收者的(de)(de)(de)值(zhi),因(yin)為它的(de)(de)(de)地址是在函(han)數(shu)的(de)(de)(de)調用棧(zhan)里(li)面。

最后一個例子(zi)。把值作為接收(shou)者(zhe)函數當(dang)做指針作為接收(shou)者(zhe)的方法(fa)(fa)是(shi)非(fei)法(fa)(fa)的,因為指針接收(shou)者(zhe)的方法(fa)(fa)集中不包含值類型的方法(fa)(fa)集。

通過函數調用語法從方法中獲取函數的值。接收者作為調用函數的第一個參數。給定 f :=T.Mv,f 作為f(t,7) 進行調用而不是 t.f(7)。想創建一個綁定接(jie)收者(zhe)的函(han)(han)數(shu)(shu)可以使用函(han)(han)數(shu)(shu)字面值或者(zhe)方法值。

在接(jie)口類(lei)型中定義函數獲取(qu)函數值是合法的。最終(zhong)的函數調用(yong)會使用(yong)接(jie)口類(lei)型作為接(jie)收者。

方法值

如果表達式 x 擁有靜態類型 T 并且 M 在類型 T 的方法集中。x.M 叫做方法值。方法值 x.M 是一(yi)個函數(shu)值,這個函數(shu)和(he) x.M 擁有相(xiang)同的參數(shu)列表。表達(da)式(shi) x 在計(ji)算方(fang)法(fa)值時會被保存和(he)計(ji)算,這個拷貝的副本會作(zuo)為任(ren)何接(jie)下來調用的接(jie)收者。

類型(xing)(xing) T 可能是(shi)接口(kou)類型(xing)(xing)也可能不是(shi)接口(kou)類型(xing)(xing)。

與(yu)方法表(biao)達式中講(jiang)過(guo)的一樣,假(jia)設類型 T 有(you)兩個方法:接(jie)收者類型為 T 的 Mv 和接(jie)受者類型為 *T 的 Mp :

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

生(sheng)成一個類型的函數值:

func(int) int

以下兩種調(diao)用是等價的:

t.Mv(7)
f := t.Mv; f(7)

相似的,表達式:

pt.Mp

生(sheng)成一個類型的函數(shu)值:

func(float32) float32

與選擇器相同,使用指針調用以值作為接收者的非接口方法會自動將指針解引用:pt.Mv 等價于 (*pt).Mv

與方法調用相同,使用值調用以指針作為接收者的非接口方法會自動對值取址:pt.Mv 等價于 (&pt).Mv

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

盡管上面使(shi)用的都是非接口(kou)類(lei)(lei)型(xing)(xing)的例(li)子,不(bu)過(guo)對于接口(kou)類(lei)(lei)型(xing)(xing)同樣適(shi)用。

var i interface { M(int) } = myVal
f := i.M; f(7)  // like i.M(7)

index表達式

主要表達式格式:

a[x]

可以(yi)表示數組元素、數組的(de)指針、切片、字符串或 map 類型 a 索引 x 對應的(de)值(zhi)。x 稱作(zuo)索引或者 map 的(de)鍵。遵循以(yi)下(xia)規則:

如果a不(bu)是 map 類型(xing):

  • 索(suo)引(yin) x 必須是整型或(huo)無(wu)類型常量。

  • 常量(liang)索引必(bi)須(xu)是非(fei)負數(shu)且可以使用 int 類(lei)型(xing)表示。

  • 無類型的常量索引會作為 int 型的值。

  • 索引 x 的范圍在 0<=x<len(a) 內,否(fou)則就是越界。

對于數組類型 A:

  • 常量(liang)索引必(bi)須(xu)在合(he)法范(fan)圍內。

  • 如果 x 在運(yun)行時(shi)越界會引起運(yun)行時(shi)恐慌。

  • a[x] 表示數組在索引 x 處的元素。a[x] 的(de)類型就是 A 的(de)元素(su)類型。

對于數(shu)組的指針類型:

  • 可以使用 a[x] 表示 (*a)[x]

對于切片類型 S:

  • 如果 x 在運行時越界會引起運行時恐慌。
  • a[x] 表示切片在索引 x 處的元素。a[x] 的類型就是 S 的元素類型。

對于字符串類型:

  • 如(ru)果字符(fu)串 a 為(wei)常量,那么常量索(suo)引必須在合法范圍內(nei)。

  • 如果 x 在運(yun)(yun)行時越界會引起運(yun)(yun)行時恐慌。

  • a[x] 表示索引 x 處的非常量字節,它是byte類型。

  • 不能(neng)對 a[x] 分配值。

對于 map 類型(xing) M:

  • 必(bi)須保證 x 的(de)類型能夠給 M 的(de)鍵分配值。

  • 如(ru)果(guo)map包含鍵為 x 的(de)值(zhi),a[x] 就(jiu)是 map 中鍵 x 對應的(de)值(zhi),它(ta)的(de)類(lei)型(xing)就(jiu)是 M 的(de)元素類(lei)型(xing)。

  • 如果(guo) map 值(zhi)為(wei) nil 或不包含這(zhe)個實體(ti),那么 a[x] 為(wei) M 元(yuan)素類型的零值(zhi)。

否則 a[x] 就是非法的。

基于 map[K]V 類型 a 的(de)索引表達式可(ke)以使(shi)用特殊格式的(de)賦(fu)值(zhi)和(he)初始化(hua)語法。

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

它會額外(wai)生成一個無類型的(de)布爾(er)值。如(ru)(ru)果(guo) ok 是 true,那么代表在map中(zhong)有(you)該鍵,如(ru)(ru)果(guo)沒有(you) ok 為 false。

給(gei)一個值為 nil 的 map 類型變量(liang)賦(fu)值會導致運(yun)行時(shi)恐慌。

切片表達式

切片(pian)表達式(shi)可以基(ji)于字(zi)符(fu)串(chuan)、數(shu)組、數(shu)組指針、切片(pian)創建字(zi)符(fu)串(chuan)子串(chuan)或切片(pian)。它有(you)兩種變體,一種是簡(jian)單的格式(shi)是指定(ding)開始和結束位置(zhi),完全格式(shi)的語法(fa)還可以指定(ding)容量。

####### 簡(jian)單切片表達式

對于數(shu)組(zu)(zu)、字符串、指針(zhen)數(shu)組(zu)(zu)、切片 a,主要表達式:

a[low:high]

可以構造字符串子串或切片。索引 lowhigh 決定結果切片中的元素。結果切片的索引從 0 開始,長度為 high - low。從數組切分出的切片 s 擁有類型 []int,長度為(wei) 3 ,容(rong)積為(wei) 4。

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

為了方便(bian)起見,索引值(zhi)都(dou)可以缺(que)(que)省(sheng)(sheng)。當(dang) low 缺(que)(que)省(sheng)(sheng)時默認(ren)從 0 開始。當(dang)缺(que)(que) high 缺(que)(que)省(sheng)(sheng)時默認(ren)的(de)取切片的(de)長度。

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)。對于切(qie)片,最(zui)大的索引值可以為切(qie)片的容量(liang),而(er)不是(shi)切(qie)片的長度。常(chang)量(liang)索引必須為非負數,且能夠(gou)轉換(huan)成 int 類型(xing)。對于數組或者常(chang)量(liang)字(zi)符串(chuan)。常(chang)量(liang)索引值必須在合法(fa)范圍內。如果(guo)(guo)2個索引都是(shi)常(chang)量(liang)。low 必須小于 high。如果(guo)(guo)索引在運行(xing)(xing)時訪問了(le)非法(fa)內存,程(cheng)序會發生運行(xing)(xing)時恐慌。

除(chu)了無類型(xing)字(zi)符(fu)串(chuan)(chuan),對于切(qie)片和字(zi)符(fu)串(chuan)(chuan)的(de)操作(zuo)結果(guo)是非常(chang)量類型(xing)的(de)值,它的(de)類型(xing)與(yu)運算(suan)(suan)元相同。如(ru)果(guo)運算(suan)(suan)元為(wei)(wei)無類型(xing)字(zi)符(fu)串(chuan)(chuan),那(nei)么結果(guo)類型(xing)會為(wei)(wei) string。如(ru)果(guo)把數(shu)(shu)組作(zuo)為(wei)(wei)運算(suan)(suan)元,它必(bi)須是可(ke)尋址的(de),并(bing)且獲(huo)得的(de)切(qie)片和原數(shu)(shu)組具(ju)有(you)同一元素類型(xing)。

如果(guo)(guo)切(qie)片運算元(yuan)(yuan)為(wei) nil,那(nei)么(me)結(jie)(jie)果(guo)(guo)也(ye)是 nil。否則(ze)結(jie)(jie)果(guo)(guo)切(qie)片會(hui)和運算元(yuan)(yuan)共享(xiang)相同(tong)的底層無類型數(shu)組。

完全切片表達式

對于數(shu)組,數(shu)組指針或非字符串切片(pian),主要(yao)表達(da)式為:

a[low : high : max]

它會構造一個同類型切片,并具有與簡單切片表達式的 a[low:high] 相同的長度和元素。另外,它還可以把切片的容量設置為 max - low。這時只有第一(yi)個索引可以為缺(que)省值,默認為零。從(cong)數組(zu)中獲得切片以后:

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]。如(ru)果切分操作元是數組(zu),那么這個數組(zu)必須是可(ke)以尋址的。

如果索引必須在 0 <= low <= high <= max <= cap(a) 范圍內。常量索引不能是負數并且能夠使用 int 類型表示(shi);對于(yu)數組,索(suo)引(yin)必須在合法范圍(wei)(wei)內。如(ru)果(guo)有(you)多個(ge)索(suo)引(yin)都(dou)是(shi)常量的,那(nei)么所有(you)索(suo)引(yin)都(dou)需(xu)要在合法范圍(wei)(wei)內。如(ru)果(guo)索(suo)引(yin)是(shi)非(fei)法的,會引(yin)起運(yun)行時恐慌。

類型斷言

對于接口類(lei)型 x 和類(lei)型 T,主要表達式(shi):

x.(T)

可以斷言(yan) x 不是 nil 且 x 的(de)值是 T 類型。標(biao)記(ji) x.(T) 叫做(zuo)類型斷言(yan)。

更(geng)確切(qie)的(de)說,如果 T 不是(shi)接口(kou)類(lei)型(xing),那(nei)么 x.(T) 將(jiang)會斷言(yan)動態類(lei)型(xing) x 的(de)類(lei)型(xing)是(shi)不是(shi) T。

這時,T 必須(xu)實(shi)現了 x 的(接口(kou))類(lei)(lei)型。否則(ze)斷(duan)言(yan)(yan)會(hui)是(shi)非法的因為 x 不能保存 T 類(lei)(lei)型的值(zhi)。如果 T 是(shi)接口(kou)類(lei)(lei)型,那么可(ke)以(yi)斷(duan)言(yan)(yan)動態類(lei)(lei)型 x 是(shi)否實(shi)現了 T 接口(kou)。

如果類型斷言(yan)成功(gong),表達式的值(zhi)為 x 的值(zhi),但它(ta)的類型是(shi)T。如果斷言(yan)失敗,將會導致(zhi)運行時(shi)恐(kong)慌。換句話說(shuo),即使 x 是(shi)運行時(shi)確定的,x.(T) 也必須是(shi)編程時(shi)就確認(ren)存在的。

var x interface{} = 7          // x 擁有動態類型 int 值為 7
i := x.(int)                   // i 為 int 類型值為 7

type I interface { m() }

func f(y I) {
   s := y.(string)        // 非法: 字符串沒有實現接口 I (缺少 m 方法)
   r := y.(io.Reader)     // r 擁有接口 io.Reader 所以 y 的動態類型必須同時實現 I 和 io.Reader
   …
}

類型斷言可以使用特定(ding)格式的賦值和初始化(hua)語句。

v, ok = x.(T)
v, ok := x.(T)
var v, ok = x.(T)
var v, ok T1 = x.(T)

這時將會額外生成一個無類型的布爾值。如果斷言成功,ok返回 true,否則是 false。并且 v 會是 T 類型的(de)零值。這時(shi)不(bu)會有恐慌發生。

調用

給定函數類型為 F 的表達(da)式 f:

f(a1, a2, … an)

可以使用(yong) a1,a2...an 來調用(yong)函(han)(han)(han)(han)數(shu)(shu)(shu) f。除一種特殊情況之外,函(han)(han)(han)(han)數(shu)(shu)(shu)參(can)數(shu)(shu)(shu)必須是(shi)對(dui)應 F 函(han)(han)(han)(han)數(shu)(shu)(shu)參(can)數(shu)(shu)(shu)類(lei)型的單值表達式(shi),且在函(han)(han)(han)(han)數(shu)(shu)(shu)調用(yong)前就已經完成求(qiu)值。表達式(shi)的結果類(lei)型是(shi) f 的結果類(lei)型。函(han)(han)(han)(han)數(shu)(shu)(shu)調用(yong)和方法(fa)調用(yong)相(xiang)似,只(zhi)是(shi)方法(fa)額外需要一個(ge)接收(shou)者類(lei)型。

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

在函(han)(han)(han)數調(diao)用(yong)(yong)中,函(han)(han)(han)數的值和(he)參(can)數是按照順序(xu)求值的。在計算之后作為參(can)數會(hui)傳(chuan)進函(han)(han)(han)數,函(han)(han)(han)數開始(shi)執行。當函(han)(han)(han)數執行完(wan)成(cheng)后返(fan)(fan)回(hui)的參(can)數將會(hui)返(fan)(fan)回(hui)給函(han)(han)(han)數的調(diao)用(yong)(yong)者。

調用值為 nil 的函數會導致運行(xing)時(shi)恐慌。

作為特例,如果函數或者方法的(de)返回值等于參數列(lie)表的(de)個(ge)數,那么(me)會嵌套調用。這(zhe)將把返回值直接(jie)賦值給下一次調用函數的(de)參數。

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

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

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

如果 x 的方法集中包含 m 那么 x.m() 是合法的。并且參數列表和 m 的參數列表相同。如果x是可尋址的,那么那么x指針的方法集(&x).m()可以簡寫成x.m()

var p Point
p.Scale(3.5)

沒有(you)方法(fa)類型,也(ye)沒有(you)方法(fa)字面值。

通過 ... 來傳遞參數

如果 f 的最后一個參數 p 的類型是 ...T。那么在函數內部 p 參數的類型就是 []T。如果 f 調用時沒有傳入(ru) p 對應的(de)參數,那么p為 nil。否則這(zhe)些參數會以切(qie)片(pian)方式傳入(ru),在新的(de)底(di)層切(qie)片(pian)中。切(qie)片(pian)中的(de)類型(xing)都是能賦(fu)值給類型(xing) T 的(de)值。這(zhe)個切(qie)片(pian)的(de)長度和容量在不(bu)同的(de)調用中有所(suo)不(bu)同。

給定函數調用:

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

Greeting 中,第一次調用時,who是 nil 類型。而在第二次調用時是[]string{"Joe", "Anna", "Eileen"}

如果在調用的時候的最后一個參數是[]T,那么我們可以使用...來將切片中的值依(yi)次賦(fu)值給(gei)參數列表。

給定切(qie)片s并且調用:

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

z 在 Greeting。中 who 會和(he)切片 s 共享相同(tong)的底層數組(zu)。

操作符

操(cao)作符用(yong)來連接運(yun)算(suan)元。

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   = "+" | "-" | "!" | "^" | "*" | "&" | "<-" .

比較運算符在此處討(tao)論。對于(yu)其他二(er)元操作(zuo)符,兩個操作(zuo)元的類(lei)型必(bi)須是相同的,除了位移(yi)和(he)無(wu)類(lei)型常量(liang)。針對常量(liang)的操作(zuo),請看常量(liang)表達(da)式章節。

除了位移操作,如果其中一個操作符是無(wu)類(lei)型常量,而(er)另個不(bu)是,那么無(wu)類(lei)型的常量會(hui)轉換成另一個運算元的類(lei)型。

在右移(yi)表達式中(zhong)的(de)(de)運(yun)算元必須是無符號的(de)(de)整數或者可以(yi)轉(zhuan)(zhuan)換成 uint 的(de)(de)無類型(xing)(xing)的(de)(de)常量。如(ru)果(guo)左移(yi)一個(ge)無類型(xing)(xing)常量那么(me)結果(guo)依然是無類型(xing)(xing)的(de)(de)。他首先會轉(zhuan)(zhuan)換成指(zhi)定類型(xing)(xing)。

var s uint = 33
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 == 0 if ints are 32bits in size
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 == true if ints are 32bits in size
var p = 1<<s == 1<<33  // illegal if ints are 32bits in size: 1 has type int, but 1<<33 overflows int
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
運算符優先級

一元(yuan)運(yun)算符擁有最高優先(xian)級。++ 和(he) -- 是語句(ju)而不是表(biao)達式,他們在運(yun)算符的(de)優先(xian)級之(zhi)外(wai)。所以 (*p)++ 和(he) *p++ 是一樣的(de)。

二元運(yun)算符有 5 個優先級。乘(cheng)法(fa)運(yun)算符在最(zui)(zui)高(gao)級,緊接(jie)著是加法(fa)運(yun)算符。比較運(yun)算符,&& 運(yun)算符,最(zui)(zui)后是 ||。

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 && <-chanPtr > 0

算數運算符

算(suan)數(shu)(shu)(shu)運(yun)算(suan)符(fu)應用(yong)在 2 個(ge)數(shu)(shu)(shu)字(zi)值之(zhi)(zhi)間,別切生成一個(ge)相(xiang)同(tong)類型的(de)值作為第(di)一個(ge)運(yun)算(suan)元(yuan)。四種算(suan)數(shu)(shu)(shu)運(yun)算(suan)符(fu)(+,-,*,/)應用(yong)在數(shu)(shu)(shu)字(zi),浮點(dian),復合類型之(zhi)(zhi)中。+ 也可以(yi)用(yong)于字(zi)符(fu)串。位運(yun)算(suan)和位移運(yun)算(suan)只適用(yong)于整數(shu)(shu)(shu)。

+    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 << unsigned integer
>>   right shift            integer >> unsigned integer
數字運算符

對于兩個整數 x 和 y。整數商 q=x/y 和余數 r=x%y 遵循以下規律。

x = q*y + r  and  |r| < |y|

x/y 截斷為 0。

x     y     x / y     x % y
 5     3       1         2
-5     3      -1        -2
 5    -3      -1         2
-5    -3       1        -2

作為這個規則的例外情況,如果 x 非常大,那么 q=x/-1 等于 x。

x, q
int8                     -128
int16                  -32768
int32             -2147483648
int64    -9223372036854775808

如果(guo)除(chu)數(shu)(shu)是一個常量。那么它不(bu)能是 0,如果(guo)除(chu)數(shu)(shu)在運行時(shi)為 0,會導致(zhi)運行時(shi)恐(kong)慌。如果(guo)除(chu)數(shu)(shu)是負(fu)數(shu)(shu)并且除(chu)數(shu)(shu)是:

x     x / 4     x % 4     x >> 2     x & 3
 11      2         3         2          3
-11     -2        -3        -3          1

位移運算符移動左側運算元右側元算元指定的位數。如果左側是有符號整型,那它就實現了位移運算,如果是無符號整數使用邏輯位移。位移運算沒有上限,位移操作讓左邊運算元位移 n 個 1。x<<1x*2 是相等的。并且 x>>1x/2 是相同的。

對于整數運算(suan)元,一元運算(suan)符+-^定義如下:

+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
整型溢出

對于無符號的值,運算符+-*和<<都是2禁止運算。這里的n是無符號類型的寬度,無符號整型將會丟棄溢出的位,并且程序將會返回wrap around

對于有(you)符(fu)號(hao)的(de)整數,操作符(fu)+=*<<都(dou)會溢出(chu)并(bing)(bing)且(qie)(qie)值存在(zai),并(bing)(bing)且(qie)(qie)代(dai)表相應的(de)有(you)符(fu)號(hao)的(de)值。在(zai)運算時不(bu)會拋(pao)出(chu)異常(chang)。標(biao)一起不(bu)會報錯。所以(yi)不(bu)是所有(you)情況(kuang)下x<x+1都(dou)成(cheng)立(li)。

浮點數運算符

對于浮(fu)點數和其他復雜數字,+x和x是一樣的(de),-x是x的(de)對立面。除了(le)IEEE-754還(huan)沒有指定浮(fu)點數除0或者復數的(de)結果。是否拋出異常(chang)將會依賴(lai)其具體實現。

一(yi)種實現可(ke)以(yi)合(he)(he)并多(duo)個浮(fu)點(dian)操作(zuo)進(jin)一(yi)個操作(zuo),有(you)可(ke)能是夸語句的(de)(de),并且他(ta)的(de)(de)結果可(ke)能和依次單獨執行的(de)(de)結果不(bu)一(yi)樣。1個浮(fu)點(dian)數類型將(jiang)會轉(zhuan)變成目標的(de)(de)精度,防(fang)止(zhi)四舍五入的(de)(de)融(rong)合(he)(he)。

// 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
字符串

字符(fu)串可(ke)以使用+和+=操(cao)作符(fu)。

s := "hi" + string(c)
s += " and good bye"

字(zi)符串想家將會創建一個新的字(zi)符串。

比較運算符

比較(jiao)運算符(fu)比較(jiao)連個運算元,并且生成一個無類(lei)型的布爾值。

==    equal
!=    not equal
<     less
<=    less or equal
>     greater
>=    greater or equal

在任何比(bi)較運算元(yuan)中2種類型必須是(shi)可以分配的。

使用等于運算符==!=的運算元必須是可比較的。使用順序運算符<,<=,>>=必(bi)須是可比較的。這些限制導致比較運算符(fu)被(bei)定義成以下的方式。

  • 布爾值是可比較的,兩個布爾值當他們同為true或者false的使用是相等(deng)的

  • 整(zheng)數值是可比較(jiao)和排序的

  • 浮點數是可比較和排序的,具(ju)體定(ding)義在IEEE-754標準中。

  • 復數是可比較(jiao)的,2個復數當實(shi)部(bu)和虛(xu)部(bu)都(dou)相(xiang)等時就是相(xiang)等的。

  • 字符串是可(ke)以比較和(he)排序的。是按(an)照字節順序排序。

  • 指(zhi)(zhi)針式(shi)可以排序的(de),連個(ge)(ge)指(zhi)(zhi)針當指(zhi)(zhi)向相同(tong)變量時是相同(tong)的(de),或者他們(men)2個(ge)(ge)都是nil。指(zhi)(zhi)向一個(ge)(ge)為非配的(de)變量的(de)結果是未(wei)定義的(de)。

  • channel是可比較的(de)。當兩個管道是用同一個make出來的(de),或者都是nil時時相(xiang)等的(de)。

  • 接口(kou)值時(shi)可以(yi)比較的(de)(de),2個(ge)接口(kou)值時(shi)相等(deng)的(de)(de)如果2個(ge)標識符(fu)的(de)(de)動態(tai)類型是一樣的(de)(de)或者他們都是nil。

  • 一(yi)個(ge)非接口類(lei)型(xing)(xing)(xing)的(de)值x和一(yi)個(ge)接口類(lei)型(xing)(xing)(xing)的(de)值T在非接口類(lei)型(xing)(xing)(xing)是(shi)可以比(bi)較的(de)并且非接口類(lei)型(xing)(xing)(xing)實(shi)現了接口是(shi)是(shi)可以比(bi)較的(de)。當他們(men)的(de)動(dong)態類(lei)型(xing)(xing)(xing)類(lei)型(xing)(xing)(xing)相同(tong)時(shi)(shi)時(shi)(shi)相等(deng)的(de)。

  • 當(dang)結構體(ti)內(nei)的(de)(de)(de)所(suo)有字段(duan)都是可以比較的(de)(de)(de)時候,他(ta)是可以比較的(de)(de)(de)。連(lian)個結構體(ti)的(de)(de)(de)值當(dang)非空字段(duan)都相等時他(ta)們(men)是相等的(de)(de)(de)。

  • 數(shu)組(zu)類型(xing)的(de)值(zhi)時可(ke)比(bi)較的(de),如果數(shu)組(zu)的(de)原屬時可(ke)以比(bi)較的(de),那么當數(shu)組(zu)的(de)所(suo)有值(zhi)是相等的(de)時候他們(men)就是相等的(de)。

使用兩個(ge)(ge)動態類(lei)型(xing)的標識(shi)符(fu)來比較接口(kou)(kou)的值。如果這(zhe)個(ge)(ge)類(lei)型(xing)的值時不(bu)可(ke)比較的,那么將會引起(qi)一個(ge)(ge)panic。這(zhe)個(ge)(ge)行為不(bu)僅(jin)僅(jin)時接口(kou)(kou),數組(zu)結(jie)構體接口(kou)(kou)字段都(dou)有這(zhe)個(ge)(ge)問(wen)題。

切(qie)片(pian),map,和函數值(zhi)(zhi)都(dou)是(shi)不可比較的,然(ran)而,作為(wei)一個(ge)特殊的例(li)子,切(qie)片(pian),map和函數的值(zhi)(zhi)的nil時(shi)可以(yi)比較的,指針(zhen),channel和接口(kou)的值(zhi)(zhi)nil也是(shi)可以(yi)比較的。

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
)

邏輯操作符

邏(luo)輯運算(suan)符使用布爾值值,并且生成一個相同(tong)類(lei)型(xing)的(de)(de)結果值作為操作元。右面(mian)的(de)(de)操作元計算(suan)是有條件的(de)(de)。

&&    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 會生成一個類型為 *T 并指向 x 的指針。運算元必須是能夠取址的,它可以是一個變量,指針,切片的取值操作;或是一個可取址結構體的字段選擇器;或是對于可取址數組的索引取值操作。作為尋址能力的例外,x 可能是一個復合字面值。如果對 x 進行取址操作將會 panic,&x 也(ye)會 panic。

對于一個 *T 類型的運算元 x,指針解引用 *x 表示 x 指向的 T 類型。如果 x 為 nil,那么解引用 *x 會 panic。

&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 接收(shou)(shou)到的(de)值。帶方向的(de)管道需要有接受(shou)權限,接收(shou)(shou)操作(zuo)的(de)類型(xing)也是通道的(de)元素類型(xing)。表達式會(hui)一直(zhi)(zhi)(zhi)阻塞(sai)直(zhi)(zhi)(zhi)到接收(shou)(shou)到返(fan)回值。從 nil 通道接收(shou)(shou)值會(hui)一直(zhi)(zhi)(zhi)阻塞(sai)。從一個已經關閉的(de)通道接收(shou)(shou)數據會(hui)在其他數據都(dou)被接收(shou)(shou)以(yi)后生(sheng)成該(gai)通道元素類型(xing)的(de)零值。

v1 := <-ch
v2 = <-ch
f(<-ch)
<-strobe  // wait until clock pulse and discard received value

接(jie)收數據的(de)表(biao)達式(shi)可以使用賦(fu)值表(biao)達式(shi)。

x, ok = <-ch
x, ok := <-ch
var x, ok = <-ch
var x, ok T = <-ch

它還可以生成一個額外的無類型布爾值來表示通道是否關閉。如果 ok 為 true 說明獲取到的是發送到通道內的數據,而 false 它(ta)就返回一個零值因為通道內沒有元素且已(yi)經(jing)關(guan)閉。

類型轉換

類型轉換表達式 T(x) 其中 T 代表類(lei)型,x 代表可以轉換成 T 類(lei)型的表達式(shi)。

Conversion = Type "(" Expression [ "," ] ")" .

如果類型是以 *<- 開頭,或以關鍵字 func 開頭并且沒有(you)返回值列表(biao),那么它必須用括號括起來(lai)避(bi)免歧義:

*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 可以(yi)在可以(yi)用類(lei)型 T 表示(shi)時自動轉(zhuan)換。作為(wei)一個特例,整數常量 x 可以(yi)轉(zhuan)換成字符串類(lei)型就和非常量 x 一樣。

對常量的(de)轉換會(hui)生成(cheng)一(yi)個指定類型的(de)常量。

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

非常(chang)量 x 可(ke)以在以下(xia)(xia)情(qing)況下(xia)(xia)轉換(huan)成類(lei)型 T:

  • x 可以給類(lei)型 T 賦值(zhi)

  • 忽略的(de)結構(gou)體(ti)標簽,x 的(de)類型和 T 具有相(xiang)同的(de)底層類型

  • 忽(hu)略的結構(gou)體標簽,x 的類(lei)型和(he) T 都是指針類(lei)型,并且(qie)指針所指的類(lei)型具(ju)有(you)相同的底層類(lei)型

  • x 的類型和 T 都(dou)是整數或者浮點數類型

  • x 的類型和 T 都是復數(shu)類型

  • x 是一(yi)個(ge)字符串而(er) T 時(shi)字節(jie)切(qie)(qie)片(pian)或者(zhe) rune 切(qie)(qie)片(pian)

在比較兩個(ge)結構體類型的(de)時候會忽略結構體標簽:

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

這個(ge)規則也(ye)適用(yong)于數字類型(xing)與字符串類型(xing)間的(de)相互(hu)轉(zhuan)換。這個(ge)轉(zhuan)換可能會(hui)改變 x 的(de)值(zhi)并且會(hui)增(zeng)加(jia)運行(xing)時(shi)消耗。包 unsafe 實現了這個(ge)功能底層的(de)限制(zhi)。

數字之間的轉換

對于非常量的數字(zi)轉(zhuan)換,需要遵守以下規則:

  • 在轉換整型數字時,如果是一個有符號整型,它是繼承有符號的無限精度;否則就不用繼承符號。轉換時會截斷數字以適應類型的大小。例如:如果 v:=uint16(0x10F0),然后 ``uint32(int8(v)) == 0xFFFFFFF0 。類型轉換總(zong)是(shi)生(sheng)成有效值,并(bing)且永遠不會溢(yi)出。

  • 如果(guo)要將浮點(dian)數轉換成整型,會(hui)丟棄小數部分(截斷為零(ling))。

  • 如果要將整型或浮點型轉換成浮點數類型,或或者一個復數轉換成其他復數類型,結果會四舍五入成指定精度。例如: 可以使用超出IEEE-754 32位數的附加精度來存儲float32類型的變量x的值,但float32(x)表示將x的值舍入為32位精度的結果。x + 0.1 會使用超過 32 位的精度,而 float32(x+0.1) 不會。

在所(suo)有浮點數(shu)(shu)和復數(shu)(shu)的非(fei)常量轉換中(zhong),如果(guo)結(jie)構類型不(bu)能成功表示(shi)數(shu)(shu)據,那么結(jie)果(guo)將會依賴(lai)于具體平臺實現。

字符串的類型轉換
  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"
  1. 將字節切片轉換成字符串類型會生成一個由切片元素組成的字符串
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?"
  1. 將 rune 切片轉換成字符串類型會生成一個由切片元素組成的字符串
string([]rune{0x767d, 0x9d6c, 0x7fd4})   // "\u767d\u9d6c\u7fd4" == "白鵬翔"
string([]rune{})                         // ""
string([]rune(nil))                      // ""

type MyRunes []rune
string(MyRunes{0x767d, 0x9d6c, 0x7fd4})  // "\u767d\u9d6c\u7fd4" == "白鵬翔"
  1. 將字符串轉換成字節切片會生成由字符串中每個字節組成的切片
[]byte("hell?")   // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}
[]byte("")        // []byte{}

MyBytes("hell?")  // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}
  1. 將字符串轉換成 rune 切片會生成由字符串中每個 Unicode 代碼點組成的切片
[]rune(MyString("白鵬翔"))  // []rune{0x767d, 0x9d6c, 0x7fd4}
[]rune("")                 // []rune{}

MyRunes("白鵬翔")           // []rune{0x767d, 0x9d6c, 0x7fd4}
常量表達式

常量(liang)(liang)表達式(shi)只(zhi)包含常量(liang)(liang)運(yun)算元(yuan)并且在編譯程序(xu)時就已經(jing)計算完成(cheng)。

無(wu)類型布爾(er)值(zhi),數值(zhi)和(he)字(zi)符串(chuan)常(chang)量(liang)都(dou)可以當(dang)作運算(suan)元(yuan)(yuan)(yuan)。除(chu)了位置(zhi)操作符,如(ru)果二(er)元(yuan)(yuan)(yuan)運算(suan)符石不同類型的常(chang)量(liang),操作元(yuan)(yuan)(yuan),和(he)非布爾(er)值(zhi),和(he)即將在接下來出現的:整(zheng)型,rune,浮點(dian)數和(he)復(fu)數類型。例如(ru):一個無(wu)類型整(zheng)型常(chang)量(liang)減去無(wu)類型復(fu)數常(chang)量(liang),結果為復(fu)數常(chang)量(liang)。

一(yi)(yi)個(ge)常(chang)(chang)(chang)量(liang)(liang)的(de)比較運算會生(sheng)成(cheng)無(wu)類(lei)型(xing)的(de)布爾(er)常(chang)(chang)(chang)量(liang)(liang)。如果左移(yi)運算是(shi)一(yi)(yi)個(ge)無(wu)類(lei)型(xing)常(chang)(chang)(chang)量(liang)(liang),結果會是(shi)一(yi)(yi)個(ge)整型(xing)常(chang)(chang)(chang)量(liang)(liang)。它會和原來常(chang)(chang)(chang)量(liang)(liang)為相同(tong)(tong)類(lei)型(xing)。其他與無(wu)類(lei)型(xing)常(chang)(chang)(chang)量(liang)(liang)的(de)運算都會生(sheng)成(cheng)相同(tong)(tong)類(lei)型(xing)的(de)結果(布爾(er)值,整型(xing),浮點數(shu),復數(shu),字符串常(chang)(chang)(chang)量(liang)(liang))。

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)

對一個無類型整數,rune,或浮點數應用內置的 complex 函數(shu)會生成無類型的(de)復數(shu)常(chang)量。

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

常量(liang)表達(da)式總是一個(ge)明確的值;中(zhong)間值和常量(liang)自己可以比語(yu)言所支持的精度更高(gao),下面的聲(sheng)明是合(he)法的:

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

常量(liang)的除法(fa)的除數(shu)不能為 0:

3.14 / 0.0   // illegal: division by zero

定(ding)(ding)義(yi)了類型的(de)常(chang)量的(de)精度(du)必須根據常(chang)量類型定(ding)(ding)義(yi)。所(suo)以下面的(de)常(chang)量表達(da)式是(shi)非法(fa)的(de):

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)

補(bu)(bu)碼使用的一元操作符 ^ 對于非常量的匹配模式:補(bu)(bu)碼對于無(wu)符號(hao)常量為(wei) 1,對于有符號(hao)和無(wu)類型常量為(wei) -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

實現限制:編譯器在處(chu)理無類型(xing)浮(fu)點(dian)數和復數時會(hui)取近似值(zhi);具體請(qing)看常量章(zhang)節。這個(ge)取近似值(zhi)的操作在浮(fu)點(dian)數在整數上下文時會(hui)產生無效值(zhi),即使在計算(suan)過后是一個(ge)整型(xing)。

運算優先級

在包級別,初(chu)始化的依(yi)賴性由(you)變量聲明(ming)的初(chu)始化表(biao)達式(shi)順序決(jue)定。否則,當計(ji)算(suan)表(biao)達式(shi)內的操作(zuo)數(shu)時,賦值,返(fan)回語句,所有(you)函數(shu)調用(yong),方法調用(yong),和通信操作(zuo)都(dou)會由(you)左向右計(ji)算(suan)。

例如,在函數(shu)作用域中的賦值(zhi):

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

函數調用和通信的發生順序為:f()h()i()j()<-cg()k()。但是對 y 和 x 的取(qu)值(zhi)操作沒有指定。

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

在包(bao)級(ji)別,依(yi)賴的(de)初(chu)始化順序會(hui)覆(fu)蓋(gai)這個從左向右(you)的(de)規則:

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

語句

語(yu)句控制(zhi)程序的(de)執行。

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

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

終止語句

終(zhong)止(zhi)(zhi)語句會(hui)阻止(zhi)(zhi)相同(tong)代碼(ma)塊中下(xia)面所有語句的執行。以下(xia)語句屬(shu)于終(zhong)止(zhi)(zhi)語句:

  1. returngoto 語句

  2. 對內置 panic 函數的調用

  3. 代碼塊結束

  4. if 語句中:

  5. else 分支

  6. 所有分支末尾

  7. for語句中:

  8. break 語句和循環結束

  9. switch 語句:

  10. switch 語句中沒有 break 語句,

  11. 有一個默認的 case

  12. 語句列表中的每個 case 語句和有可能存在的 fallthrough 語句

  13. select 語句中:

  14. 沒有 break 語句

  15. 每個 case 中的語句列表,如果(guo)包含默(mo)認 case

所有其(qi)他語句都(dou)不是中斷(duan)語句。

如果語(yu)(yu)句序(xu)列不(bu)為(wei)空并且最后一(yi)個非空語(yu)(yu)句是終止語(yu)(yu)句,那么語(yu)(yu)句序(xu)列就以(yi)終結語(yu)(yu)句結尾。

空語句

空語句不做任何事情(qing)。

EmptyStmt = .

標簽語句

標簽語句可以作為 gotobreakcontinue 語句的目標。

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

表達式語句

除了特定的內置函(han)數,一般的函(han)數、方法(fa)和接收操(cao)作(zuo)都可以(yi)出(chu)現在表(biao)達式語(yu)句的上下文中。這(zhe)些(xie)語(yu)句可以(yi)使用括(kuo)號括(kuo)起(qi)來。

ExpressionStmt = Expression .

下面的(de)內置(zhi)函數不允許出現(xian)在語句(ju)的(de)上下文中:

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

發送語句

發(fa)送語句可以向通(tong)(tong)(tong)(tong)道(dao)發(fa)送一個值。通(tong)(tong)(tong)(tong)道(dao)表(biao)達(da)式必須是通(tong)(tong)(tong)(tong)道(dao)類(lei)型,通(tong)(tong)(tong)(tong)道(dao)方向必須允許發(fa)送操作,并且(qie)值類(lei)型是可以分配給通(tong)(tong)(tong)(tong)道(dao)元(yuan)素通(tong)(tong)(tong)(tong)道(dao)類(lei)型。

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

通道(dao)(dao)類型(xing)和值(zhi)(zhi)(zhi)表達式會(hui)(hui)在(zai)發(fa)送(song)(song)(song)之前求值(zhi)(zhi)(zhi)。發(fa)送(song)(song)(song)操作會(hui)(hui)一(yi)致阻(zu)塞,直到可以進行(xing)發(fa)送(song)(song)(song)操作。如(ru)果接(jie)收者已(yi)經準備好向(xiang)沒有緩(huan)存(cun)的通道(dao)(dao)發(fa)送(song)(song)(song)值(zhi)(zhi)(zhi)可以立即執行(xing)。如(ru)果通道(dao)(dao)內(nei)還有緩(huan)存(cun)空(kong)間(jian),向(xiang)通道(dao)(dao)內(nei)發(fa)送(song)(song)(song)值(zhi)(zhi)(zhi)也(ye)會(hui)(hui)立即執行(xing)。向(xiang)關(guan)閉(bi)的通道(dao)(dao)發(fa)送(song)(song)(song)數(shu)據(ju)會(hui)(hui)導致運行(xing)時恐慌(huang)。像值(zhi)(zhi)(zhi)為 nil 的通道(dao)(dao)發(fa)送(song)(song)(song)數(shu)據(ju)會(hui)(hui)一(yi)直阻(zu)塞。

ch <- 3  // send value 3 to channel ch

遞增/遞減語句

“++” 和 “--” 語(yu)(yu)句(ju)可(ke)以遞(di)增或(huo)(huo)者遞(di)減運(yun)算元一個無(wu)類型常(chang)量 1。作為(wei)一個賦值語(yu)(yu)句(ju),運(yun)算元必(bi)須是可(ke)尋址(zhi)的或(huo)(huo)者 map 的索引表達式(shi)。

IncDecStmt = Expression ( "++" | "--" ) .

下面的(de)賦值(zhi)語句在語義(yi)上是等價的(de):

IncDec statement    Assignment
x++                 x += 1
x--                 x -= 1

賦值

Assignment = ExpressionList assign_op ExpressionList .

assign_op = [ add_op | mul_op ] "=" .

所(suo)有左側運(yun)算元(yuan)(yuan)都必(bi)須是可(ke)尋址(zhi)的、map 索引表達式(shi)或空(kong)標識符其中(zhong)之一(yi)。運(yun)算元(yuan)(yuan)可(ke)以用(yong)括(kuo)號括(kuo)起來。

x = 1
*p = f()
a[i] = 23
(k) = <-ch  // same as: k = <-ch

對于賦值操作 x op= y 其中 op 為二元運算符,它和 x=x op (y) 是等價的,不過它只計算一次 x。op= 是(shi)單獨的一個詞匯(hui)單元,在賦值操作中左側(ce)表(biao)達(da)式(shi)和(he)右(you)側(ce)表(biao)達(da)式(shi)必須(xu)都是(shi)單值表(biao)達(da)式(shi),并且左側(ce)表(biao)達(da)式(shi)不能(neng)是(shi)空白標識符。

a[i] <<= 2
i &^= 1<<n

元祖(zu)賦值語句會(hui)把運(yun)(yun)(yun)算(suan)返(fan)回(hui)(hui)的(de)多個值分別分配給變量列表。它有兩種格(ge)式(shi),第一(yi)種:它是返(fan)回(hui)(hui)多值的(de)表達(da)式(shi),例如(ru)函數(shu)調用、通道(dao)和 map 運(yun)(yun)(yun)算(suan)、類(lei)型斷言。左側運(yun)(yun)(yun)算(suan)元的(de)數(shu)量必須等于(yu)返(fan)回(hui)(hui)值的(de)數(shu)量。如(ru)果函數(shu)返(fan)回(hui)(hui)兩個值:

x, y = f()

它會(hui)將第(di)一個(ge)返回(hui)(hui)值(zhi)分(fen)配給 x ,把第(di)二個(ge)返回(hui)(hui)值(zhi)分(fen)配給 y。第(di)二種格式中,左(zuo)側(ce)運算元(yuan)的(de)數(shu)量必須等于右(you)側(ce)運算元(yuan)的(de)數(shu)量。每個(ge)表達式都只能返回(hui)(hui)單一值(zhi),右(you)側(ce)第(di) n 個(ge)值(zhi)會(hui)賦值(zhi)給左(zuo)側(ce)第(di) n 個(ge)變量。

one, two, three = '一', '二', '三'

空標識符(fu)可以(yi)在(zai)分配時(shi)忽(hu)略一個右面(mian)位(wei)置的(de)表達式:

_ = x       // evaluate x but ignore it
x, _ = f()  // evaluate f() but ignore second result value

賦值(zhi)分為兩個階段(duan)。首先會(hui)計(ji)(ji)算(suan)左側運(yun)算(suan)元(yuan)的索引表(biao)(biao)達式(shi)和指針(zhen)的解引用工作并(bing)以(yi)一定順序計(ji)(ji)算(suan)右側表(biao)(biao)達式(shi)的值(zhi)。

然后(hou)依次對左側運算元賦值。

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}

在賦值語句中(zhong)每(mei)個值都必須(xu)能分配給左側指(zhi)定(ding)類型的值。除了(le)以下特例:

  1. 任何類(lei)型都能分配給空(kong)標識符。

  2. 如果把無類(lei)型(xing)(xing)常量分(fen)配給接口(kou)類(lei)型(xing)(xing)或者空標識符,它會轉換成默認類(lei)型(xing)(xing)。

  3. 如果無類型的布爾值分配給了接口類型或者空標識符,它會先轉換成 bool 類型。

if 語句

if 語句根據布爾值表達式的值來決定執行條件分支的代碼。如果表達式為真,就執行 if 分支內的代碼,否則執行 else 分支的代碼。

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

表達式(shi)(shi)可能先于普(pu)通語句,它會在表達式(shi)(shi)求值(zhi)之前發生。

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

switch 語句

for 語句

for 語句可以(yi)用(yong)來重復執行(xing)一段代碼。它有三種格式:迭代器可以(yi)是單一條件、for 分句或者 range 語句。

ForStmt = "for" [ Condition | ForClause | RangeClause ] Block .
Condition = Expression .
單一條件的 for 語句

這(zhe)種情況下 for 會在(zai)條(tiao)件為 true 時一直(zhi)重(zhong)復。條(tiao)件會在(zai)每次(ci)迭代時都(dou)重(zhong)新(xin)計(ji)算。如果沒(mei)有指定條(tiao)件,默認一直(zhi)為 true。

for a < b {
	a *= 2
}
帶分句的 for 語句

帶(dai)分句(ju)的 for 語句(ju)也是(shi)由(you)條(tiao)件控制(zhi),只是(shi)它有一個(ge)初始化和寄(ji)送(song)的過(guo)程(cheng)(cheng)。例如賦值、遞增(zeng)或者(zhe)遞減語句(ju)。初始化語句(ju)可以是(shi)短變量聲明,但是(shi)寄(ji)送(song)語句(ju)不能。在(zai)初始化語句(ju)中(zhong)聲明的變量可以在(zai)迭代過(guo)程(cheng)(cheng)中(zhong)使用。

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

如果初始化語(yu)句非空,它會(hui)在(zai)進入迭代(dai)前執行一(yi)次;post 語(yu)句在(zai)每次循(xun)環后(hou)都會(hui)執行一(yi)次。在(zai)只有(you)條件(jian)(jian)的情況下可以省(sheng)略分號。如果缺(que)省(sheng)條件(jian)(jian)語(yu)句,默認為(wei) true。

for cond { S() }    is the same as    for ; cond ; { S() }
for      { S() }    is the same as    for true     { S() }
帶 range 分句的 for 語句

帶 range 分句的(de) for 語句可(ke)以訪(fang)問數組、切片、字(zi)符串(chuan)、map 的(de)所(suo)有元素(su),還(huan)可(ke)以從(cong)通道(dao)中接收值。迭代(dai)獲得元素(su)分配(pei)給了相應的(de)迭代(dai)變量并執行代(dai)碼塊(kuai)。

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

右側的(de)(de) range 分(fen)(fen)句表達(da)式叫(jiao)做 range 表達(da)式,它可(ke)能是(shi)(shi)數組、數組的(de)(de)指針、切(qie)片、字符(fu)串、map 或通道接(jie)收(shou)者類型。在分(fen)(fen)配時,左側運算元(yuan)必須是(shi)(shi)可(ke)尋址的(de)(de)或者 map 的(de)(de)索引表達(da)式;它們作(zuo)為迭代變(bian)量(liang)。如果 range 表達(da)式是(shi)(shi)一個(ge)(ge)(ge)通道類型,至少需要有(you)一個(ge)(ge)(ge)變(bian)量(liang),它也可(ke)以有(you)兩個(ge)(ge)(ge)變(bian)量(liang)。如果迭代變(bian)量(liang)是(shi)(shi)空標識符(fu),就代表在分(fen)(fen)句中不存在該標識符(fu)。

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
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 語句會開始在相同地址空間(jian)中的單獨 goroutine 中調用函數。

GoStmt = "go" Expression .

表(biao)(biao)達式(shi)必須(xu)是函數或者(zhe)方法調用(yong);它不能使用(yong)括(kuo)號括(kuo)起來,調用(yong)內置(zhi)函數有表(biao)(biao)達式(shi)語句(ju)的限制。

函(han)數(shu)的(de)(de)值和參數(shu)會(hui)(hui)按順序(xu)(xu)在調用(yong)的(de)(de) goroutine 中求值。不像普(pu)通的(de)(de)函(han)數(shu)調用(yong),程序(xu)(xu)不會(hui)(hui)等待函(han)數(shu)調用(yong)完成,而(er)是直接開啟一個新的(de)(de) goroutine 執(zhi)行(xing)函(han)數(shu)。函(han)數(shu)退(tui)(tui)出時,goroutine 也會(hui)(hui)退(tui)(tui)出。函(han)數(shu)的(de)(de)任何返回值都會(hui)(hui)被(bei)丟(diu)棄。

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

select 語句

select 語句(ju)會在接收/發(fa)送操作(zuo)集中(zhong)選擇一(yi)個(ge)執(zhi)行。它看(kan)起來和 switch 很(hen)像(xiang),只(zhi)不過是專門(men)針對通信操作(zuo)的。

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

接(jie)收(shou)表(biao)達式(shi)可以(yi)將接(jie)收(shou)表(biao)達式(shi)的(de)值分配給一個或兩個變(bian)量(liang)。接(jie)收(shou)表(biao)達式(shi)必須(xu)是一個接(jie)收(shou)運算元(可以(yi)使用(yong)括號括起來(lai))。它(ta)最多允許(xu)有一個 default 語句。

select 語(yu)句(ju)執行以下幾個步驟(zou):

  1. 對于(yu) select 語句(ju)的所有分句(ju),接收操作的通道運算元、通道、發送語句(ju)的右側表達式都會執(zhi)行一次操作。

  2. 如果一(yi)個或多(duo)個通信同時(shi)發生,它會通過一(yi)致性(xing)隨機選擇(ze)一(yi)個執行。如果沒有(you) default 語句(ju),select 語句(ju)會一(yi)直(zhi)阻塞。

  3. 除了 default 分(fen)句,其他(ta)分(fen)句只有在(zai)開始(shi)進行(xing)通(tong)信的時(shi)候才會(hui)執行(xing)。

  4. 如果 select 分(fen)句(ju)是一個接收語句(ju),它可以(yi)給變量分(fen)配(pei)值。

  5. 執行 select 分句內的內容。

如果向 nil 通道發送信息在沒(mei)有 default 分(fen)句的情(qing)況下(xia)會一(yi)直阻塞。

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 語句

return 語句會終止函數 F 的(de)執(zhi)行(xing)并可選的(de)返回(hui)一個或多個返回(hui)值。所有的(de)滯后函數都會在 F 返回(hui)到它的(de)調用者之前執(zhi)行(xing)。

ReturnStmt = "return" [ ExpressionList ] .

如果函(han)數沒有返回值類(lei)型,return 不能返回任何值。

func noResult() {
	return
}

有(you)三種方式能(neng)夠返(fan)回指(zhi)定類型的值:

  1. 返回值可以直接在 return 語句中列出。每個表達式都必須返回一個值并且能夠分配給相應的返回值類型。
func simpleF() int {
	return 2
}

func complexF1() (re float64, im float64) {
	return -7.0, -4.0
}
  1. return 語句的表達式列表可以是一個返回多值的函數調用。這時會使用臨時變量來獲取函數調用的返回值并直接將其作為 return 語句的表達式列表。
func complexF2() (re float64, im float64) {
	return complexF1()
}
  1. 如果制定了返回值的標識符那么 return 的表達式列表可以為空。返回值參數會作為普通的本地變量按需分配。return 語句會直接返回它們。
func complexF3() (re float64, im float64) {
	re = 7.0
	im = 4.0
	return
}

func (devnull) Write(p []byte) (n int, _ error) {
	n = len(p)
	return
}

不管如何(he)聲明,所有的返回(hui)值(zhi)都會在進入函(han)數(shu)(shu)前(qian)提前(qian)初始化成類型的零值(zhi)。return 語句(ju)會在所有 defer 函(han)數(shu)(shu)之前(qian)指定返回(hui)值(zhi)。

實現限制:編譯器不允許在覆蓋(gai)了命名返(fan)回值的(de)作用域中直接返(fan)回。

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

break 語句

break 語句會在 forswitchselect 語句內部(bu)退出到相(xiang)同函數的某個位置(zhi)。

BreakStmt = "break" [ Label ] .

如果想指定標簽,它必須出現在它所中止的 forswitchselect 語句旁。

OuterLoop:
	for i = 0; i < n; i++ {
		for j = 0; j < m; j++ {
			switch a[i][j] {
			case nil:
				state = Error
				break OuterLoop
			case item:
				state = Found
				break OuterLoop
			}
		}
	}

continue 語句

continue 語句會提前 for 語句的下一次迭代。for 語句必須和 continue 在相同函數中。

RowLoop:
	for y, row := range rows {
		for x, data := range row {
			if data == endOfRow {
				continue RowLoop
			}
			row[x] = data + bias(x, y)
		}
	}

goto 語句

goto 會將程(cheng)序跳轉到相同函(han)數的指定(ding)標簽處(chu)。

GotoStmt = "goto" Label .
goto Error

goto 語(yu)句不允(yun)許(xu)跳過作(zuo)用域內程序變(bian)量的初始化工作(zuo)。

goto L  // BAD
	v := 3
L:

上面的(de)程(cheng)序是錯誤的(de),因為(wei)它跳過(guo)了變量 v 的(de)初始化過(guo)程(cheng)。

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

標簽(qian)作(zuo)用域外(wai)的(de)(de) goto 語句不能跳(tiao)轉到標簽(qian)處(chu),所以(yi)上面的(de)(de)代(dai)碼(ma)是錯誤的(de)(de)。

Fallthrough 語句

fallthrough 語句會跳轉到 switch 語句中的(de)下(xia)一個(ge)(ge) case 分句中。它應該(gai)只在最后一個(ge)(ge)非空分句中使(shi)用。

FallthroughStmt = "fallthrough" .

Defer 語句

defer 語句會在(zai)包裹函(han)(han)數(shu)返回后觸(chu)(chu)發(fa)函(han)(han)數(shu)調用(yong)。這(zhe)里的返回泛指函(han)(han)數(shu)因為 return 語句終止、到(dao)達函(han)(han)數(shu)末尾或者當前 goroutine 觸(chu)(chu)發(fa)運行時(shi)恐慌(huang)。

DeferStmt = "defer" Expression .

表(biao)達(da)式(shi)必須是(shi)函(han)數或者方(fang)法調用(yong);它不能使用(yong)括號括起來,調用(yong)內置(zhi)函(han)數會有一些(xie)限制(zhi)。

每次執(zhi)行 defer 語(yu)句執(zhi)行時(shi)都會(hui)(hui)計算函(han)(han)(han)數(shu)的參數(shu)和值,但是并不(bu)會(hui)(hui)調用函(han)(han)(han)數(shu)。相(xiang)反,函(han)(han)(han)數(shu)的調用是在包裹(guo)函(han)(han)(han)數(shu)返回(hui)后進行,它們的執(zhi)行順序與聲明順序正好相(xiang)反。如果 defer 對應(ying)的函(han)(han)(han)數(shu)值為 nil,會(hui)(hui)在調用函(han)(han)(han)數(shu)的時(shi)候導致運行時(shi)恐慌而不(bu)是聲明 defer 語(yu)句的時(shi)候。

例(li)如:當 defer 函數(shu)為(wei)函數(shu)字面(mian)值(zhi)(zhi)且包裹函數(shu)具有命名結果值(zhi)(zhi),此時,我們在defer 函數(shu)中可以訪問和(he)修(xiu)改命名的(de)(de)結果值(zhi)(zhi)。defer 函數(shu)的(de)(de)所有返回值(zhi)(zhi)都會被忽略。

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 1
func f() (result int) {
	defer func() {
		result++
	}()
	return 0
}

內置函數

內置(zhi)函(han)數(shu)是預定(ding)義的。調用他(ta)(ta)們和其他(ta)(ta)函(han)數(shu)一(yi)(yi)樣只是他(ta)(ta)們接(jie)受(shou)一(yi)(yi)個類(lei)型而(er)不(bu)是一(yi)(yi)個表達式。

內置函(han)數沒(mei)有標準的(de) Go 類(lei)型(xing),所(suo)以他們只能作(zuo)為(wei)調(diao)用表(biao)達式;而不(bu)能作(zuo)為(wei)函(han)數的(de)值。

Close

對于管道類型 c,內置函數 close(c) 意味著不在有數據插入到管道中。如果 c 是一個只接(jie)收數據(ju)(ju)的管(guan)(guan)道,會(hui)發生錯誤。向(xiang)已經(jing)關(guan)閉(bi)(bi)(bi)的發送數據(ju)(ju)或者重復關(guan)閉(bi)(bi)(bi)已經(jing)關(guan)閉(bi)(bi)(bi)的管(guan)(guan)道會(hui)導(dao)致(zhi)運行時(shi)(shi)恐(kong)慌。關(guan)閉(bi)(bi)(bi) nil 管(guan)(guan)道會(hui)引(yin)起(qi)運行時(shi)(shi)恐(kong)慌。調用 close 后所有(you)之(zhi)前(qian)發送的數據(ju)(ju)都能接(jie)收到,并且在最后不(bu)會(hui)阻塞而返(fan)回零(ling)值。多值的接(jie)收操作能夠返(fan)回接(jie)收到的數據(ju)(ju)和表示管(guan)(guan)道是否關(guan)閉(bi)(bi)(bi)的布爾(er)值。

長度和容積

內置函數 lencap 可(ke)以接(jie)收(shou)多種類(lei)型的參數,并且返回一個 int 類(lei)型結(jie)果值。函數的實現能夠確保結(jie)果值不會溢出。

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

切片的(de)容積底層(ceng)數組包(bao)含(han)的(de)元(yuan)素個數。在任何情況下(xia)(xia)都有以(yi)下(xia)(xia)關系:

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

nil 切片,map,或者 channel 的長度都為 0。nil 切(qie)片(pian),管道的容(rong)積都(dou)為 0。

表達式 len(x)s 是字符串常量時也為常量。如果 s 為數組或者指向數組的指針并且表達式 s 不包含 channel 接收器或者函數調用那么 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*T 類型指針。變量的初(chu)(chu)始(shi)化在初(chu)(chu)始(shi)化值(zhi)章節中介紹。

new(T)

例如:

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

給 S 類型的變量分配空間,并初始化它(a=0b=0.0),并且返回一個 *S 類型值保存變量所在(zai)的位(wei)置(zhi)。

創建切片,map 和 管道

內置函數 make 以一個(ge)(ge)類(lei)型(xing)作(zuo)為(wei)參數(shu),它必須(xu)是一個(ge)(ge)切片,map 或者(zhe)管道類(lei)型(xing),它返回一個(ge)(ge) T 類(lei)型(xing)的(de)值,而不是(*T)類(lei)型(xing),它會(hui)按初始化值章節描述(shu)的(de)方(fang)式進行初始化。

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

n 和 m 必須是整數類型或者無類型常量。一個常量參數不能為負數并且該值在 int 類型的范圍內;如果它是無類型常量,會被轉換成 int 類型。如(ru)果(guo) n 和 m 都是(shi)常量,那么(me) n 必須大于 m。如(ru)果(guo) n 是(shi)負數(shu)或者大于 m 會引發運行(xing)時 panic。

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

使用 make 來(lai)指(zhi)定大小(xiao)初始(shi)化(hua) map 類(lei)型將會(hui)創(chuang)建一個預留(liu) n 個元素空間的 map 類(lei)型。更(geng)詳(xiang)細的行(xing)為依賴(lai)于具體(ti)實現。

追加或者拷貝切片

內置函數 appendcopy 可以(yi)進行切片的通用(yong)(yong)操作。對于這兩個(ge)函數(shu),一個(ge)是拷貝內(nei)存(cun),一個(ge)是引用(yong)(yong)內(nei)存(cun)。

可變參數的函數 append 可以向切片 s 中追加一個或多個 x 值,并返回這個切片。傳進 ...T 的值會根據參數傳值。作為特例,append 在 s 為 []byte 切片時,可以使用字符串后面跟 ... 作為參數。

如果 s 的容積容納不下這些元素,那么 append 會分(fen)配一(yi)個新的足夠大的數(shu)組。否則(ze)會使用原(yuan)來的底層(ceng)數(shu)組。

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 可以從 string 類型拷貝元素到 []byte 類(lei)型。這會把字(zi)符串中的(de)元素拷貝到字(zi)節切片中。

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")
刪除 map 中的元素

內置函數 delete 移除 map 類型 m 中的(de)鍵值 k。k 的(de)類型必須是能夠轉換成 m 鍵類型的(de)類型。

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

如果 map 類型 m 是 nil 或者 m[k] 不存在,那么 delete 函數(shu)不做任何事(shi)情。

操作復數

有三個函數可以組裝或者分解復數。內置函數 complex 會構造一個復數,realimag 會分解出復數的實部和(he)虛部。

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

參數的類型和返回值類型是對應的。對于 complex,兩個參數必須是相同的浮點類型,并返回由相同浮點數組成的復數類型。complex64float32 對應的類型,complex128float64 對應的(de)參(can)(can)數(shu)類(lei)型(xing)(xing)。如(ru)果(guo)參(can)(can)數(shu)是一(yi)(yi)個(ge)無(wu)類(lei)型(xing)(xing)常量(liang),它(ta)會轉換(huan)成另一(yi)(yi)個(ge)參(can)(can)數(shu)的(de)類(lei)型(xing)(xing)。如(ru)果(guo)兩(liang)個(ge)參(can)(can)數(shu)都是無(wu)類(lei)型(xing)(xing)常量(liang),他們必須實數(shu)或者虛數(shu)部分為零(ling),并且它(ta)會返回一(yi)(yi)個(ge)無(wu)類(lei)型(xing)(xing)的(de)復數(shu)常量(liang)。

realimag 函數和 complex 正好相反的,所以對于一個值復數類型 Z 的值 z,z==Z(complex(real(z),imag(z)))

如果這么操作都是常(chang)量,那么返回的值也是常(chang)量。

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 uint = complex(1, 0)         // untyped complex constant 1 + 0i can be converted to uint
_ = 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
處理 panic

兩個內置函數 panicrecover,可以拋出和處理運行時 panic 和程序的錯(cuo)誤(wu)條件。

func panic(interface{})
func recover() interface{}

當執行 F 函數時,顯式的調用 panic或者運行時 panic 都會中斷 F 的執行。但是 F 中的延遲函數還會執行。接下來調用 F 函數處的延遲函數也會執行,一直到頂級的延遲函數。鑒于這點,程序關閉并且錯誤條件可以拋出。包括 panic 中的值。這個順序叫做 panicking

panic(42)
panic("unreachable")
panic(Error("cannot parse"))

recover 函數允許程序從一個 panicking 中恢復執行。假設函數 G 延遲執行函數 D ,在 D 中調用 recover 這時如果在 G 執行時發生 panic 會在 D 中恢復。當函數執行到 D,recover 的返回值會返回 panic 對應的錯誤,并且終止 panicking 。在這個情況下 G 函數和 panic 之間的(de)代碼(ma)不(bu)會(hui)執行。任何在 D 中(zhong) G 之前的(de)延遲函數會(hui)返(fan)回(hui)到調用者。

在下面兩種情況下 recover 會返回 nil:

  • panic 的(de)參數為 nil

  • 攜程里沒有發生 panic

  • recover 不是在延遲(chi)函數中(zhong)執行

本例中的 protect 函數(shu)會在 g 發生(sheng) panic 的時候恢復(fu)執(zhi)行。

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()
}
初始化

這個實現提(ti)供了多個內置(zhi)函(han)數(shu)來幫助進行初始化。這些函(han)數(shu)用(yong)來輸出信(xin)息但是不確(que)定會一直存在(zai)于(yu)語言(yan)中,他們都沒有返(fan)回值。

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 不接(jie)受除(chu)了布爾(er)值,數字(zi),字(zi)符(fu)串以外的其他類型。

程序的初始化和執行

零值

當為變量分配內存空間時,不管是聲明還是調用 new 或者使用字面值和 make 初始化,只要創建了一個新值變量都會有一個默認值。這樣的元素和值會使用它類型的零值:false 是布爾值(zhi)的(de)零值(zhi),0 為數(shu)值(zhi)類(lei)型零值(zhi),"" 為字(zi)(zi)符串零值(zhi),nil 為指針(zhen),函數(shu),接(jie)口,切(qie)片,頻(pin)道,字(zi)(zi)典。初始化會遞歸完成,所以結構體里的(de)數(shu)組中的(de)元素也都會有它自己的(de)零值(zhi)。

下面兩個聲明時相(xiang)等(deng)的:

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

這和下面的聲(sheng)明時同(tong)等(deng)效果的:

var t T
包的初始化

保級(ji)變量(liang)會按聲明的順序進(jin)行(xing)(xing)初始化(hua)(hua),如(ru)果依賴其他變量(liang),則會在其他變量(liang)之(zhi)后進(jin)行(xing)(xing)初始化(hua)(hua)。

更確切的(de)說,如果包級變(bian)(bian)量(liang)(liang)(liang)還沒初始(shi)(shi)化并且沒有初始(shi)(shi)化表達式或(huo)者表達式中不包含(han)對(dui)其他(ta)未(wei)初始(shi)(shi)化變(bian)(bian)量(liang)(liang)(liang)的(de)依賴(lai),那么會認為它正在等待初始(shi)(shi)化。初始(shi)(shi)化過(guo)程會從最早聲明的(de)變(bian)(bian)量(liang)(liang)(liang)開始(shi)(shi)向下(xia)一個包級變(bian)(bian)量(liang)(liang)(liang)重復,直(zhi)到沒有需(xu)要(yao)初始(shi)(shi)化的(de)變(bian)(bian)量(liang)(liang)(liang)。

如(ru)果(guo)在初始化過程完成后還有(you)未初始化的變量,那么這(zhe)些(xie)變量可能是循環初始化了,這(zhe)事程序不是合法(fa)的。

在多個文件(jian)(jian)中變量的(de)(de)聲(sheng)明順序(xu)會依據編譯(yi)時(shi)文件(jian)(jian)出(chu)現(xian)的(de)(de)順序(xu):聲(sheng)明在第一個文件(jian)(jian)中的(de)(de)變量優(you)先于第二個文件(jian)(jian)中聲(sheng)明的(de)(de)變量,依此(ci)類推。

對(dui)依(yi)賴關系的(de)(de)分(fen)(fen)析(xi)不會根(gen)據變(bian)量的(de)(de)具體值,它只分(fen)(fen)析(xi)在源碼中是否引用了其他(ta)變(bian)量。例(li)如(ru),如(ru)果變(bian)量 x 的(de)(de)初始化表達式引用了變(bian)量 y 那么(me) x 就依(yi)賴于 y:

  • 引用一(yi)個變量(liang)或者函數中用到了一(yi)個變量(liang)

  • 引用了一個方法值 m 或者方法表達式 t.m (這里的靜態類型 t 不是借口類型,并且方法 mt 方法集中的方法)。t.m 的(de)返回值不會在此時(shi)影響。

  • 變量,函數,或者方法 x 依賴變量 y

依賴分析(xi)會(hui)在每個包中(zhong)執行;他(ta)只考慮(lv)當前包中(zhong)的析(xi)變(bian)量,函數,和方法(fa)。

例如,給定聲明:

var (
	a = c + b
	b = f()
	c = f()
	d = 3
)

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

初始化(hua)順序為 d,b,c,a。

變量可以在包中聲明的初始化函數 init 中進行初始(shi)化,它沒有參數(shu)和返回值。

func init() {}

可以(yi)為每個包定義多個該(gai)函數(shu),甚至在(zai)一個文件(jian)中(zhong)也(ye)可以(yi)。并且(qie)不會(hui)聲(sheng)明該(gai)該(gai)標識符(fu)。因(yin)此 init 函數(shu)不能在(zai)程(cheng)序中(zhong)調用。

還未導入的包會先初始化包級的變量然后按照 init 函數在源碼中的(de)順序調用,它可能在包(bao)(bao)的(de)多個(ge)文件中。如果(guo)需要導(dao)(dao)入一個(ge)包(bao)(bao),它會在初始化自己之前先初始化這(zhe)個(ge)需要導(dao)(dao)入的(de)包(bao)(bao)。如果(guo)導(dao)(dao)入一個(ge)包(bao)(bao)多次,那這(zhe)個(ge)包(bao)(bao)只(zhi)會初始化一次。導(dao)(dao)入的(de)包(bao)(bao)不能存在循環(huan)引用。

包的初始化——變量初始化和對 init 函數的調用會按順序發生在同一個 goroutine 中。 init 函數可能會啟動其他 goroutine。不過一般 init 函數都是按序進行初(chu)始(shi)化的:它只在(zai)上一步已經執行完成時(shi)才(cai)會(hui)調(diao)用下一個(ge)步驟。

確(que)保初始化行為是可以復現的(de),構建(jian)系統鼓勵在(zai)同一個(ge)包中(zhong)包含多個(ge)文件(jian)這(zhe)些文件(jian)在(zai)編譯器中(zhong)會(hui)以字母排(pai)序。

程序執行

一個完整的程序由一個 main 包導入所有需要的包。main 包必須以 main 作為包名并且聲明一個沒有參數和返回值的 main 函數。

func main() {}

程序先初始化 main 包然后調用 main 函數。當 main 函數返(fan)回(hui)時(shi),程序就會(hui)退出(chu)。它不會(hui)等待其他 goroutines 完成。

錯誤

預定義的錯誤類型(xing)為:

type error interface {
	Error() string
}

它(ta)是(shi)表示錯(cuo)誤信息(xi)的常規接口,nil 代表沒有發生錯(cuo)誤。例如,在文件中(zhong)讀取數據可以定義為:

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

運行時恐慌

運行時錯誤(例如數組的越界訪問)會造成運行時恐慌,它和以 runtime.Error 接口實現調用內置的 panic 函數一樣。runtime.Error 滿足預定義的 error 接(jie)口(kou)。不同(tong)的(de)(de)錯(cuo)誤(wu)值代表(biao)不同(tong)的(de)(de)運行(xing)時錯(cuo)誤(wu)條件(jian)。

package runtime

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

系統相關

unsafe 包

unsafe 是編譯器已知的內置包,可以通過導入路徑 unsafe 訪問包內容,提供 unsafe 包目的是支持底層編程(包括操作非 Go 類型的數據結構)。使用 unsafe 包必須自己保證類型安全而且它有可能破壞程序的移植性。unsafe 包提供(gong)了以下(xia)接(jie)口:

package unsafe

type ArbitraryType int  // 任意一個 Go 類型;它不是一個具體的類型。
type Pointer *ArbitraryType

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

Pointer 是一個指針類型,但是不能解引用 Pointer 的值。所有底層類型 uintptr 的指針和值都能轉換成 Pointer 類型,反之亦然。Pointeruintptr 之間(jian)的(de)轉換效果由具體實現定義。

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

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

var p ptr = nil

假設變量 v 由 var v = x 定義。Alignof 以表達式 x 作為參數并返回 x 的對齊字節數。Sizeof 以表達式 x 作為(wei)參(can)數并(bing)返回(hui) x 的大小。

函數 Offsetof 以選擇器 s.f( s 或者 *s 結(jie)構體中(zhong)的 f 字段(duan)(duan))作為參數,返回字段(duan)(duan)相(xiang)對結(jie)構體首地址(zhi)的位置(zhi)。如果(guo) f 是一個嵌入字段(duan)(duan),那 f 必須(xu)可以直接訪問(wen)(不能(neng)通(tong)過指針進行(xing)間(jian)接訪問(wen))。對于結(jie)構體 s 的 f 字段(duan)(duan):

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

計算機的體系結構要求對齊內存地址(對于一個變量的地址有多種因素影響對齊)。Alignof 函(han)數(shu)獲取一(yi)個人(ren)和類型的表達式(shi)并返回變量對齊的字(zi)節數(shu)。對于變量 x:

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

編譯時 uintptr 類型常量表達式會調用 AlignofOffsetof,和 Sizeof

確定的大小和對齊字節數

對于數字類型,確定有(you)以下尺(chi)寸:

type                                 size in bytes

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

Go 中規定的最(zui)小(xiao)對(dui)齊特性:

  1. 對于任意變量類型 x:unsafe.Alignof(x) 至少為 1。

  2. 對于結構體類型:unsafe.Alignof(x) 是所有內部字段 unsafe.Alignof(x.f) 的最大值,并(bing)且至(zhi)少為 1。

  3. 對于數組類型:unsafe.Alignof(x) 和數組(zu)元素類型的(de) alignment 相同。

結構體(ti)(數(shu)組)在(zai)內部沒(mei)有字段(元素(su))的(de)(de)時候大(da)小為 0。兩(liang)個所占空(kong)間(jian)大(da)小為 0 的(de)(de)不同變量可能在(zai)內存(cun)中擁(yong)有相同地址。

posted @ 2025-03-05 11:14  張占嶺  閱讀(83)  評論(0)    收藏  舉報