《effective-go》 學習筆記

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"格式化"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"gofmt是一個cli程序,會優先讀取標準輸入,如果傳入了文件路徑的話,會格式化這個文件,如果傳入一個目錄,會格式化目錄中所有.go文件,如果不傳參數,會格式化當前目錄下的所有.go文件"}]}]}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"註釋"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"命令godoc是一個很強大的工具,同樣用於展示指定代碼包的文檔"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https://www.jianshu.com/p/b91c4400d4b2"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在程序中,每個可導出(首 字母大寫)的名稱都應該有文檔註釋"}]}]}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"命名"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"包應當以小寫的單個單詞來命名,且不應使用下劃線或駝峯記法"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"它並不需要在所有源碼中保持唯一,即便在少數發生衝突的情 況下, 也可爲導入的包選擇一個別名來局部使用。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"包的導入者可通過包名來引用其內容,因此包中的可導出名稱可以此來避免衝突。 (請勿使 用 import . 記法,它可以簡化必須在被測試包外運行的測試, 除此之外應儘量避免使用。) 例如,bufio 包中的緩存讀取器類型叫做 Reader 而非 BufReader,因爲用戶將它看做 bufio.Reader,這是個清楚而簡潔的名稱。 "}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"GetOwner。大寫字母即爲可導出的這種規定爲區分方法和字段提供了便利。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"按照約定,只包含一個方法的接口應當以該方法的名稱加上 - er 後綴來命名,如 Reader、 Writer、 Formatter、CloseNotifier 等。諸如此類的命名有很多,遵循它們及其代表的函數名會讓事情變得簡單。 Read、Write、 Close、Flush、 String 等都具有典型的簽名和意義。爲避免衝突,請不要用這些名稱爲你的 方法命名, 除非你明確知道它們的簽名和意義相同。反之,若你的類型實現了的方法, 與一 個衆所周知的類型的方法擁有相同的含義,那就使用相同的命名。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Go 中約定使用駝峯記法 MixedCaps 或 mixedCaps。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"分號"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"無論如何,你都不應將一個控制結構(if、for、switch 或 select)的左大括號放在下一 行。如果這樣做,就會在大括號前面插入一個分號,這可能引起不需要的效果。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"控制結構"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"與C語言差異"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Go 不再使用 do 或 while 循環,只有一個更通用的 for;switch 要更靈活一點;if 和 switch 像 for 一樣可接受可選 的初始化語句; 此外,還有一個包含類型選擇和多路通信複用器的新控制結構:select。 其 語法也有些許不同:沒有圓括號,而其主體必須始終使用大括號括住。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"重新聲明與再次賦值"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在滿足下列條件時,已被聲明的變量 v 可出現在:= 聲明中: "}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(1)本次聲明與已聲明的 v 處於同一作用域中(若 v 已在外層作用域中聲明過,則此次聲明 會創建一個新的變量 §)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(2)在初始化中與其類型相應的值才能賦予 v,且 "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(3)在此次聲明中至少另有一個變量是新聲明的。 "}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"For"}]},{"type":"codeblock","attrs":{"lang":"golang"},"content":[{"type":"text","text":"// 如同 C 的 for 循環 \nfor init; condition; post { } \n// 如同 C 的 while 循環 \nfor condition { } \n// 如同 C 的 for(;;) 循環 \nfor { }"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"若你想遍歷數組、切片、字符串或者映射,或從信道中讀取消息, range 子句能夠幫你輕鬆 實現循環。 "}]},{"type":"codeblock","attrs":{"lang":"golang"},"content":[{"type":"text","text":"for key, value := range oldMap { \n\tnewMap[key] = value \n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"switch"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Go 的 switch 比 C 的更通用。其表達式無需爲常量或整數,case 語句會自上而下逐一進行求 值直到匹配爲止。若 switch 後面沒有表達式,它將匹配 true,因此,我們可以將 if-else-ifelse 鏈寫成一個 switch,這也更符合 Go 的風格。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"switch 並不會自動下溯,但 case 可通過逗號分隔來列舉相同的處理條件。 "}]}]}]},{"type":"codeblock","attrs":{"lang":"golang"},"content":[{"type":"text","text":"func shouldEscape(c byte) bool { \n\tswitch c { \n\tcase ' ', '?', '&', '=', '#', '+', '%': \n\t\treturn true \n\t} \n\treturn false\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"函數"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"多值返回"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Go 與衆不同的特性之一就是函數和方法可返回多個值。這種形式可以改善 C 中一些笨拙的習 慣: 將錯誤值返回(例如用 -1 表示 EOF)和修改通過地址傳入的實參。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"可命名結果形參"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Go 函數的返回值或結果 “形參” 可被命名,並作爲常規變量使用,就像傳入的形參一樣。 命 名後,一旦該函數開始執行,它們就會被初始化爲與其類型相應的零值; 若該函數執行了一 條不帶實參的 return 語句,則結果形參的當前值將被返回。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Defer"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Go 的 defer 語句用於預設一個函數調用(即推遲執行函數), 該函數會在執行 defer 的函數 返回之前立即執行。被推遲函數的實參(如果該函數爲方法則還包括接收者)在推遲執行時就會求值, 而不是在 調用執行時才求值。被推遲的函數按照後進先出(LIFO)的順序執行"}]}]}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"數據"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"new 分配"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Go 提供了兩種分配原語,即內建函數 new 和 make。 它們所做的事情不同,所應用的類型 也不同。它們可能會引起混淆,但規則卻很簡單。 讓我們先來看看 new。這是個用來分配內 存的內建函數, 但與其它語言中的同名函數不同,它不會初始化內存,只會將內存置零。 也 就是說,new(T) 會爲類型爲 T 的新項分配已置零的內存空間, 並返回它的地址,也就是一個 類型爲 "},{"type":"codeinline","content":[{"type":"text","text":"*T"}]},{"type":"text","text":" 的值。用 Go 的術語來說,它返回一個指針, 該指針指向新分配的,類型爲 T 的 零值。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"構造函數與複合字面"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"複合字面的字段必須按順序全部列出。但如果以 字段: 值 對的形式明確地標出元素,初始化 字段時就可以按任何順序出現,未給出的字段值將賦予零值。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"make 分配"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"再回到內存分配上來。內建函數 make(T, args) 的目的不同於 new(T)。它只用於創建切片、 映射和信道,並返回類型爲 T(而非 "},{"type":"codeinline","content":[{"type":"text","text":"*T"}]},{"type":"text","text":" )的一個已初始化 (而非置零)的值。 出現這種用 差異的原因在於,這三種類型本質上爲引用數據類型,它們在使用前必須初始化。 例如,切 片是一個具有三項內容的描述符,包含一個指向(數組內部)數據的指針、長度以及容量, 在這三項被初始化之前,該切片爲 nil。對於切片、映射和信道,make 用於初始化其內部的 數據結構並準備好將要使用的值。例如, "},{"type":"codeinline","content":[{"type":"text","text":"make([]int, 10, 100)"}]}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"數組"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以下爲數組在 Go 和 C 中的主要區別。在 Go 中, 數組是值。將一個數組賦予另一個數組會複製其所有元素。 特別地,若將某個數組傳入某個函數,它將接收到該數組的一份副本而非指針。 數組的大小是其類型的一部分。類型 [10]int 和 [20]int 是不同的。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"切片"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"切片保存了對底層數組的引用,若你將某個切片賦予另一個切片,它們會引用同一個數組。 若某個函數將一個切片作爲參數傳入,則它對該切片元素的修改對調用者而言同樣可見, 這 可以理解爲傳遞了底層數組的指針。因此,Read 函數可接受一個切片實參 而非一個指針和 一個計數;切片的長度決定了可讀取數據的上限。以下爲 os 包中 File 類型的 Read 方法籤 名: "}]}]}]},{"type":"codeblock","attrs":{"lang":"golang"},"content":[{"type":"text","text":"func (file `*`File) Read(buf []byte) (n int, err error)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"二維切片"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Go 的數組和切片都是一維的。要創建等價的二維數組或切片,就必須定義一個數組的數組, 或切片的切片,就像這樣: "}]}]}]},{"type":"codeblock","attrs":{"lang":"golang"},"content":[{"type":"text","text":"type Transform [3][3]float64 // A 3x3 array, really an array of arrays. \ntype LinesOfText [][]byte // A slice of byte slices."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由於切片長度是可變的,因此其內部可能擁有多個不同長度的切片。在我們的 LinesOfText 例 子中,這是種常見的情況:每行都有其自己的長度。"}]},{"type":"codeblock","attrs":{"lang":"golang"},"content":[{"type":"text","text":"text := LinesOfText{ \n\t[]byte(\"Now is the time\"), \n\t[]byte(\"for all good gophers\"), \n\t[]byte(\"to bring some fun to the party.\"), \n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有時必須分配一個二維數組,例如在處理像素的掃描行時,這種情況就會發生。 我們有兩種 方式來達到這個目的。一種就是獨立地分配每一個切片;而另一種就是隻分配一個數組, 將 各個切片都指向它。採用哪種方式取決於你的應用。若切片會增長或收縮, 就應該通過獨立 分配來避免覆蓋下一行;若不會,用單次分配來構造對象會更加高效。 以下是這兩種方法的 大概代碼,僅供參考。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(1)首先是一次一行的:"}]},{"type":"codeblock","attrs":{"lang":"golang"},"content":[{"type":"text","text":"// 分配頂層切片。 \npicture := make([][]uint8, YSize) // 每 y 個單元一行。 \n// 遍歷行,爲每一行都分配切片 \nfor i := range picture { \n\tpicture[i] = make([]uint8, XSize) \n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(2)現在是一次分配,對行進行切片:"}]},{"type":"codeblock","attrs":{"lang":"golang"},"content":[{"type":"text","text":"// 分配頂層切片,和前面一樣。 \npicture := make([][]uint8, YSize) // 每 y 個單元一行。 \n// 分配一個大的切片來保存所有像素 \npixels := make([]uint8, XSize*YSize) // 擁有類型 []uint8,儘管圖片是 [][]uint8. \n// 遍歷行,從剩餘像素切片的前面切出每行來。 \nfor i := range picture { \n\tpicture[i], pixels = pixels[:XSize], pixels[XSize:]\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"映射"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"映射是方便而強大的內建數據結構,它可以關聯不同類型的值。其鍵可以是任何相等性操作 符支持的類型, 如整數、浮點數、複數、字符串、指針、接口(只要其動態類型支持相等性 判斷)、結構以及數組。 切片不能用作映射鍵,因爲它們的相等性還未定義。與切片一樣, 映射也是引用類型。 若將映射傳入函數中,並更改了該映射的內容,則此修改對調用者同樣 可見。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"打印"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當然,映射中的鍵可能按任意順序輸出。當打印結構體時,改進的格式 %+v 會爲結構體的每 個字段添上字段名,而另一種格式 %#v 將完全按照 Go 的語法打印值。 "}]}]}]},{"type":"codeblock","attrs":{"lang":"golang"},"content":[{"type":"text","text":"type T struct {\n\ta int \n\tb float64 \n\tc string \n} \nt := &T{ 7, -2.35, \"abc\\tdef\" } \nfmt.Printf(\"%v\\n\", t) \nfmt.Printf(\"%+v\\n\", t) \nfmt.Printf(\"%#v\\n\", t) \nfmt.Printf(\"%#v\\n\", timeZone) \n// prints 將打印 \n&{7 -2.35 abc def} \n&{a:7 b:-2.35 c:abc def} \n&main.T{a:7, b:-2.35, c:\"abc\\tdef\"} \nmap[string] int{\"CST\":-21600, \"PST\":-28800, \"EST\":-18000, \"UTC\":0, \"MST\":-25200}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們的 String 方法也可調用 Sprintf, 因爲打印例程可以完全重入並按這種方式封裝。不過要 理解這種方式,還有一個重要的細節: 請勿通過調用 Sprintf 來構造 String 方法,因爲它會無 限遞歸你的的 String 方法。"}]}]}]},{"type":"codeblock","attrs":{"lang":"golang"},"content":[{"type":"text","text":"type MyString string func (m MyString) String() string { return fmt.Sprintf(\"MyString=%s\", m) // 錯誤:會無限遞歸 \n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"初始化"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"init 函數"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每個源文件都可以通過定義自己的無參數 init 函數來設置一些必要的狀態。 (其實每 個文件都可以擁有多個 init 函數。)而它的結束就意味着初始化結束: 只有該包中的所有變 量聲明都通過它們的初始化器求值後 init 纔會被調用, 而那些 init 只有在所有已導入的包都 被初始化後纔會被求值。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"方法"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"指針 vs. 值"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在之前討論切片時,我們編寫了一個 Append 函數。 我們也可將其定義爲切片的方法。爲 此,我們首先要聲明一個已命名的類型來綁定該方法, 然後使該方法的接收者成爲該類型的 值。 "}]}]}]},{"type":"codeblock","attrs":{"lang":"golang"},"content":[{"type":"text","text":"type ByteSlice []byte\nfunc (slice ByteSlice) Append(data []byte) []byte { \n\t// 主體和前面相同。 \n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們仍然需要該方法返回更新後的切片。爲了消除這種不便,我們可通過重新定義該方法, 將一個指向 ByteSlice 的指針作爲該方法的接收者, 這樣該方法就能重寫調用者提供的切片 了。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其實我們做得更好。若我們將函數修改爲與標準 Write 類似的方法,就像這樣,"}]},{"type":"codeblock","attrs":{"lang":"golang"},"content":[{"type":"text","text":"func (p *ByteSlice) Write(data []byte) (n int, err error) { \n\tslice := *p \n\t// 依舊和前面相同。 \n\t*p = slice \n\treturn len(data), nil \n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼類型 "},{"type":"codeinline","content":[{"type":"text","text":"*ByteSlice"}]},{"type":"text","text":" 就滿足了標準的 io.Writer 接口,這將非常實用。 例如,我們可以通過 打印將內容寫入。 "}]},{"type":"codeblock","attrs":{"lang":"golang"},"content":[{"type":"text","text":"var b ByteSlice \nfmt.Fprintf(&b, \"This hour has %d days\\n\", 7)"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們將 ByteSlice 的地址傳入,因爲只有 "},{"type":"codeinline","content":[{"type":"text","text":"*ByteSlice"}]},{"type":"text","text":" 才滿足 io.Writer。以指針或值爲接收者 的區別在於:值方法可通過指針和值調用, 而指針方法只能通過指針來調用。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"之所以會有這條規則是因爲指針方法可以修改接收者;通過值調用它們會導致方法接收到該 值的副本, 因此任何修改都將被丟棄,因此該語言不允許這種錯誤。不過有個方便的例外: 若該值是可尋址的, 那麼該語言就會自動插入取址操作符來對付一般的通過值調用的指針方 法。在我們的例子中,變量 b 是可尋址的,因此我們只需通過 b.Write 來調用它的 Write 方 法,編譯器會將它重寫爲 (&b).Write。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"順便一提,在字節切片上使用 Write 的想法已被 bytes.Buffer 所實現。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"接口與其它類型"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"接口"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Go中的接口爲指定對象的行爲提供了一種方法:如果某樣東西可以完成這個,那麼它就可以用在這裏。我們已經見過許多簡單的示例了;通過實現 String 方法,我們可以自定義打印函數,而通過 Write 方法,Fprintf 則能對任何對象產生輸出。在 Go 代碼中,僅包含一兩種方法的接口很常見,且其名稱通常來自於實現它的方法,如 io.Writer 就是實現了 Write 的一類對象。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每種類型都能實現多個接口。例如一個實現了 sort.Interface 接口的集合就可通過 sort 包中的 例程進行排序。該接口包括 Len()、Less(i, j int) bool 以及 Swap(i, j int),另外,該集合仍然可 以有一個自定義的格式化器。 以下特意構建的例子 Sequence 就同時滿足這兩種情況。"}]}]}]},{"type":"codeblock","attrs":{"lang":"golang"},"content":[{"type":"text","text":"type Sequence []int \n\n// Methods required by sort.Interface. \n// sort.Interface 所需的方法。 \nfunc (s Sequence) Len() int {\n\treturn len(s) \n} \nfunc (s Sequence) Less(i, j int) bool {\n\treturn s[i] < s[j] \n} \nfunc (s Sequence) Swap(i, j int) { \n\ts[i], s[j] = s[j], s[i] \n} \n\n// Method for printing - sorts the elements before printing. \n// 用於打印的方法 - 在打印前對元素進行排序。 \nfunc (s Sequence) String() string { \n\tsort.Sort(s) \n\tstr := \"[\" \n\tfor i, elem := range s { \n\t\tif i > 0 { \n\t\t\tstr += \" \" \n\t\t} \n\t\tstr += fmt.Sprint(elem) \n\t} \n\treturn str + \"]\"\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"類型轉換"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"該方法是通過類型轉換技術,在 String 方法中安全調用 Sprintf 的另個一例子。若我們忽略類 型名的話,這兩種類型(Sequence 和 []int)其實是相同的,因此在二者之間進行轉換是合法 的。 轉換過程並不會創建新值,它只是值暫讓現有的時看起來有個新類型而已。 (還有些合 法轉換則會創建新值,如從整數轉換爲浮點數等。)"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"接口轉換與類型斷言"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"類型選擇 是類型轉換的一種形式:它接受一個接口,在選擇 (switch)中根據其判斷選擇對 應的情況(case), 並在某種意義上將其轉換爲該種類型。以下代碼爲 fmt.Printf 通過類型 選擇將值轉換爲字符串的簡化版。若它已經爲字符串,我們需要該接口中實際的字符串值; 若它有 String 方法,我們則需要調用該方法所得的結果。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"類型斷言 若它所轉換的值中不包含字符串,該程序就會以運行時錯誤崩潰。爲避免這種情況, 需使 用 “逗號, ok” 慣用測試它能安全地判斷該值是否爲字符串: "}]}]}]},{"type":"codeblock","attrs":{"lang":"golang"},"content":[{"type":"text","text":"str, ok := value.(string) \nif ok \n{ \n\tfmt.Printf(\"string value is: %q\\n\", str) \n} else {\n fmt.Printf(\"value is not a string\\n\")\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"空白標識符"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"若某次賦值需要匹配多個左值,但其中某個變量不會被程序使用, 那麼用空白標識符來代替 該變量可避免創建無用的變量,並能清楚地表明該值將被丟棄。 例如,當調用某個函數時, 它會返回一個值和一個錯誤,但只有錯誤很重要, 那麼可使用空白標識符來丟棄無關的值。 "}]}]}]},{"type":"codeblock","attrs":{"lang":"golang"},"content":[{"type":"text","text":"if _, err := os.Stat(path); os.IsNotExist(err) {\n\tfmt.Printf(\"%s does not exist\\n\", path) \n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"要讓編譯器停止關於未使用導入的抱怨,需要空白標識符來引用已導入包中的符號。 同樣, 將未使用的變量 fd 賦予空白標識符也能關閉未使用變量錯誤。 該程序的以下版本可以編譯。 "}]}]}]},{"type":"codeblock","attrs":{"lang":"golang"},"content":[{"type":"text","text":"package main \n\nimport (\n\t\"fmt\" \n\t\"io\" \n\t\"log\" \n\t\"os\" \n) \n\nvar _ = fmt.Printf // For debugging; delete when done. // 用於調試,結束時刪除。 \nvar _ io.Reader // For debugging; delete when done. // 用於調試,結束時刪除。 \n\nfunc main() { \n\tfd, err := os.Open(\"test.go\") \n\tif err != nil { \n\t\tlog.Fatal(err) \n\t} \n\t// TODO: use fd. \n\t_ = fd \n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"像前例中 fmt 或 io 這種未使用的導入總應在最後被使用或移除: 空白賦值會將代碼標識爲工 作正在進行中。但有時導入某個包只是爲了其副作用, 而沒有任何明確的使用。例如,在 net/http/pprof 包的 init 函數中記錄了 HTTP 處理程序的調試信息。它有個可導出的 API, 但 大部分客戶端只需要該處理程序的記錄和通過 Web 葉訪問數據。只爲了其副作用來哦導入該 包, 只需將包重命名爲空白標識符: "}]}]}]},{"type":"codeblock","attrs":{"lang":"golang"},"content":[{"type":"text","text":"import _ \"net/http/pprof\" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這種導入格式能明確表示該包是爲其副作用而導入的,因爲沒有其它使用該包的可能: 在此 文件中,它沒有名字。(若它有名字而我們沒有使用,編譯器就會拒絕該程序。)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"內嵌"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"若我們需要直接引用內嵌字段,可以忽略包限定名,直接將該字段的類型名作爲字段名, 就 像我們在 ReaderWriter 結構體的 Read 方法中做的那樣。 若我們需要訪問 Job 類型的變量 job 的 "},{"type":"codeinline","content":[{"type":"text","text":"*log.Logger"}]},{"type":"text","text":" , 可以直接寫作 job.Logger。若我們想精煉 Logger 的方法時, 這會非 常有用。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"併發"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"通過通信共享內存"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在併發編程中,爲實現對共享變量的正確訪問需要精確的控制,這在多數環境下都很困難。 Go語言另闢蹊徑,它將共享的值通過信道傳遞,實際上,多個獨立執行的線程從不會主動共享。在任意給定的時間點,只有一個Go程能夠訪問該值。數據競爭從設計上就被杜絕了。 爲了提倡這種思考方式,我們將它簡化爲一句口號:"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"不要通過共享內存來通信,而應通過通信來共享內存。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Goroutines Go程"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們稱之爲 Go程是因爲現有的術語—線程、協程、進程等等—無法準確傳達它的含義。Go程具有簡單的模型:它是與其它Go程併發運行在同一地址空間的函數。它是輕量級的,所有小號幾乎就只有棧空間的分配。而且棧最開始是非常小的,所以它們很廉價,僅在需要時纔會隨着堆空間的分配(和釋放)而變化。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Go程在多線程操作系統上可實現多路複用,因此若一個線程阻塞,比如說等待 I/O,那麼其它的線程就會運行。Go程的設計隱藏了線程創建和管理的諸多複雜性。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在函數或方法前添加 go 關鍵字能夠在新的 Go程中調用它。當調用完成後,該 Go程也會安靜地退出。(效果有點像 Unix Shell 中的 & 符號,它能讓命令在後臺運行。)"}]}]}]},{"type":"codeblock","attrs":{"lang":"golang"},"content":[{"type":"text","text":"go list.Sort() // 併發運行 list.Sort,無需等它結束。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Channels 信道"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"信道與映射一樣,也需要通過 make 來分配內存。其結果值充當了對底層數據結構的引用。 若提供了一個可選的整數形參,它就會爲該信道設置緩衝區大小。默認值是零,表示不帶緩 衝的或同步的信道。"}]}]}]},{"type":"codeblock","attrs":{"lang":"golang"},"content":[{"type":"text","text":"ci := make(chan int) // 整數類型的無緩衝信道 \ncj := make(chan int, 0) // 整數類型的無緩衝信道 \ncs := make(chan *os.File, 100) // 指向文件指針的帶緩衝信道"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"無緩衝信道在通信時會同步交換數據,它能確保(兩個 Go 程的)計算處於確定狀態。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"信道有很多慣用法,我們從這裏開始瞭解。在上一節中,我們在後臺啓動了排序操作。 信道 使得啓動的 Go 程等待排序完成。"}]}]}]},{"type":"codeblock","attrs":{"lang":"golang"},"content":[{"type":"text","text":"c := make(chan int) // 分配一個信道 \n// 在 Go 程中啓動排序。當它完成後,在信道上發送信號。 \ngo func() { \n\tlist.Sort() \n\tc 接受之前,參見 Go 內存模 型),因此信號必須在信道的接收端獲取,而非發送端。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然而,它卻有個設計問題:儘管只有 MaxOutstanding 個 Go 程能同時運行,但 Serve 還是爲 每個進入的請求都創建了新的 Go 程。其結果就是,若請求來得很快, 該程序就會無限地消 耗資源。爲了彌補這種不足,我們可以通過修改 Serve 來限制創建 Go 程,這是個明顯的解 決方案,但要當心我們修復後出現的 Bug。"}]}]}]},{"type":"codeblock","attrs":{"lang":"golang"},"content":[{"type":"text","text":"func Serve(queue chan *Request) { \n\tfor req := range queue { \n\t\tsem \n \nQR Link Generator \n \n \n{{if .}} \n \n
\n{{.}} \n
\n
\n{{end}} \n
\n\n \n
\n \n
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章