Fixes #198
pull/1/head
chai2010 2016-01-18 11:22:04 +08:00
parent 884ada9cd0
commit 9666211cd7
71 changed files with 107 additions and 105 deletions

View File

@ -56,15 +56,15 @@ Go的標準庫已經提供了100多個package用來完成一門程序語言
package main是一個比較特殊的package。這個package里會定義一個獨立的程序這個程序是可以運行的而不是像其它package一樣對應一個library。在main這個package里main函數也是一個特殊的函數這是我們整個程序的入口譯註其實C繫語言差不多都是這樣。main函數所做的事情就是我們程序做的事情。當然了main函數一般是通過是調用其它packge里的函數來完成自己的工作比如fmt.Println。
我們必告訴編譯器如何要正確地執行這個源文件需要用到哪些package這就是import在這個文件里扮演的角色。上述的hello world例子隻用到了一個其它的package就是fmt。一般情況下需要import的package可能不隻一個。
我們必告訴編譯器如何要正確地執行這個源文件需要用到哪些package這就是import在這個文件里扮演的角色。上述的hello world例子隻用到了一個其它的package就是fmt。一般情況下需要import的package可能不隻一個。
這也正是因爲go語言必引入所有要用到的package的原則假如你沒有在代碼里import需要用到的package程序將無法編譯通過同時當你import了沒有用到的package也會無法編譯通過譯註Go語言編譯過程沒有警告信息爭議特性之一
這也正是因爲go語言必引入所有要用到的package的原則假如你沒有在代碼里import需要用到的package程序將無法編譯通過同時當你import了沒有用到的package也會無法編譯通過譯註Go語言編譯過程沒有警告信息爭議特性之一
import聲明必跟在文件的package聲明之後。在import語句之後則是各種方法、變量、常量、類型的聲明語句(分别用關鍵字func, var, const, type來進行定義)。這些內容的聲明順序併沒有什麽規定,可以隨便調整順序(譯註:最好還是定一下規范)。我們例子里的程序比較簡單隻包含了一個函數。併且在該函數里也隻調用了一個其它函數。爲了節省空間有些時候的例子我們會省略package和import聲明但是讀者需要註意這些聲明是一定要包含在源文件里的。
import聲明必跟在文件的package聲明之後。在import語句之後則是各種方法、變量、常量、類型的聲明語句(分别用關鍵字func, var, const, type來進行定義)。這些內容的聲明順序併沒有什麽規定,可以隨便調整順序(譯註:最好還是定一下規范)。我們例子里的程序比較簡單隻包含了一個函數。併且在該函數里也隻調用了一個其它函數。爲了節省空間有些時候的例子我們會省略package和import聲明但是讀者需要註意這些聲明是一定要包含在源文件里的。
一個函數的聲明包含func這個關鍵字、函數名、參數列表、返迴結果列表我們例子里的main函數參數列表和返迴值都是空的以及包含在大括號里的函數體。關於函數的更詳細描述在第五章。
Go語言是一門不需要分號作爲語句或者聲明結束的語言除非要在一行中將多個語句、聲明隔開。然而在編譯時編譯器會主動在一些特定的符號譯註比如行末是一個標識符、一個整數、浮點數、虛數、字符或字符串文字、關鍵字break、continue、fallthrough或return中的一個、運算符和分隔符++、--、)、]或}中的一個) 後添加分號所以在哪里加分號合適是取決於Go語言代碼的。例如在Go語言中的函數聲明和 { 大括號必在同一行而在x + y這樣的表達式中在+號後換行可以,但是在+號前換行則會有問題(譯註:以+結尾的話不會被插入分號分隔符但是以x結尾的話則會被分號分隔符從而導致編譯錯誤
Go語言是一門不需要分號作爲語句或者聲明結束的語言除非要在一行中將多個語句、聲明隔開。然而在編譯時編譯器會主動在一些特定的符號譯註比如行末是一個標識符、一個整數、浮點數、虛數、字符或字符串文字、關鍵字break、continue、fallthrough或return中的一個、運算符和分隔符++、--、)、]或}中的一個) 後添加分號所以在哪里加分號合適是取決於Go語言代碼的。例如在Go語言中的函數聲明和 { 大括號必在同一行而在x + y這樣的表達式中在+號後換行可以,但是在+號前換行則會有問題(譯註:以+結尾的話不會被插入分號分隔符但是以x結尾的話則會被分號分隔符從而導致編譯錯誤
Go語言在代碼格式上采取了很強硬的態度。gofmt工具會將你的代碼格式化爲標準格式譯註這個格式化工具沒有任何可以調整代碼格式的參數Go語言就是這麽任性併且go工具中的fmt子命令會自動對特定package下的所有.go源文件應用gofmt工具格式化。如果不指定package則默認對當前目録下的源文件進行格式化。本書中的所有代碼已經是執行過gofmt後的標準格式代碼。你應該在自己的代碼上也執行這種格式化。規定一種標準的代碼格式可以規避掉無盡的無意義的撕逼譯註也導致了Go語言的TIOBE排名較低因爲缺少撕逼的話題。當然了這可以避免由於代碼格式導致的邏輯上的歧義。

View File

@ -72,7 +72,7 @@ for initialization; condition; post {
這里需要註意for循環的兩邊是不需要像其它語言一樣寫括號的。併且左大括號需要和for語句在同一行。
initialization部分是可選的如果你寫了這部分的話在for循環之前這部分的邏輯會被執行。需要註意的是這部分必是一個簡單的語句也就是説是一個簡短的變量聲明一個賦值語句或是一個函數調用。condition部分必是一個結果爲boolean值的表達式在每次循環之前語言都會檢査當前是否滿足這個條件如果不滿足的話便會結束循環post部分的語句則是在每次循環迭代結束之後被執行之後conditon部分會在下一次執行前再被執行依此往複。當condition條件里的判斷結果變爲false之後循環卽結束。
initialization部分是可選的如果你寫了這部分的話在for循環之前這部分的邏輯會被執行。需要註意的是這部分必是一個簡單的語句也就是説是一個簡短的變量聲明一個賦值語句或是一個函數調用。condition部分必是一個結果爲boolean值的表達式在每次循環之前語言都會檢査當前是否滿足這個條件如果不滿足的話便會結束循環post部分的語句則是在每次循環迭代結束之後被執行之後conditon部分會在下一次執行前再被執行依此往複。當condition條件里的判斷結果變爲false之後循環卽結束。
上面提到是for循環里的三個部分都是可以被省略的如果你把initialization和post部分都省略的話那麽連中間隔離他們的分號也是可以被省略的比如下面這種for循環就和傳統的while循環是一樣的
@ -115,7 +115,7 @@ func main() {
}
```
每一次循環迭代range都會返迴一對結果當前迭代的下標以及在該下標處的元素的值。在這個例子里我們不需要這個下標但是因爲range的處理要求我們必要同時處理下標和值。我們可以在這里聲明一個接收index的臨時變量來解決這個問題但是Go語言又不允許隻聲明而在後續代碼里不使用這個變量如果你這樣做了編譯器會返迴一個編譯錯誤。
每一次循環迭代range都會返迴一對結果當前迭代的下標以及在該下標處的元素的值。在這個例子里我們不需要這個下標但是因爲range的處理要求我們必要同時處理下標和值。我們可以在這里聲明一個接收index的臨時變量來解決這個問題但是Go語言又不允許隻聲明而在後續代碼里不使用這個變量如果你這樣做了編譯器會返迴一個編譯錯誤。
在Go語言中應對這種情況的解決方法是用空白標識符就是上面那個下劃線_。空白標識符可以在任何你接收自己不需要處理的值時使用。在這里我們用它來忽略掉range返迴的那個沒用的下標值。大多數的Go程序員都會像上面這樣來寫類似的os.Args遍歷由於遍歷os.Args的下標索引是隱式自動生成的可以避免因顯式更新索引導致的錯誤。

View File

@ -167,7 +167,7 @@ func main() {
}
```
ReadFile函數返迴一個byte的slice這個slice必被轉換爲string之後才能夠用string.Split方法來進行處理。我們在3.5.4節中會更詳細地講解string和byte slice字節數組
ReadFile函數返迴一個byte的slice這個slice必被轉換爲string之後才能夠用string.Split方法來進行處理。我們在3.5.4節中會更詳細地講解string和byte slice字節數組
在更底層一些的地方bufio.Scannerioutil.ReadFile和ioutil.WriteFile使用的是*os.File的Read和Write方法不過一般程序員併不需要去直接了解到其底層實現細節在bufio和io/ioutil包中提供的方法已經足夠好用。

View File

@ -69,7 +69,7 @@ bla kIndex)
當我們import了一個包路徑包含有多個單詞的package時比如image/colorimage和color兩個單詞通常我們隻需要用最後那個單詞表示這個包就可以。所以當我們寫color.White時這個變量指向的是image/color包里的變量同理gif.GIF是屬於image/gif包里的變量。
這個程序里的常量聲明給出了一繫列的常量值,常量是指在程序編譯後運行時始終都不會變化的值,比如圈數、幀數、延遲值。常量聲明和變量聲明一般都會出現在包級别,所以這些常量在整個包中都是可以共享的,或者你也可以把常量聲明定義在函數體內部,那麽這種常量就隻能在函數體內用。目前常量聲明的值必是一個數字值、字符串或者一個固定的boolean值。
這個程序里的常量聲明給出了一繫列的常量值,常量是指在程序編譯後運行時始終都不會變化的值,比如圈數、幀數、延遲值。常量聲明和變量聲明一般都會出現在包級别,所以這些常量在整個包中都是可以共享的,或者你也可以把常量聲明定義在函數體內部,那麽這種常量就隻能在函數體內用。目前常量聲明的值必是一個數字值、字符串或者一個固定的boolean值。
[]color.Color{...}和gif.GIF{...}這兩個表達式就是我們説的複合聲明4.2和4.4.1節有説明。這是實例化Go語言里的複合類型的一種寫法。這里的前者生成的是一個slice切片後者生成的是一個struct結構體。

View File

@ -85,7 +85,7 @@ func counter(w http.ResponseWriter, r *http.Request) {
}
```
這個服務器有兩個請求處理函數請求的url會決定具體調用哪一個對/count這個url的請求會調用到count這個函數其它所有的url都會調用默認的處理函數。如果你的請求pattern是以/結尾那麽所有以該url爲前綴的url都會被這條規則匹配。在這些代碼的背後服務器每一次接收請求處理時都會另起一個goroutine這樣服務器就可以同一時間處理多數請求。然而在併發情況下假如眞的有兩個請求同一時刻去更新count那麽這個值可能併不會被正確地增加這個程序可能會被引發一個嚴重的bug競態條件參見9.1)。爲了避免這個問題,我們必保證每次脩改變量的最多隻能有一個goroutine這也就是代碼里的mu.Lock()和mu.Unlock()調用將脩改count的所有行爲包在中間的目的。第九章中我們會進一步講解共享變量。
這個服務器有兩個請求處理函數請求的url會決定具體調用哪一個對/count這個url的請求會調用到count這個函數其它所有的url都會調用默認的處理函數。如果你的請求pattern是以/結尾那麽所有以該url爲前綴的url都會被這條規則匹配。在這些代碼的背後服務器每一次接收請求處理時都會另起一個goroutine這樣服務器就可以同一時間處理多數請求。然而在併發情況下假如眞的有兩個請求同一時刻去更新count那麽這個值可能併不會被正確地增加這個程序可能會被引發一個嚴重的bug競態條件參見9.1)。爲了避免這個問題,我們必保證每次脩改變量的最多隻能有一個goroutine這也就是代碼里的mu.Lock()和mu.Unlock()調用將脩改count的所有行爲包在中間的目的。第九章中我們會進一步講解共享變量。
下面是一個更爲豐富的例子handler函數會把請求的http頭和請求的form數據都打印出來這樣可以讓檢査和調試這個服務更爲方便

View File

@ -6,4 +6,4 @@
每個包還通過控製包內名字的可見性和是否導出來實現封裝特性。通過限製包成員的可見性併隱藏包API的具體實現將允許包的維護者在不影響外部包用戶的前提下調整包的內部實現。通過限製包內變量的可見性還可以強製用戶通過某些特定函數來訪問和更新內部變量這樣可以保證內部變量的一致性和併發時的互斥約束。
當我們脩改了一個源文件,我們必重新編譯該源文件對應的包和所有依賴該包的其他包。卽使是從頭構建Go語言編譯器的編譯速度也明顯快於其它編譯語言。Go語言的閃電般的編譯速度主要得益於三個語言特性。第一點所有導入的包必在每個文件的開頭顯式聲明,這樣的話編譯器就沒有必要讀取和分析整個源文件來判斷包的依賴關繫。第二點,禁止包的環狀依賴,因爲沒有循環依賴,包的依賴關繫形成一個有向無環圖,每個包可以被獨立編譯,而且很可能是被併發編譯。第三點,編譯後包的目標文件不僅僅記録包本身的導出信息,目標文件同時還記録了包的依賴關繫。因此,在編譯一個包的時候,編譯器隻需要讀取每個直接導入包的目標文件,而不需要遍歷所有依賴的的文件(譯註:很多都是重複的間接依賴)。
當我們脩改了一個源文件,我們必重新編譯該源文件對應的包和所有依賴該包的其他包。卽使是從頭構建Go語言編譯器的編譯速度也明顯快於其它編譯語言。Go語言的閃電般的編譯速度主要得益於三個語言特性。第一點所有導入的包必在每個文件的開頭顯式聲明,這樣的話編譯器就沒有必要讀取和分析整個源文件來判斷包的依賴關繫。第二點,禁止包的環狀依賴,因爲沒有循環依賴,包的依賴關繫形成一個有向無環圖,每個包可以被獨立編譯,而且很可能是被併發編譯。第三點,編譯後包的目標文件不僅僅記録包本身的導出信息,目標文件同時還記録了包的依賴關繫。因此,在編譯一個包的時候,編譯器隻需要讀取每個直接導入包的目標文件,而不需要遍歷所有依賴的的文件(譯註:很多都是重複的間接依賴)。

View File

@ -1,6 +1,6 @@
## 10.3. 包聲明
在每個Go語音源文件的開頭都必有包聲明語句。包聲明語句的主要目的是確定當前包被其它包導入時默認的標識符(也稱爲包名)。
在每個Go語音源文件的開頭都必有包聲明語句。包聲明語句的主要目的是確定當前包被其它包導入時默認的標識符(也稱爲包名)。
例如math/rand包的每個源文件的開頭都包含`package rand`包聲明語句所以當你導入這個包你就可以用rand.Int、rand.Float64類似的方式訪問包的成員。
@ -19,8 +19,8 @@ func main() {
通常來説默認的包名就是包導入路徑名的最後一段因此卽使兩個包的導入路徑不同它們依然可能有一個相同的包名。例如math/rand包和crypto/rand包的包名都是rand。稍後我們將看到如何同時導入兩個有相同包名的包。
關於默認包名一般采用導入路徑名的最後一段的約定也有三種例外情況。第一個例外包對應一個可執行程序也就是main包這時候main包本身的導入路徑是無關緊要的。名字爲main的包是給go build§10.7.3)構建命令一個信息,這個包編譯完之後必調用連接器生成一個可執行程序。
關於默認包名一般采用導入路徑名的最後一段的約定也有三種例外情況。第一個例外包對應一個可執行程序也就是main包這時候main包本身的導入路徑是無關緊要的。名字爲main的包是給go build§10.7.3)構建命令一個信息,這個包編譯完之後必調用連接器生成一個可執行程序。
第二個例外包所在的目録中可能有一些文件名是以_test.go爲後綴的Go源文件譯註前面必有其它的字符,因爲以`_`前綴的源文件是被忽略的併且這些源文件聲明的包名也是以_test爲後綴名的。這種目録可以包含兩種包一種普通包加一種則是測試的外部擴展包。所有以_test爲後綴包名的測試外部擴展包都由go test命令獨立編譯普通包和測試的外部擴展包是相互獨立的。測試的外部擴展包一般用來避免測試代碼中的循環導入依賴具體細節我們將在11.2.4節中介紹。
第二個例外包所在的目録中可能有一些文件名是以_test.go爲後綴的Go源文件譯註前面必有其它的字符,因爲以`_`前綴的源文件是被忽略的併且這些源文件聲明的包名也是以_test爲後綴名的。這種目録可以包含兩種包一種普通包加一種則是測試的外部擴展包。所有以_test爲後綴包名的測試外部擴展包都由go test命令獨立編譯普通包和測試的外部擴展包是相互獨立的。測試的外部擴展包一般用來避免測試代碼中的循環導入依賴具體細節我們將在11.2.4節中介紹。
第三個例外,一些依賴版本號的管理工具會在導入路徑後追加版本號信息,例如"gopkg.in/yaml.v2"。這種情況下包的名字併不包含版本號後綴而是yaml。

View File

@ -25,7 +25,7 @@ import (
)
```
如果我們想同時導入兩個有着名字相同的包例如math/rand包和crypto/rand包那麽導入聲明必至少爲一個同名包指定一個新的包名以避免衝突。這叫做導入包的重命名。
如果我們想同時導入兩個有着名字相同的包例如math/rand包和crypto/rand包那麽導入聲明必至少爲一個同名包指定一個新的包名以避免衝突。這叫做導入包的重命名。
```Go
import (

View File

@ -4,7 +4,7 @@
因爲每個目録隻包含一個包因此每個對應可執行程序或者叫Unix術語中的命令的包會要求放到一個獨立的目録中。這些目録有時候會放在名叫cmd目録的子目録下面例如用於提供Go文檔服務的golang.org/x/tools/cmd/godoc命令就是放在cmd子目録§10.7.4)。
每個包可以由它們的導入路徑指定,就像前面看到的那樣,或者用一個相對目録的路徑知指定,相對路徑必以`.`或`..`開頭。如果沒有指定參數,那麽默認指定爲當前目録對應的包。 下面的命令用於構建同一個包, 雖然它們的寫法各不相同:
每個包可以由它們的導入路徑指定,就像前面看到的那樣,或者用一個相對目録的路徑知指定,相對路徑必以`.`或`..`開頭。如果沒有指定參數,那麽默認指定爲當前目録對應的包。 下面的命令用於構建同一個包, 雖然它們的寫法各不相同:
```
$ cd $GOPATH/src/gopl.io/ch1/helloworld

View File

@ -110,7 +110,7 @@ func TestCheckQuotaNotifiesUser(t *testing.T) {
}
```
這里有一個問題: 當測試函數返迴後, CheckQuota 將不能正常工作, 因爲 notifyUsers 依然使用的是測試函數的僞發送郵件函數. (當更新全局對象的時候總會有這種風險.) 我們必脩改測試代碼恢複 notifyUsers 原先的狀態以便後續其他的測試沒有影響, 要確保所有的執行路徑後都能恢複, 包括測試失敗或 panic 情形. 在這種情況下, 我們建議使用 defer 處理恢複的代碼.
這里有一個問題: 當測試函數返迴後, CheckQuota 將不能正常工作, 因爲 notifyUsers 依然使用的是測試函數的僞發送郵件函數. (當更新全局對象的時候總會有這種風險.) 我們必脩改測試代碼恢複 notifyUsers 原先的狀態以便後續其他的測試沒有影響, 要確保所有的執行路徑後都能恢複, 包括測試失敗或 panic 情形. 在這種情況下, 我們建議使用 defer 處理恢複的代碼.
```Go
func TestCheckQuotaNotifiesUser(t *testing.T) {

View File

@ -38,7 +38,7 @@ $ go list -f={{.TestGoFiles}} fmt
包的測試代碼通常都在這些文件中, 不過 fmt 包併非如此; 稍後我們再解釋 export_test.go 文件的作用.
XTestGoFiles 表示的是屬於測試擴展包的測試代碼, 也就是 fmt_test 包, 因此它們必先導入 fmt 包. 同樣, 這些文件也隻是在測試時被構建運行:
XTestGoFiles 表示的是屬於測試擴展包的測試代碼, 也就是 fmt_test 包, 因此它們必先導入 fmt 包. 同樣, 這些文件也隻是在測試時被構建運行:
{% raw %}

View File

@ -1,7 +1,7 @@
## 11.2. 測試函數
每個測試函數必導入 testing 包. 測試函數有如下的籤名:
每個測試函數必導入 testing 包. 測試函數有如下的籤名:
```Go
func TestName(t *testing.T) {
@ -9,7 +9,7 @@ func TestName(t *testing.T) {
}
```
測試函數的名字必鬚以Test開頭, 可選的後綴名必鬚以大寫字母開頭:
測試函數的名字必須以Test開頭, 可選的後綴名必須以大寫字母開頭:
```Go
func TestSin(t *testing.T) { /* ... */ }
@ -209,7 +209,7 @@ ok gopl.io/ch11/word2 0.015s
失敗的測試的輸出併不包括調用 t.Errorf 時刻的堆棧調用信息. 不像其他語言或測試框架的 assert 斷言, t.Errorf 調用也沒有引起 panic 或停止測試的執行. 卽使表格中前面的數據導致了測試的失敗, 表格後面的測試數據依然會運行測試, 因此在一個測試中我們可能了解多個失敗的信息.
如果我們眞的需要停止測試, 或許是因爲初始化失敗或可能是早先的錯誤導致了後續錯誤等原因, 我們可以使用 t.Fatal 或 t.Fatalf 停止測試. 它們必在和測試函數同一個 goroutine 內調用.
如果我們眞的需要停止測試, 或許是因爲初始化失敗或可能是早先的錯誤導致了後續錯誤等原因, 我們可以使用 t.Fatal 或 t.Fatalf 停止測試. 它們必在和測試函數同一個 goroutine 內調用.
測試失敗的信息一般的形式是 "f(x) = y, want z", f(x) 解釋了失敗的操作和對應的輸出, y 是實際的運行結果, z 是期望的正確的結果. 就像前面檢査迴文字符串的例子, 實際的函數用於 f(x) 部分. 如果顯示 x 是表格驅動型測試中比較重要的部分, 因爲同一個斷言可能對應不同的表格項執行多次. 要避免無用和冗餘的信息. 在測試類似 IsPalindrome 返迴布爾類型的函數時, 可以忽略併沒有額外信息的 z 部分. 如果 x, y 或 z 是 y 的長度, 輸出一個相關部分的簡明總結卽可. 測試的作者應該要努力幫助程序員診斷失敗的測試.

View File

@ -12,7 +12,7 @@ func ExampleIsPalindrome() {
}
```
示例函數有三個用處. 最主要的一個是用於文檔: 一個包的例子可以更簡潔直觀的方式來演示函數的用法, 會文字描述會更直接易懂, 特别是作爲一個提醒或快速參考時. 一個例子函數也可以方便展示屬於同一個接口的幾種類型或函數直接的關繫, 所有的文檔都必關聯到一個地方, 就像一個類型或函數聲明都統一到包一樣. 同時, 示例函數和註釋併不一樣, 示例函數是完整眞是的Go代碼, 需要介紹編譯器的編譯時檢査, 這樣可以保證示例代碼不會腐爛成不能使用的舊代碼.
示例函數有三個用處. 最主要的一個是用於文檔: 一個包的例子可以更簡潔直觀的方式來演示函數的用法, 會文字描述會更直接易懂, 特别是作爲一個提醒或快速參考時. 一個例子函數也可以方便展示屬於同一個接口的幾種類型或函數直接的關繫, 所有的文檔都必關聯到一個地方, 就像一個類型或函數聲明都統一到包一樣. 同時, 示例函數和註釋併不一樣, 示例函數是完整眞是的Go代碼, 需要介紹編譯器的編譯時檢査, 這樣可以保證示例代碼不會腐爛成不能使用的舊代碼.
根據示例函數的後綴名部分, godoc 的web文檔會將一個示例函數關聯到某個具體函數或包本身, 因此 ExampleIsPalindrome 示例函數將是 IsPalindrome 函數文檔的一部分, Example 示例函數將是包文檔的一部分.

View File

@ -10,7 +10,7 @@ Maurice Wilkes, 第一個存儲程序計算機 EDSAC 的設計者, 1949年在他
Go語言的測試技術是相對低級的. 它依賴一個 'go test' 測試命令, 和一組按照約定方式編寫的測試函數, 測試命令可以運行測試函數. 編寫相對輕量級的純測試代碼是有效的, 而且它很容易延伸到基準測試和示例文檔.
在實踐中, 編寫測試代碼和編寫程序本身併沒有多大區别. 我們編寫的每一個函數也是針對每個具體的任務. 我們必小心處理邊界條件, 思考合適的數據結構, 推斷合適的輸入應該産生什麽樣的結果輸出. 編程測試代碼和編寫普通的Go代碼過程是類似的; 它併不需要學習新的符號, 規則和工具.
在實踐中, 編寫測試代碼和編寫程序本身併沒有多大區别. 我們編寫的每一個函數也是針對每個具體的任務. 我們必小心處理邊界條件, 思考合適的數據結構, 推斷合適的輸入應該産生什麽樣的結果輸出. 編程測試代碼和編寫普通的Go代碼過程是類似的; 它併不需要學習新的符號, 規則和工具.

View File

@ -79,7 +79,7 @@ func display(path string, v reflect.Value) {
雖然reflect.Value類型帶有很多方法但是隻有少數的方法對任意值都是可以安全調用的。例如Index方法隻能對Slice、數組或字符串類型的值調用其它類型如果調用將導致panic異常。
**結構體:** NumField方法報告結構體中成員的數量Field(i)以reflect.Value類型返迴第i個成員的值。成員列表包含了匿名成員在內的全部成員。通過在path添加“.f”來表示成員路徑我們必獲得結構體對應的reflect.Type類型信息包含結構體類型和第i個成員的名字。
**結構體:** NumField方法報告結構體中成員的數量Field(i)以reflect.Value類型返迴第i個成員的值。成員列表包含了匿名成員在內的全部成員。通過在path添加“.f”來表示成員路徑我們必獲得結構體對應的reflect.Type類型信息包含結構體類型和第i個成員的名字。
**Maps:** MapKeys方法返迴一個reflect.Value類型的slice每一個都對應map的可以。和往常一樣遍歷map時順序是隨機的。MapIndex(key)返迴map中key對應的value。我們向path添加“[key]”來表示訪問路徑。我們這里有一個未完成的工作。其實map的key的類型併不局限於formatAtom能完美處理的類型數組、結構體和接口都可以作爲map的key。針對這種類型完善key的顯示信息是練習12.1的任務。)

View File

@ -10,7 +10,7 @@ err := json.Unmarshal(data, &movie)
Unmarshal函數使用了反射機製類脩改movie變量的每個成員根據輸入的內容爲Movie成員創建對應的map、結構體和slice。
現在讓我們爲S表達式編碼實現一個簡易的Unmarshal類似於前面的json.Unmarshal標準庫函數對應我們之前實現的sexpr.Marshal函數的逆操作。我們必提醒一下一個健壯的和通用的實現通常需要比例子更多的代碼爲了便於演示我們采用了精簡的實現。我們隻支持S表達式有限的子集同時處理錯誤的方式也比較粗暴代碼的目的是爲了演示反射的用法而不是構造一個實用的S表達式的解碼器。
現在讓我們爲S表達式編碼實現一個簡易的Unmarshal類似於前面的json.Unmarshal標準庫函數對應我們之前實現的sexpr.Marshal函數的逆操作。我們必提醒一下一個健壯的和通用的實現通常需要比例子更多的代碼爲了便於演示我們采用了精簡的實現。我們隻支持S表達式有限的子集同時處理錯誤的方式也比較粗暴代碼的目的是爲了演示反射的用法而不是構造一個實用的S表達式的解碼器。
詞法分析器lexer使用了標準庫中的text/scanner包將輸入流的字節數據解析爲一個個類似註釋、標識符、字符串面值和數字面值之類的標記。輸入掃描器scanner的Scan方法將提前掃描和返迴下一個記號對於rune類型。大多數記號比如“(”對應一個單一rune可表示的Unicode字符但是text/scanner也可以用小的負數表示記號標識符、字符串等由多個字符組成的記號。調用Scan方法將返迴這些記號的類型接着調用TokenText方法將返迴記號對應的文本內容。
@ -74,7 +74,7 @@ func read(lex *lexer, v reflect.Value) {
最有趣的部分是遞歸。最簡單的是對數組類型的處理。直到遇到“)”結束標記我們使用Index函數來獲取數組每個元素的地址然後遞歸調用read函數處理。和其它錯誤類似如果輸入數據導致解碼器的引用超出了數組的范圍解碼器將拋出panic異常。slice也采用類似方法解析不同的是我們將爲每個元素創建新的變量然後將元素添加到slice的末尾。
在循環處理結構體和map每個元素時必解碼一個(key value)格式的對應子列表。對於結構體key部分對於成員的名字。和數組類似我們使用FieldByName找到結構體對應成員的變量然後遞歸調用read函數處理。對於mapkey可能是任意類型對元素的處理方式和slice類似我們創建一個新的變量然後遞歸填充它最後將新解析到的key/value對添加到map。
在循環處理結構體和map每個元素時必解碼一個(key value)格式的對應子列表。對於結構體key部分對於成員的名字。和數組類似我們使用FieldByName找到結構體對應成員的變量然後遞歸調用read函數處理。對於mapkey可能是任意類型對元素的處理方式和slice類似我們創建一個新的變量然後遞歸填充它最後將新解析到的key/value對添加到map。
```Go
func readList(lex *lexer, v reflect.Value) {

View File

@ -40,7 +40,7 @@ struct{ bool; int16; float64 } // 2 words 3words
`unsafe.Alignof` 函數返迴對應參數的類型需要對齊的倍數. 和 Sizeof 類似, Alignof 也是返迴一個常量表達式, 對應一個常量. 通常情況下布爾和數字類型需要對齊到它們本身的大小(最多8個字節), 其它的類型對齊到機器字大小.
`unsafe.Offsetof` 函數的參數必是一個字段 `x.f`, 然後返迴 `f` 字段相對於 `x` 起始地址的偏移量, 包括可能的空洞.
`unsafe.Offsetof` 函數的參數必是一個字段 `x.f`, 然後返迴 `f` 字段相對於 `x` 起始地址的偏移量, 包括可能的空洞.
圖 13.1 顯示了一個結構體變量 x 以及其在32位和64位機器上的典型的內存. 灰色區域是空洞.

View File

@ -43,7 +43,7 @@ pb := (*int16)(unsafe.Pointer(tmp))
*pb = 42
```
産生錯誤的原因很微妙。有時候垃圾迴收器會移動一些變量以降低內存碎片等問題。這類垃圾迴收器被稱爲移動GC。當一個變量被移動所有的保存改變量舊地址的指針必同時被更新爲變量移動後的新地址。從垃圾收集器的視角來看一個unsafe.Pointer是一個指向變量的指針因此當變量被移動是對應的指針也必被更新但是uintptr類型的臨時變量隻是一個普通的數字所以其值不應該被改變。上面錯誤的代碼因爲引入一個非指針的臨時變量tmp導致垃圾收集器無法正確識别這個是一個指向變量x的指針。當第二個語句執行時變量x可能已經被轉移這時候臨時變量tmp也就不再是現在的`&x.b`地址。第三個向之前無效地址空間的賦值語句將徹底摧譭整個程序!
産生錯誤的原因很微妙。有時候垃圾迴收器會移動一些變量以降低內存碎片等問題。這類垃圾迴收器被稱爲移動GC。當一個變量被移動所有的保存改變量舊地址的指針必同時被更新爲變量移動後的新地址。從垃圾收集器的視角來看一個unsafe.Pointer是一個指向變量的指針因此當變量被移動是對應的指針也必被更新但是uintptr類型的臨時變量隻是一個普通的數字所以其值不應該被改變。上面錯誤的代碼因爲引入一個非指針的臨時變量tmp導致垃圾收集器無法正確識别這個是一個指向變量x的指針。當第二個語句執行時變量x可能已經被轉移這時候臨時變量tmp也就不再是現在的`&x.b`地址。第三個向之前無效地址空間的賦值語句將徹底摧譭整個程序!
還有很多類似原因導致的錯誤。例如這條語句:

View File

@ -78,7 +78,7 @@ type comparison struct {
}
```
爲了確保算法對於有環的數據結構也能正常退出,我們必記録每次已經比較的變量從而避免進入第二次的比較。Equal函數分配了一組用於比較的結構體包含每對比較對象的地址unsafe.Pointer形式保存和類型。我們要記録類型的原因是有些不同的變量可能對應相同的地址。例如如果x和y都是數組類型那麽x和x[0]將對應相同的地址y和y[0]也是對應相同的地址這可以用於區分x與y之間的比較或x[0]與y[0]之間的比較是否進行過了。
爲了確保算法對於有環的數據結構也能正常退出,我們必記録每次已經比較的變量從而避免進入第二次的比較。Equal函數分配了一組用於比較的結構體包含每對比較對象的地址unsafe.Pointer形式保存和類型。我們要記録類型的原因是有些不同的變量可能對應相同的地址。例如如果x和y都是數組類型那麽x和x[0]將對應相同的地址y和y[0]也是對應相同的地址這可以用於區分x與y之間的比較或x[0]與y[0]之間的比較是否進行過了。
```Go
// cycle check

View File

@ -1,6 +1,6 @@
## 2.1. 命名
Go語言中的函數名、變量名、常量名、類型名、語句標號和包名等所有的命名都遵循一個簡單的命名規則一個名字必以一個字母Unicode字母或下劃線開頭後面可以跟任意數量的字母、數字或下劃線。大寫字母和小寫字母是不同的heapSort和Heapsort是兩個不同的名字。
Go語言中的函數名、變量名、常量名、類型名、語句標號和包名等所有的命名都遵循一個簡單的命名規則一個名字必以一個字母Unicode字母或下劃線開頭後面可以跟任意數量的字母、數字或下劃線。大寫字母和小寫字母是不同的heapSort和Heapsort是兩個不同的名字。
Go語言中類似if和switch的關鍵字有25個關鍵字不能用於自定義名字隻能在特定語法結構中使用。
@ -29,7 +29,7 @@ continue for import return var
這些內部預先定義的名字併不是關鍵字,你可以再定義中重新使用它們。在一些特殊的場景中重新定義它們也是有意義的,但是也要註意避免過度而引起語義混亂。
如果一個名字是在函數內部定義,那麽它的就隻在函數內部有效。如果是在函數外部定義,那麽將在當前包的所有文件中都可以訪問。名字的開頭字母的大小寫決定了名字在包外的可見性。如果一個名字是大寫字母開頭的(譯註:必是在函數外部定義的包級名字包級函數名本身也是包級名字那麽它將是導出的也就是説可以被外部的包訪問例如fmt包的Printf函數就是導出的可以在fmt包外部訪問。包本身的名字一般總是用小寫字母。
如果一個名字是在函數內部定義,那麽它的就隻在函數內部有效。如果是在函數外部定義,那麽將在當前包的所有文件中都可以訪問。名字的開頭字母的大小寫決定了名字在包外的可見性。如果一個名字是大寫字母開頭的(譯註:必是在函數外部定義的包級名字包級函數名本身也是包級名字那麽它將是導出的也就是説可以被外部的包訪問例如fmt包的Printf函數就是導出的可以在fmt包外部訪問。包本身的名字一般總是用小寫字母。
名字的長度沒有邏輯限製但是Go語言的風格是盡量使用短小的名字對於局部變量尤其是這樣你會經常看到i之類的短名字而不是冗長的theLoopIndex命名。通常來説如果一個名字的作用域比較大生命週期也比較長那麽用長的名字將會更有意義。

View File

@ -2,7 +2,7 @@
聲明語句定義了程序的各種實體對象以及部分或全部的屬性。Go語言主要有四種類型的聲明語句var、const、type和func分别對應變量、常量、類型和函數實體對象的聲明。這一章我們重點討論變量和類型的聲明第三章將討論常量的聲明第五章將討論函數的聲明。
一個Go語言編寫的程序對應一個或多個以.go爲文件後綴名的源文件中。每個源文件以包的聲明語句開始説明該源文件是屬於哪個包。包聲明語句之後是import語句導入依賴的其它包然後是包一級的類型、變量、常量、函數的聲明語句包一級的各種類型的聲明語句的順序無關緊要譯註函數內部的名字則必先聲明之後才能使用)。例如,下面的例子中聲明了一個常量、一個函數和兩個變量:
一個Go語言編寫的程序對應一個或多個以.go爲文件後綴名的源文件中。每個源文件以包的聲明語句開始説明該源文件是屬於哪個包。包聲明語句之後是import語句導入依賴的其它包然後是包一級的類型、變量、常量、函數的聲明語句包一級的各種類型的聲明語句的順序無關緊要譯註函數內部的名字則必先聲明之後才能使用)。例如,下面的例子中聲明了一個常量、一個函數和兩個變量:
```Go
gopl.io/ch2/boiling

View File

@ -53,7 +53,7 @@ in, err := os.Open(infile)
out, err := os.Create(outfile)
```
簡短變量聲明語句中必至少要聲明一個新的變量,下面的代碼將不能編譯通過:
簡短變量聲明語句中必至少要聲明一個新的變量,下面的代碼將不能編譯通過:
```Go
f, err := os.Open(infile)

View File

@ -16,7 +16,7 @@ fmt.Println(x) // "2"
對於聚合類型每個成員——比如結構體的每個字段、或者是數組的每個元素——也都是對應一個變量,因此可以被取地址。
變量有時候被稱爲可尋址的值。卽使變量由表達式臨時生成,那麽表達式也必能接受`&`取地址操作。
變量有時候被稱爲可尋址的值。卽使變量由表達式臨時生成,那麽表達式也必能接受`&`取地址操作。
任何類型的指針的零值都是nil。如果`p != nil`測試爲眞那麽p是指向某個有效變量。指針之間也是可以進行相等測試的隻有當它們指向同一個變量或全部是nil時才相等。
@ -55,7 +55,7 @@ incr(&v) // side effect: v is now 2
fmt.Println(incr(&v)) // "3" (and v is 3)
```
每次我們對一個變量取地址,或者複製指針,我們都是爲原變量創建了新的别名。例如,`*p`就是是 變量v的别名。指針特别有價值的地方在於我們可以不用名字而訪問一個變量但是這是一把雙刃劍要找到一個變量的所有訪問者併不容易我們必知道變量全部的别名譯註這是Go語言的垃圾迴收器所做的工作。不僅僅是指針會創建别名很多其他引用類型也會創建别名例如slice、map和chan甚至結構體、數組和接口都會創建所引用變量的别名。
每次我們對一個變量取地址,或者複製指針,我們都是爲原變量創建了新的别名。例如,`*p`就是是 變量v的别名。指針特别有價值的地方在於我們可以不用名字而訪問一個變量但是這是一把雙刃劍要找到一個變量的所有訪問者併不容易我們必知道變量全部的别名譯註這是Go語言的垃圾迴收器所做的工作。不僅僅是指針會創建别名很多其他引用類型也會創建别名例如slice、map和chan甚至結構體、數組和接口都會創建所引用變量的别名。
指針是實現標準庫中flag包的關鍵技術它使用命令行參數來設置對應變量的值而這些對應命令行標誌參數的變量可能會零散分布在整個程序中。爲了説明這一點在早些的echo版本中就包含了兩個可選的命令行參數`-n`用於忽略行尾的換行符,`-s sep`用於指定分隔字符默認是空格。下面這是第四個版本對應包路徑爲gopl.io/ch2/echo4。
@ -82,9 +82,9 @@ func main() {
}
```
調用flag.Bool函數會創建一個新的對應布爾型標誌參數的變量。它有三個屬性第一個是的命令行標誌參數的名字“n”然後是該標誌參數的默認值這里是false最後是該標誌參數對應的描述信息。如果用戶在命令行輸入了一個無效的標誌參數或者輸入`-h`或`-help`參數那麽將打印所有標誌參數的名字、默認值和描述信息。類似的調用flag.String函數將於創建一個對應字符串類型的標誌參數變量同樣包含命令行標誌參數對應的參數名、默認值、和描述信息。程序中的`sep`和`n`變量分别是指向對應命令行標誌參數變量的指針,因此必用`*sep`和`*n`形式的指針語法間接引用它們。
調用flag.Bool函數會創建一個新的對應布爾型標誌參數的變量。它有三個屬性第一個是的命令行標誌參數的名字“n”然後是該標誌參數的默認值這里是false最後是該標誌參數對應的描述信息。如果用戶在命令行輸入了一個無效的標誌參數或者輸入`-h`或`-help`參數那麽將打印所有標誌參數的名字、默認值和描述信息。類似的調用flag.String函數將於創建一個對應字符串類型的標誌參數變量同樣包含命令行標誌參數對應的參數名、默認值、和描述信息。程序中的`sep`和`n`變量分别是指向對應命令行標誌參數變量的指針,因此必用`*sep`和`*n`形式的指針語法間接引用它們。
當程序運行時,必在使用標誌參數對應的變量之前調用先flag.Parse函數用於更新每個標誌參數對應變量的值之前是默認值。對於非標誌參數的普通命令行參數可以通過調用flag.Args()函數來訪問返迴值對應對應一個字符串類型的slice。如果在flag.Parse函數解析命令行參數時遇到錯誤默認將打印相關的提示信息然後調用os.Exit(2)終止程序。
當程序運行時,必在使用標誌參數對應的變量之前調用先flag.Parse函數用於更新每個標誌參數對應變量的值之前是默認值。對於非標誌參數的普通命令行參數可以通過調用flag.Args()函數來訪問返迴值對應對應一個字符串類型的slice。如果在flag.Parse函數解析命令行參數時遇到錯誤默認將打印相關的提示信息然後調用os.Exit(2)終止程序。
讓我們運行一些echo測試用例

View File

@ -9,7 +9,7 @@ fmt.Println(*p) // "0"
fmt.Println(*p) // "2"
```
用new創建變量和普通變量聲明語句方式創建變量沒有什麽區别除了不需要聲明一個臨時變量的名字外我們還可以在表達式中使用new(T)。換言之new函數類似是一種語法,而不是一個新的基礎概念。
用new創建變量和普通變量聲明語句方式創建變量沒有什麽區别除了不需要聲明一個臨時變量的名字外我們還可以在表達式中使用new(T)。換言之new函數類似是一種語法,而不是一個新的基礎概念。
下面的兩個newInt函數有着相同的行爲

View File

@ -44,7 +44,7 @@ func f() { func g() {
}
```
這里的x變量必在堆上分配因爲它在函數退出後依然可以通過包一級的global變量找到雖然它是在函數內部定義的用Go語言的術語説這個x局部變量從函數f中逃逸了。相反當g函數返迴時變量`*y`將是不可達的,也就是説可以馬上被迴收的。因此,`*y`併沒有從函數g中逃逸編譯器可以選擇在棧上分配`*y`的存儲空間譯註也可以選擇在堆上分配然後由Go語言的GC迴收這個變量的內存空間雖然這里用的是new方式。其實在任何時候你併不需爲了編寫正確的代碼而要考慮變量的逃逸行爲要記住的是逃逸的變量需要額外分配內存同時對性能的優化可能會産生細微的影響。
這里的x變量必在堆上分配因爲它在函數退出後依然可以通過包一級的global變量找到雖然它是在函數內部定義的用Go語言的術語説這個x局部變量從函數f中逃逸了。相反當g函數返迴時變量`*y`將是不可達的,也就是説可以馬上被迴收的。因此,`*y`併沒有從函數g中逃逸編譯器可以選擇在棧上分配`*y`的存儲空間譯註也可以選擇在堆上分配然後由Go語言的GC迴收這個變量的內存空間雖然這里用的是new方式。其實在任何時候你併不需爲了編寫正確的代碼而要考慮變量的逃逸行爲要記住的是逃逸的變量需要額外分配內存同時對性能的優化可能會産生細微的影響。
Go語言的自動垃圾收集器對編寫正確的代碼是一個鉅大的幫助但也併不是説你完全不用考慮內存了。你雖然不需要顯式地分配和釋放內存但是要編寫高效的程序你依然需要了解變量的生命週期。例如如果將指向短生命週期對象的指針保存到具有長生命週期的對象中特别是保存到全局變量時會阻止對短生命週期對象的垃圾迴收從而可能影響程序的性能

View File

@ -39,7 +39,7 @@ i, j, k = 2, 3, 5
但如果表達式太複雜的話,應該盡量避免過度使用元組賦值;因爲每個變量單獨賦值語句的寫法可讀性會更好。
有些表達式會産生多個值,比如調用一個有多個返迴值的函數。當這樣一個函數調用出現在元組賦值右邊的表達式中時(譯註:右邊不能再有其它表達式),左邊變量的數目必和右邊一致。
有些表達式會産生多個值,比如調用一個有多個返迴值的函數。當這樣一個函數調用出現在元組賦值右邊的表達式中時(譯註:右邊不能再有其它表達式),左邊變量的數目必和右邊一致。
```Go
f, err = os.Open("foo.txt") // function call returns two values

View File

@ -16,10 +16,10 @@ medals[2] = "bronze"
map和chan的元素雖然不是普通的變量但是也有類似的隱式賦值行爲。
不管是隱式還是顯式地賦值,在賦值語句左邊的變量和右邊最終的求到的值必有相同的數據類型。更直白地説,隻有右邊的值對於左邊的變量是可賦值的,賦值語句才是允許的。
不管是隱式還是顯式地賦值,在賦值語句左邊的變量和右邊最終的求到的值必有相同的數據類型。更直白地説,隻有右邊的值對於左邊的變量是可賦值的,賦值語句才是允許的。
可賦值性的規則對於不同類型有着不同要求,對每個新類型特殊的地方我們會專門解釋。對於目前我們已經討論過的類型,它的規則是簡單的:類型必完全匹配nil可以賦值給任何指針或引用類型的變量。常量§3.6)則有更靈活的賦值規則,因爲這樣可以避免不必要的顯式的類型轉換。
可賦值性的規則對於不同類型有着不同要求,對每個新類型特殊的地方我們會專門解釋。對於目前我們已經討論過的類型,它的規則是簡單的:類型必完全匹配nil可以賦值給任何指針或引用類型的變量。常量§3.6)則有更靈活的賦值規則,因爲這樣可以避免不必要的顯式的類型轉換。
對於兩個值是否可以用`==`或`!=`進行相等比較的能力也和可賦值能力有關繫:對於任何類型的值的相等比較,第二個值必是對第一個值類型對應的變量是可賦值的,反之依然。和前面一樣,我們會對每個新類型比較特殊的地方做專門的解釋。
對於兩個值是否可以用`==`或`!=`進行相等比較的能力也和可賦值能力有關繫:對於任何類型的值的相等比較,第二個值必是對第一個值類型對應的變量是可賦值的,反之依然。和前面一樣,我們會對每個新類型比較特殊的地方做專門的解釋。

View File

@ -2,7 +2,7 @@
Go語言中的包和其他語言的庫或模塊的概念類似目的都是爲了支持模塊化、封裝、單獨編譯和代碼重用。一個包的源代碼保存在一個或多個以.go爲文件後綴名的源文件中通常一個包所在目録路徑的後綴是包的導入路徑例如包gopl.io/ch1/helloworld對應的目録路徑是$GOPATH/src/gopl.io/ch1/helloworld。
每個包都對應一個獨立的名字空間。例如在image包中的Decode函數和在unicode/utf16包中的 Decode函數是不同的。要在外部引用該函數顯式使用image.Decode或utf16.Decode形式訪問。
每個包都對應一個獨立的名字空間。例如在image包中的Decode函數和在unicode/utf16包中的 Decode函數是不同的。要在外部引用該函數顯式使用image.Decode或utf16.Decode形式訪問。
包還可以讓我們通過控製哪些名字是外部可見的來隱藏內部實現信息。在Go語言中一個簡單的規則是如果一個名字是大寫字母開頭的那麽該名字是導出的譯註因爲漢字不區分大小寫因此漢字開頭的名字是沒有導出的

View File

@ -101,7 +101,7 @@ fmt.Printf("%08b\n", x>>1) // "00010001", the set {0, 4}
6.5節給出了一個可以遠大於一個字節的整數集的實現。)
在`x<<n``x>>n`移位運算中決定了移位操作bit數部分必是無符號數被操作的x數可以是有符號或無符號數。算術上一個`x<<n`$$2^n$$`x>>n`右移運算等價於除以$$2^n$$。
在`x<<n``x>>n`移位運算中決定了移位操作bit數部分必是無符號數被操作的x數可以是有符號或無符號數。算術上一個`x<<n`$$2^n$$`x>>n`右移運算等價於除以$$2^n$$。
左移運算用零填充右邊空缺的bit位無符號數的右移運算也是用0填充左邊空缺的bit位但是有符號數的右移運算會用符號位的值填充左邊空缺的bit位。因爲這個原因最好用無符號運算這樣你可以將整數完全當作一個bit位模式處理。
@ -118,7 +118,7 @@ for i := len(medals) - 1; i >= 0; i-- {
出於這個原因無符號數往往隻有在位運算或其它特殊的運算場景才會使用就像bit集合、分析二進製文件格式或者是哈希和加密操作等。它們通常併不用於僅僅是表達非負數量的場合。
一般來説,需要一個顯式的轉換將一個值從一種類型轉化位另一種類型,併且算術和邏輯運算的二元操作中必是相同的類型。雖然這偶爾會導致需要很長的表達式,但是它消除了所有和類型相關的問題,而且也使得程序容易理解。
一般來説,需要一個顯式的轉換將一個值從一種類型轉化位另一種類型,併且算術和邏輯運算的二元操作中必是相同的類型。雖然這偶爾會導致需要很長的表達式,但是它消除了所有和類型相關的問題,而且也使得程序容易理解。
在很多場景,會遇到類似下面的代碼通用的錯誤:

View File

@ -154,12 +154,12 @@ func f(x, y float64) float64 {
**練習 3.3** 根據高度給每個多邊形上色,那樣峯值部將是紅色(#ff0000),谷部將是藍色(#0000ff)。
**練習 3.4** 參考1.7節Lissajous例子的函數構造一個web服務器用於計算函數麴面然後返迴SVG數據給客戶端。服務器必設置Content-Type頭部
**練習 3.4** 參考1.7節Lissajous例子的函數構造一個web服務器用於計算函數麴面然後返迴SVG數據給客戶端。服務器必設置Content-Type頭部
```Go
w.Header().Set("Content-Type", "image/svg+xml")
```
這一步在Lissajous例子中不是必因爲服務器使用標準的PNG圖像格式可以根據前面的512個字節自動輸出對應的頭部。允許客戶端通過HTTP請求參數設置高度、寬度和顔色等參數。
這一步在Lissajous例子中不是必因爲服務器使用標準的PNG圖像格式可以根據前面的512個字節自動輸出對應的頭部。允許客戶端通過HTTP請求參數設置高度、寬度和顔色等參數。

View File

@ -20,7 +20,7 @@ if 'a' <= c && c <= 'z' ||
}
```
布爾值併不會隱式轉換爲數字值0或1反之亦然。必使用一個顯式的if語句輔助轉換
布爾值併不會隱式轉換爲數字值0或1反之亦然。必使用一個顯式的if語句輔助轉換
```Go
i := 0

View File

@ -30,7 +30,7 @@ Unicode轉義也可以使用在rune字符中。下面三個字符是等價的
'世' '\u4e16' '\U00004e16'
```
對於小於256碼點值可以寫在一個十六進製轉義字節中例如'\x41'對應字符'A',但是對於更大的碼點則必使用\u或\U轉義形式。因此'\xe4\xb8\x96'併不是一個合法的rune字符雖然這三個字節對應一個有效的UTF8編碼的碼點。
對於小於256碼點值可以寫在一個十六進製轉義字節中例如'\x41'對應字符'A',但是對於更大的碼點則必使用\u或\U轉義形式。因此'\xe4\xb8\x96'併不是一個合法的rune字符雖然這三個字節對應一個有效的UTF8編碼的碼點。
得益於UTF8編碼優良的設計諸多字符串操作都不需要解碼操作。我們可以不用解碼直接測試一個字符串是否是另一個字符串的前綴

View File

@ -2,7 +2,7 @@
一個字符串是一個不可改變的字節序列。字符串可以包含任意的數據包括byte值0但是通常是用來包含人類可讀的文本。文本字符串通常被解釋爲采用UTF8編碼的Unicode碼點rune序列我們稍後會詳細討論這個問題。
內置的len函數可以返迴一個字符串中的字節數目不是rune字符數目索引操作s[i]返迴第i個字節的字節值i必滿足0 ≤ i< len(s)
內置的len函數可以返迴一個字符串中的字節數目不是rune字符數目索引操作s[i]返迴第i個字節的字節值i必滿足0 ≤ i< len(s)
```Go
s := "hello, world"

View File

@ -80,7 +80,7 @@ c := 0i // untyped complex; implicit complex128(0i)
註意默認類型是規則的無類型的整數常量默認轉換爲int對應不確定的內存大小但是浮點數和複數常量則默認轉換爲float64和complex128。Go語言本身併沒有不確定內存大小的浮點數和複數類型而且如果不知道浮點數類型的話將很難寫出正確的數值算法。
如果要給變量一個不同的類型,我們必顯式地將無類型的常量轉化爲所需的類型,或給聲明的變量指定明確的類型,像下面例子這樣:
如果要給變量一個不同的類型,我們必顯式地將無類型的常量轉化爲所需的類型,或給聲明的變量指定明確的類型,像下面例子這樣:
```Go
var i = int8(0)

View File

@ -35,7 +35,7 @@ q := [...]int{1, 2, 3}
fmt.Printf("%T\n", q) // "[3]int"
```
數組的長度是數組類型的一個組成部分,因此[3]int和[4]int是兩種不同的數組類型。數組的長度必是常量表達式,因爲數組的長度需要在編譯階段確定。
數組的長度是數組類型的一個組成部分,因此[3]int和[4]int是兩種不同的數組類型。數組的長度必是常量表達式,因爲數組的長度需要在編譯階段確定。
```Go
q := [3]int{1, 2, 3}

View File

@ -38,7 +38,7 @@ func appendInt(x []int, y int) []int {
}
```
每次調用appendInt函數先檢測slice底層數組是否有足夠的容量來保存新添加的元素。如果有足夠空間的話直接擴展slice依然在原有的底層數組之上將新添加的y元素複製到新擴展的空間併返迴slice。因此輸入的x和輸出的z共享相同的底層數組。
每次調用appendInt函數先檢測slice底層數組是否有足夠的容量來保存新添加的元素。如果有足夠空間的話直接擴展slice依然在原有的底層數組之上將新添加的y元素複製到新擴展的空間併返迴slice。因此輸入的x和輸出的z共享相同的底層數組。
如果沒有足夠的增長空間的話appendInt函數則會先分配一個足夠大的slice用於保存新的結果先將輸入的x複製到新的空間然後添加y元素。結果z和輸入的x引用的將是不同的底層數組。

View File

@ -80,7 +80,7 @@ fmt.Println(s) // "[2 3 4 5 0 1]"
要註意的是slice類型的變量s和數組類型的變量a的初始化語法的差異。slice和數組的字面值語法很類似它們都是用花括弧包含一繫列的初始化元素但是對於slice併沒有指明序列的長度。這會隱式地創建一個合適大小的數組然後slice的指針指向底層的數組。就像數組字面值一樣slice的字面值也可以按順序指定初始化值序列或者是通過索引和元素值指定或者的兩種風格的混合語法初始化。
和數組不同的是slice之間不能比較因此我們不能使用==操作符來判斷兩個slice是否含有全部相等元素。不過標準庫提供了高度優化的bytes.Equal函數來判斷兩個字節型slice是否相等[]byte但是對於其他類型的slice我們必自己展開每個元素進行比較:
和數組不同的是slice之間不能比較因此我們不能使用==操作符來判斷兩個slice是否含有全部相等元素。不過標準庫提供了高度優化的bytes.Equal函數來判斷兩個字節型slice是否相等[]byte但是對於其他類型的slice我們必自己展開每個元素進行比較:
```Go
func equal(x, y []string) bool {
@ -98,7 +98,7 @@ func equal(x, y []string) bool {
上面關於兩個slice的深度相等測試運行的時間併不比支持==操作的數組或字符串更多但是爲何slice不直接支持比較運算符呢這方面有兩個原因。第一個原因一個slice的元素是間接引用的一個slice甚至可以包含自身。雖然有很多辦法處理這種情形但是沒有一個是簡單有效的。
第二個原因因爲slice的元素是間接引用的一個固定值的slice在不同的時間可能包含不同的元素因爲底層數組的元素可能會被脩改。併且Go語言中map等哈希表之類的數據結構的key隻做簡單的淺拷貝它要求在整個聲明週期中相等的key必對相同的元素。對於像指針或chan之類的引用類型==相等測試可以判斷兩個是否是引用相同的對象。一個針對slice的淺相等測試的==操作符可能是有一定用處的也能臨時解決map類型的key問題但是slice和數組不同的相等測試行爲會讓人睏惑。因此安全的做飯是直接禁止slice之間的比較操作。
第二個原因因爲slice的元素是間接引用的一個固定值的slice在不同的時間可能包含不同的元素因爲底層數組的元素可能會被脩改。併且Go語言中map等哈希表之類的數據結構的key隻做簡單的淺拷貝它要求在整個聲明週期中相等的key必對相同的元素。對於像指針或chan之類的引用類型==相等測試可以判斷兩個是否是引用相同的對象。一個針對slice的淺相等測試的==操作符可能是有一定用處的也能臨時解決map類型的key問題但是slice和數組不同的相等測試行爲會讓人睏惑。因此安全的做飯是直接禁止slice之間的比較操作。
slice唯一合法的比較操作是和nil比較例如

View File

@ -2,7 +2,7 @@
哈希表是一種巧妙併且實用的數據結構。它是一個無序的key/value對的集合其中所有的key都是不同的然後通過給定的key可以在常數時間複雜度內檢索、更新或刪除對應的value。
在Go語言中一個map就是一個哈希表的引用map類型可以寫爲map[K]V其中K和V分别對應key和value。map中所有的key都有相同的類型所以的value也有着相同的類型但是key和value之間可以是不同的數據類型。其中K對應的key必是支持==比較運算符的數據類型所以map可以通過測試key是否相等來判斷是否已經存在。雖然浮點數類型也是支持相等運算符比較的但是將浮點數用做key類型則是一個壞的想法正如第三章提到的最壞的情況是可能出現的NaN和任何浮點數都不相等。對於V對應的value數據類型則沒有任何的限製。
在Go語言中一個map就是一個哈希表的引用map類型可以寫爲map[K]V其中K和V分别對應key和value。map中所有的key都有相同的類型所以的value也有着相同的類型但是key和value之間可以是不同的數據類型。其中K對應的key必是支持==比較運算符的數據類型所以map可以通過測試key是否相等來判斷是否已經存在。雖然浮點數類型也是支持相等運算符比較的但是將浮點數用做key類型則是一個壞的想法正如第三章提到的最壞的情況是可能出現的NaN和任何浮點數都不相等。對於V對應的value數據類型則沒有任何的限製。
內置的make函數可以創建一個map
@ -76,7 +76,7 @@ for name, age := range ages {
}
```
Map的迭代順序是不確定的併且不同的哈希函數實現可能導致不同的遍歷順序。在實踐中遍歷的順序是隨機的每一次遍歷的順序都不相同。這是故意的每次都使用隨機的遍歷順序可以強製要求程序不會依賴具體的哈希函數實現。如果要按順序遍歷key/value對我們必顯式地對key進行排序可以使用sort包的Strings函數對字符串slice進行排序。下面是常見的處理方式
Map的迭代順序是不確定的併且不同的哈希函數實現可能導致不同的遍歷順序。在實踐中遍歷的順序是隨機的每一次遍歷的順序都不相同。這是故意的每次都使用隨機的遍歷順序可以強製要求程序不會依賴具體的哈希函數實現。如果要按順序遍歷key/value對我們必顯式地對key進行排序可以使用sort包的Strings函數對字符串slice進行排序。下面是常見的處理方式
```Go
import "sort"
@ -113,7 +113,7 @@ map上的大部分操作包括査找、刪除、len和range循環都可以安
ages["carol"] = 21 // panic: assignment to entry in nil map
```
在向map存數據前必先創建map。
在向map存數據前必先創建map。
通過key作爲索引下標來訪問map將産生一個value。如果key在map中是存在的那麽將得到與key對應的value如果key不存在那麽將得到value對應類型的零值正如我們前面看到的ages["bob"]那樣。這個規則很實用但是有時候可能需要知道對應的元素是否眞的是在map之中。例如如果元素類型是一個數字你可以需要區分一個已經存在的0和不存在而返迴零值的0可以像下面這樣測試
@ -130,7 +130,7 @@ if age, ok := ages["bob"]; !ok { /* ... */ }
在這種場景下map的下標語法將産生兩個值第二個是一個布爾值用於報告元素是否眞的存在。布爾變量一般命名爲ok特别適合馬上用於if條件判斷部分。
和slice一樣map之間也不能進行相等比較唯一的例外是和nil進行比較。要判斷兩個map是否包含相同的key和value我們必通過一個循環實現:
和slice一樣map之間也不能進行相等比較唯一的例外是和nil進行比較。要判斷兩個map是否包含相同的key和value我們必通過一個循環實現:
```Go
func equal(x, y map[string]int) bool {
@ -178,7 +178,7 @@ func main() {
Go程序員將這種忽略value的map當作一個字符串集合併非所有`map[string]bool`類型value都是無關緊要的有一些則可能會同時包含tue和false的值。
有時候我們需要一個map或set的key是slice類型但是map的key必是可比較的類型但是slice併不滿足這個條件。不過我們可以通過兩個步驟繞過這個限製。第一步定義一個輔助函數k將slice轉爲map對應的string類型的key確保隻有x和y相等時k(x) == k(y)才成立。然後創建一個key爲string類型的map在每次對map操作時先用k輔助函數將slice轉化爲string類型。
有時候我們需要一個map或set的key是slice類型但是map的key必是可比較的類型但是slice併不滿足這個條件。不過我們可以通過兩個步驟繞過這個限製。第一步定義一個輔助函數k將slice轉爲map對應的string類型的key確保隻有x和y相等時k(x) == k(y)才成立。然後創建一個key爲string類型的map在每次對map操作時先用k輔助函數將slice轉化爲string類型。
下面的例子演示了如何使用map來記録提交相同的字符串列表的次數。它使用了fmt.Sprintf函數將字符串列表轉換爲一個字符串以用於map的key通過%q參數忠實地記録每個字符串元素的信息

View File

@ -50,7 +50,7 @@ func Bonus(e *Employee, percent int) int {
}
```
如果要在函數內部脩改結構體成員的話,用指針傳入是必因爲在Go語言中所有的函數參數都是值拷貝傳入的函數參數將不再是函數調用時的原始變量。
如果要在函數內部脩改結構體成員的話,用指針傳入是必因爲在Go語言中所有的函數參數都是值拷貝傳入的函數參數將不再是函數調用時的原始變量。
```Go
func AwardAnnualRaise(e *Employee) {

View File

@ -52,7 +52,7 @@ w.Circle.Radius = 5
w.Spokes = 20
```
Go語言有一個特性讓我們隻聲明一個成員對應的數據類型而不指名成員的名字這類成員就叫匿名成員。匿名成員的數據類型必是命名的類型或指向一個命名的類型的指針。下面的代碼中Circle和Wheel各自都有一個匿名成員。我們可以説Point類型被嵌入到了Circle結構體同時Circle類型被嵌入到了Wheel結構體。
Go語言有一個特性讓我們隻聲明一個成員對應的數據類型而不指名成員的名字這類成員就叫匿名成員。匿名成員的數據類型必是命名的類型或指向一個命名的類型的指針。下面的代碼中Circle和Wheel各自都有一個匿名成員。我們可以説Point類型被嵌入到了Circle結構體同時Circle類型被嵌入到了Wheel結構體。
```Go
type Circle struct {
@ -85,7 +85,7 @@ w = Wheel{8, 8, 5, 20} // compile error: unknown fields
w = Wheel{X: 8, Y: 8, Radius: 5, Spokes: 20} // compile error: unknown fields
```
結構體字面值必遵循形狀類型聲明時的結構,所以我們隻能用下面的兩種語法,它們彼此是等價的:
結構體字面值必遵循形狀類型聲明時的結構,所以我們隻能用下面的兩種語法,它們彼此是等價的:
```Go
gopl.io/ch4/embed
@ -121,6 +121,6 @@ w.X = 8 // equivalent to w.circle.point.X = 8
但是在包外部因爲circle和point沒有導出不能訪問它們的成員因此簡短的匿名成員訪問語法也是禁止的。
到目前未知,我們看到匿名成員特性隻是對訪問嵌套成員的點運算符提供了簡短的語法醣。稍後,我們將會看到匿名成員併不要求是結構體類型;其實任何命令的類型都可以作爲結構體的匿名成員。但是爲什麽要嵌入一個沒有任何子成員類型的匿名成員類型呢?
到目前爲止,我們看到匿名成員特性隻是對訪問嵌套成員的點運算符提供了簡短的語法糖。稍後,我們將會看到匿名成員併不要求是結構體類型;其實任何命令的類型都可以作爲結構體的匿名成員。但是爲什麽要嵌入一個沒有任何子成員類型的匿名成員類型呢?
答案是匿名類型的方法集。簡短的點運算符語法可以用於選擇匿名成員嵌套的成員也可以用於訪問它們的方法。實際上外層的結構體不僅僅是獲得了匿名成員類型的所有成員而且也獲得了該類型導出的全部的方法。這個機製可以用於將一個有簡單行爲的對象組合成有複雜行爲的對象。組合是Go語言中面向對象編程的核心我們將在6.3節中專門討論。

View File

@ -199,7 +199,7 @@ func SearchIssues(terms []string) (*IssuesSearchResult, error) {
}
```
在早些的例子中我們使用了json.Unmarshal函數來將JSON格式的字符串解碼爲字節slice。但是這個例子中我們使用了基於流式的解碼器json.Decoder它可以從一個輸入流解碼JSON數據盡管這不是必的。如您所料還有一個針對輸出流的json.Encoder編碼對象。
在早些的例子中我們使用了json.Unmarshal函數來將JSON格式的字符串解碼爲字節slice。但是這個例子中我們使用了基於流式的解碼器json.Decoder它可以從一個輸入流解碼JSON數據盡管這不是必的。如您所料還有一個針對輸出流的json.Encoder編碼對象。
我們調用Decode方法來填充變量。這里有多種方法可以格式化結構。下面是最簡單的一種以一個固定寬度打印每個issue但是在下一節我們將看到如果利用模闆來輸出複雜的格式。

View File

@ -20,7 +20,7 @@ fmt.Println(hypot(3,4)) // "5"
x和y是形參名,3和4是調用時的傳入的實數函數返迴了一個float64類型的值。
返迴值也可以像形式參數一樣被命名。在這種情況下每個返迴值被聲明成一個局部變量併根據該返迴值的類型將其初始化爲0。
如果一個函數在聲明時,包含返迴值列表,該函數必以 return語句結尾除非函數明顯無法運行到結尾處。例如函數在結尾時調用了panic異常或函數中存在無限循環。
如果一個函數在聲明時,包含返迴值列表,該函數必以 return語句結尾除非函數明顯無法運行到結尾處。例如函數在結尾時調用了panic異常或函數中存在無限循環。
正如hypot一樣如果一組形參或返迴值有相同的類型我們不必爲每個形參都寫出參數類型。下面2個聲明是等價的
@ -45,7 +45,7 @@ fmt.Printf("%T\n", zero) // "func(int, int) int"
函數的類型被稱爲函數的標識符。如果兩個函數形式參數列表和返迴值列表中的變量類型一一對應,那麽這兩個函數被認爲有相同的類型和標識符。形參和返迴值的變量名不影響函數標識符也不影響它們是否可以以省略參數類型的形式表示。
每一次函數調用都必按照聲明順序爲所有參數提供實參參數值。在函數調用時Go語言沒有默認參數值也沒有任何方法可以通過參數名指定形參因此形參和返迴值的變量名對於函數調用者而言沒有意義。
每一次函數調用都必按照聲明順序爲所有參數提供實參參數值。在函數調用時Go語言沒有默認參數值也沒有任何方法可以通過參數名指定形參因此形參和返迴值的變量名對於函數調用者而言沒有意義。
在函數體中,函數的形參作爲局部變量,被初始化爲調用者提供的值。函數的形參和有名返迴值作爲函數最外層的局部變量,被存儲在相同的詞法塊中。

View File

@ -41,9 +41,9 @@ func findLinks(url string) ([]string, error) {
在findlinks中有4處return語句每一處return都返迴了一組值。前三處return將http和html包中的錯誤信息傳遞給findlinks的調用者。第一處return直接返迴錯誤信息其他兩處通過fmt.Errorf§7.8輸出詳細的錯誤信息。如果findlinks成功結束最後的return語句將一組解析獲得的連接返迴給用戶。
在finallinks中我們必確保resp.Body被關閉釋放網絡資源。雖然Go的垃圾迴收機製會迴收不被使用的內存但是這不包括操作繫統層面的資源比如打開的文件、網絡連接。因此我們必顯式的釋放這些資源。
在finallinks中我們必確保resp.Body被關閉釋放網絡資源。雖然Go的垃圾迴收機製會迴收不被使用的內存但是這不包括操作繫統層面的資源比如打開的文件、網絡連接。因此我們必顯式的釋放這些資源。
調用多返迴值函數時,返迴給調用者的是一組值,調用者必顯式的將這些值分配給變量:
調用多返迴值函數時,返迴給調用者的是一組值,調用者必顯式的將這些值分配給變量:
```Go
links, err := findLinks(url)

View File

@ -1,6 +1,6 @@
### 5.4.2. 文件結尾錯誤EOF
函數經常會返迴多種錯誤,這對終端用戶來説可能會很有趣,但對程序而言,這使得情況變得複雜。很多時候,程序必根據錯誤類型作出不同的響應。讓我們考慮這樣一個例子從文件中讀取n個字節。如果n等於文件的長度讀取過程的任何錯誤都表示失敗。如果n小於文件的長度調用者會重複的讀取固定大小的數據直到文件結束。這會導致調用者必分别處理由文件結束引起的各種錯誤。基於這樣的原因io包保證任何由文件結束引起的讀取失敗都返迴同一個錯誤——io.EOF該錯誤在io包中定義
函數經常會返迴多種錯誤,這對終端用戶來説可能會很有趣,但對程序而言,這使得情況變得複雜。很多時候,程序必根據錯誤類型作出不同的響應。讓我們考慮這樣一個例子從文件中讀取n個字節。如果n等於文件的長度讀取過程的任何錯誤都表示失敗。如果n小於文件的長度調用者會重複的讀取固定大小的數據直到文件結束。這會導致調用者必分别處理由文件結束引起的各種錯誤。基於這樣的原因io包保證任何由文件結束引起的讀取失敗都返迴同一個錯誤——io.EOF該錯誤在io包中定義
```Go
package io

View File

@ -5,7 +5,7 @@
還有一部分函數隻要輸入的參數滿足一定條件也能保證運行成功。比如time.Date函數該函數將年月日等參數構造成time.Time對象除非最後一個參數時區是nil。這種情況下會引發panic異常。panic是來自被調函數的信號表示發生了某個已知的bug。一個良好的程序永遠不應該發生panic異常。
對於大部分函數而言永遠無法確保能否成功運行。這是因爲錯誤的原因超出了程序員的控製。舉個例子任何進行I/O操作的函數都會面臨出現錯誤的可能隻有沒有經驗的程序員才會相信讀寫操作不會失敗卽時是簡單的讀寫。因此當本該可信的操作出乎意料的失敗後我們必弄清楚導致失敗的原因。
對於大部分函數而言永遠無法確保能否成功運行。這是因爲錯誤的原因超出了程序員的控製。舉個例子任何進行I/O操作的函數都會面臨出現錯誤的可能隻有沒有經驗的程序員才會相信讀寫操作不會失敗卽時是簡單的讀寫。因此當本該可信的操作出乎意料的失敗後我們必弄清楚導致失敗的原因。
在Go的錯誤處理中錯誤是軟件包API和應用程序用戶界面的一個重要組成部分程序運行失敗僅被認爲是幾個預期的結果之一。

View File

@ -36,7 +36,7 @@ squares的例子證明函數值不僅僅是一串代碼還記録了狀態
通過這個例子我們看到變量的生命週期不由它的作用域決定squares返迴後變量x仍然隱式的存在於f中。
接下來,我們討論一個有點學術性的例子,考慮這樣一個問題:給定一些計算機課程,每個課程都有前置課程,隻有完成了前置課程才可以開始當前課程的學習;我們的目標是選擇出一組課程,這組課程必確保按順序學習時,能全部被完成。每個課程的前置課程如下:
接下來,我們討論一個有點學術性的例子,考慮這樣一個問題:給定一些計算機課程,每個課程都有前置課程,隻有完成了前置課程才可以開始當前課程的學習;我們的目標是選擇出一組課程,這組課程必確保按順序學習時,能全部被完成。每個課程的前置課程如下:
```Go
gopl.io/ch5/toposort
@ -91,7 +91,7 @@ func topoSort(m map[string][]string) []string {
}
```
當匿名函數需要被遞歸調用時,我們必首先聲明一個變量(在上面的例子中,我們首先聲明了 visitAll再將匿名函數賦值給這個變量。如果不分成兩部函數字面量無法與visitAll綁定我們也無法遞歸調用該匿名函數。
當匿名函數需要被遞歸調用時,我們必首先聲明一個變量(在上面的例子中,我們首先聲明了 visitAll再將匿名函數賦值給這個變量。如果不分成兩部函數字面量無法與visitAll綁定我們也無法遞歸調用該匿名函數。
```Go
visitAll := func(items []string) {

View File

@ -17,7 +17,7 @@ switch s := suit(drawCard()); s {
}
```
斷言函數必滿足的前置條件是明智的做法,但這很容易被濫用。除非你能提供更多的錯誤信息,或者能更快速的發現錯誤,否則不需要使用斷言,編譯器在運行時會幫你檢査代碼。
斷言函數必滿足的前置條件是明智的做法,但這很容易被濫用。除非你能提供更多的錯誤信息,或者能更快速的發現錯誤,否則不需要使用斷言,編譯器在運行時會幫你檢査代碼。
```Go
func Reset(x *Buffer) {

View File

@ -77,7 +77,7 @@ fmt.Println(perim.Distance()) // "12"
在上面兩個對Distance名字的方法的調用中編譯器會根據方法的名字以及接收器來決定具體調用的是哪一個函數。第一個例子中path[i-1]數組中的類型是Point因此Point.Distance這個方法被調用在第二個例子中perim的類型是Path因此Distance調用的是Path.Distance。
對於一個給定的類型,其內部的方法都必有唯一的方法名但是不同的類型卻可以有同樣的方法名比如我們這里Point和Path就都有Distance這個名字的方法所以我們沒有必要非在方法名之前加類型名來消除歧義比如PathDistance。這里我們已經看到了方法比之函數的一些好處方法名可以簡短。當我們在包外調用的時候這種好處就會被放大因爲我們可以使用這個短名字而可以省略掉包的名字下面是例子
對於一個給定的類型,其內部的方法都必有唯一的方法名但是不同的類型卻可以有同樣的方法名比如我們這里Point和Path就都有Distance這個名字的方法所以我們沒有必要非在方法名之前加類型名來消除歧義比如PathDistance。這里我們已經看到了方法比之函數的一些好處方法名可以簡短。當我們在包外調用的時候這種好處就會被放大因爲我們可以使用這個短名字而可以省略掉包的名字下面是例子
```Go
import "gopl.io/ch6/geometry"

View File

@ -9,9 +9,9 @@ func (p *Point) ScaleBy(factor float64) {
}
```
這個方法的名字是`(*Point).ScaleBy`。這里的括號是必的;沒有括號的話這個表達式可能會被理解爲`*(Point.ScaleBy)`。
這個方法的名字是`(*Point).ScaleBy`。這里的括號是必的;沒有括號的話這個表達式可能會被理解爲`*(Point.ScaleBy)`。
在現實的程序里一般會約定如果Point這個類有一個指針作爲接收器的方法那麽所有Point的方法都必有一個指針接收器,卽使是那些併不需要這個指針接收器的函數。我們在這里打破了這個約定隻是爲了展示一下兩種方法的異同而已。
在現實的程序里一般會約定如果Point這個類有一個指針作爲接收器的方法那麽所有Point的方法都必有一個指針接收器,卽使是那些併不需要這個指針接收器的函數。我們在這里打破了這個約定隻是爲了展示一下兩種方法的異同而已。
隻有類型(Point)和指向他們的指針(*Point),才是可能會出現在接收器聲明里的兩種接收器。此外,爲了避免歧義,在聲明方法時,如果一個類型名本身是一個指針的話,是不允許其出現在接收器中的,比如下面這個例子:

View File

@ -37,7 +37,7 @@ fmt.Println(p.Distance(q.Point)) // "10"
Point類的方法也被引入了ColoredPoint。用這種方式內嵌可以使我們定義字段特别多的複雜類型我們可以將字段先按小類型分組然後定義小類型的方法之後再把它們組合起來。
讀者如果對基於類來實現面向對象的語言比較熟悉的話可能會傾向於將Point看作一個基類而ColoredPoint看作其子類或者繼承類或者將ColoredPoint看作"is a" Point類型。但這是錯誤的理解。請註意上面例子中對Distance方法的調用。Distance有一個參數是Point類型但q併不是一個Point類所以盡管q有着Point這個內嵌類型我們也必要顯式地選擇它。嚐試直接傳q的話你會看到下面這樣的錯誤
讀者如果對基於類來實現面向對象的語言比較熟悉的話可能會傾向於將Point看作一個基類而ColoredPoint看作其子類或者繼承類或者將ColoredPoint看作"is a" Point類型。但這是錯誤的理解。請註意上面例子中對Distance方法的調用。Distance有一個參數是Point類型但q併不是一個Point類所以盡管q有着Point這個內嵌類型我們也必要顯式地選擇它。嚐試直接傳q的話你會看到下面這樣的錯誤
```go
p.Distance(q) // compile error: cannot use q (ColoredPoint) as Point

View File

@ -34,7 +34,7 @@ time.AfterFunc(10 * time.Second, r.Launch)
譯註:省掉了上面那個例子里的匿名函數。
和方法"值"相關的還有方法表達式。當調用一個方法時,與調用一個普通的函數相比,我們必要用選擇器(p.Distance)語法來指定方法的接收器。
和方法"值"相關的還有方法表達式。當調用一個方法時,與調用一個普通的函數相比,我們必要用選擇器(p.Distance)語法來指定方法的接收器。
當T是一個類型時方法表達式可能會寫作T.f或者(*T).f會返迴一個函數"值",這種函數會將其第一個參數用作接收器,所以可以用通常(譯註:不寫選擇器)的方式來對其進行調用:

View File

@ -2,7 +2,7 @@
一個對象的變量或者方法如果對調用方是不可見的話,一般就被定義爲“封裝”。封裝有時候也被叫做信息隱藏,同時也是面向對象編程最關鍵的一個方面。
Go語言隻有一種控製可見性的手段大寫首字母的標識符會從定義它們的包中被導出小寫字母的則不會。這種限製包內成員的方式同樣適用於struct或者一個類型的方法。因而如果我們想要封裝一個對象我們必將其定義爲一個struct。
Go語言隻有一種控製可見性的手段大寫首字母的標識符會從定義它們的包中被導出小寫字母的則不會。這種限製包內成員的方式同樣適用於struct或者一個類型的方法。因而如果我們想要封裝一個對象我們必將其定義爲一個struct。
這也就是前面的小節中IntSet被定義爲struct類型的原因盡管它隻有一個字段

View File

@ -43,7 +43,7 @@ io.Writer類型定義了函數Fprintf和這個函數調用者之間的約定。
因爲fmt.Fprintf函數沒有對具體操作的值做任何假設而是僅僅通過io.Writer接口的約定來保證行爲所以第一個參數可以安全地傳入一個任何具體類型的值隻需要滿足io.Writer接口。一個類型可以自由的使用另一個滿足相同接口的類型來進行替換被稱作可替換性(LSP里氏替換)。這是一個面向對象的特徵。
讓我們通過一個新的類型來進行校驗,下面\*ByteCounter類型里的Write方法僅僅在丟失寫向它的字節前統計它們的長度。(在這個+=賦值語句中讓len(p)的類型和\*c的類型匹配的轉換是必的。)
讓我們通過一個新的類型來進行校驗,下面\*ByteCounter類型里的Write方法僅僅在丟失寫向它的字節前統計它們的長度。(在這個+=賦值語句中讓len(p)的類型和\*c的類型匹配的轉換是必的。)
```go
// gopl.io/ch7/bytecounter

View File

@ -23,7 +23,7 @@ rwc = w // compile error: io.Writer lacks Close method
因爲ReadWriter和ReadWriteCloser包含所有Writer的方法所以任何實現了ReadWriter和ReadWriteCloser的類型必定也實現了Writer接口
在進一步學習前,必先解釋表示一個類型持有一個方法當中的細節。迴想在6.2章中對於每一個命名過的具體類型T它一些方法的接收者是類型T本身然而另一些則是一個*T的指針。還記得在T類型的參數上調用一個*T的方法是合法的隻要這個參數是一個變量編譯器隱式的獲取了它的地址。但這僅僅是一個語法T類型的值不擁有所有*T指針的方法那這樣它就可能隻實現更少的接口。
在進一步學習前,必先解釋表示一個類型持有一個方法當中的細節。迴想在6.2章中對於每一個命名過的具體類型T它一些方法的接收者是類型T本身然而另一些則是一個*T的指針。還記得在T類型的參數上調用一個*T的方法是合法的隻要這個參數是一個變量編譯器隱式的獲取了它的地址。但這僅僅是一個語法T類型的值不擁有所有*T指針的方法那這樣它就可能隻實現更少的接口。
舉個例子可能會更清晰一點。在第6.5章中IntSet類型的String方法的接收者是一個指針類型所以我們不能在一個不能尋址的IntSet值上調用這個方法

View File

@ -72,9 +72,9 @@ func (f *celsiusFlag) Set(s string) error {
}
```
調用fmt.Sscanf函數從輸入s中解析一個浮點數value和一個字符串unit。雖然通常必檢査Sscanf的錯誤返迴但是在這個例子中我們不需要因爲如果有錯誤發生就沒有switch case會匹配到。
調用fmt.Sscanf函數從輸入s中解析一個浮點數value和一個字符串unit。雖然通常必檢査Sscanf的錯誤返迴但是在這個例子中我們不需要因爲如果有錯誤發生就沒有switch case會匹配到。
下面的CelsiusFlag函數將所有邏輯都封裝在一起。它返迴一個內嵌在celsiusFlag變量f中的Celsius指針給調用者。Celsius字段是一個會通過Set方法在標記處理的過程中更新的變量。調用Var方法將標記加入應用的命令行標記集合中有異常複雜命令行接口的全局變量flag.CommandLine.Programs可能有幾個這個類型的變量。調用Var方法將一個*celsiusFlag參數賦值給一個flag.Value參數,導致編譯器去檢査*celsiusFlag是否有必的方法。
下面的CelsiusFlag函數將所有邏輯都封裝在一起。它返迴一個內嵌在celsiusFlag變量f中的Celsius指針給調用者。Celsius字段是一個會通過Set方法在標記處理的過程中更新的變量。調用Var方法將標記加入應用的命令行標記集合中有異常複雜命令行接口的全局變量flag.CommandLine.Programs可能有幾個這個類型的變量。調用Var方法將一個*celsiusFlag參數賦值給一個flag.Value參數,導致編譯器去檢査*celsiusFlag是否有必的方法。
```go
// CelsiusFlag defines a Celsius flag with the specified name,

View File

@ -42,7 +42,7 @@ w = os.Stdout
w.Write([]byte("hello")) // "hello"
```
通常在編譯期,我們不知道接口值的動態類型是什麽,所以一個接口上的調用必鬚使用動態分配。因爲不是直接進行調用,所以編譯器必鬚把代碼生成在類型描述符的方法Write上然後間接調用那個地址。這個調用的接收者是一個接口動態值的拷貝os.Stdout。效果和下面這個直接調用一樣
通常在編譯期,我們不知道接口值的動態類型是什麽,所以一個接口上的調用必須使用動態分配。因爲不是直接進行調用,所以編譯器必須把代碼生成在類型描述符的方法Write上然後間接調用那個地址。這個調用的接收者是一個接口動態值的拷貝os.Stdout。效果和下面這個直接調用一樣
```go
os.Stdout.Write([]byte("hello")) // "hello"
@ -93,7 +93,7 @@ var x interface{} = []int{1, 2, 3}
fmt.Println(x == x) // panic: comparing uncomparable type []int
```
考慮到這點,接口類型是非常與衆不同的。其它類型要麽是安全的可比較類型(如基本類型和指針)要麽是完全不可比較的類型(如切片,映射類型,和函數),但是在比較接口值或者包含了接口值的聚合類型時,我們必要意識到潛在的panic。同樣的風險也存在於使用接口作爲map的鍵或者switch的操作數。隻能比較你非常確定它們的動態值是可比較類型的接口值。
考慮到這點,接口類型是非常與衆不同的。其它類型要麽是安全的可比較類型(如基本類型和指針)要麽是完全不可比較的類型(如切片,映射類型,和函數),但是在比較接口值或者包含了接口值的聚合類型時,我們必要意識到潛在的panic。同樣的風險也存在於使用接口作爲map的鍵或者switch的操作數。隻能比較你非常確定它們的動態值是可比較類型的接口值。
當我們處理錯誤或者調試的過程中得知接口值的動態類型是非常有幫助的。所以我們使用fmt包的%T動作:

View File

@ -80,7 +80,7 @@ func printTracks(tracks []*Track) {
}
```
爲了能按照Artist字段對播放列表進行排序我們會像對StringSlice那樣定義一個新的帶有必LenLess和Swap方法的切片類型。
爲了能按照Artist字段對播放列表進行排序我們會像對StringSlice那樣定義一個新的帶有必LenLess和Swap方法的切片類型。
```go
type byArtist []*Track
@ -89,7 +89,7 @@ func (x byArtist) Less(i, j int) bool { return x[i].Artist < x[j].Artist }
func (x byArtist) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
```
爲了調用通用的排序程序,我們必先將tracks轉換爲新的byArtist類型它定義了具體的排序
爲了調用通用的排序程序,我們必先將tracks轉換爲新的byArtist類型它定義了具體的排序
```go
sort.Sort(byArtist(tracks))
@ -137,7 +137,7 @@ func Reverse(data Interface) Interface { return reverse{data} }
reverse的另外兩個方法Len和Swap隱式地由原有內嵌的sort.Interface提供。因爲reverse是一個不公開的類型所以導出函數Reverse函數返迴一個包含原有sort.Interface值的reverse類型實例。
爲了可以按照不同的列進行排序,我們必定義一個新的類型例如byYear
爲了可以按照不同的列進行排序,我們必定義一個新的類型例如byYear
```go
type byYear []*Track

View File

@ -175,7 +175,7 @@ mux.HandleFunc("/list", db.list)
mux.HandleFunc("/price", db.price)
```
從上面的代碼很容易看出應該怎麽構建一個程序它有兩個不同的web服務器監聽不同的端口的併且定義不同的URL將它們指派到不同的handler。我們隻要構建另外一個ServeMux併且在調用一次ListenAndServe可能併行的。但是在大多數程序中一個web服務器就足夠了。此外在一個應用程序的多個文件中定義HTTP handler也是非常典型的如果它們必全部都顯示的註冊到這個應用的ServeMux實例上會比較麻煩。
從上面的代碼很容易看出應該怎麽構建一個程序它有兩個不同的web服務器監聽不同的端口的併且定義不同的URL將它們指派到不同的handler。我們隻要構建另外一個ServeMux併且在調用一次ListenAndServe可能併行的。但是在大多數程序中一個web服務器就足夠了。此外在一個應用程序的多個文件中定義HTTP handler也是非常典型的如果它們必全部都顯示的註冊到這個應用的ServeMux實例上會比較麻煩。
所以爲了方便net/http包提供了一個全局的ServeMux實例DefaultServerMux和包級别的http.Handle和http.HandleFunc函數。現在爲了使用DefaultServeMux作爲服務器的主handler我們不需要將它傳給ListenAndServe函數nil值就可以工作。

View File

@ -50,7 +50,7 @@ type call struct {
type Env map[Var]float64
```
我們也需要每個表示式去定義一個Eval方法這個方法會根據給定的environment變量返迴表達式的值。因爲每個表達式都必提供這個方法我們將它加入到Expr接口中。這個包隻會對外公開ExprEnv和Var類型。調用方不需要獲取其它的表達式類型就可以使用這個求值器。
我們也需要每個表示式去定義一個Eval方法這個方法會根據給定的environment變量返迴表達式的值。因爲每個表達式都必提供這個方法我們將它加入到Expr接口中。這個包隻會對外公開ExprEnv和Var類型。調用方不需要獲取其它的表達式類型就可以使用這個求值器。
```go
type Expr interface {
@ -248,7 +248,7 @@ log(10) unknown function "log"
sqrt(1, 2) call to sqrt has 2 args, want 1
```
Check方法的參數是一個Var類型的集合這個集合聚集從表達式中找到的變量名。爲了保證成功的計算這些變量中的每一個都必出現在環境變量中。從邏輯上講這個集合就是調用Check方法返迴的結果但是因爲這個方法是遞歸調用的所以對於Check方法填充結果到一個作爲參數傳入的集合中會更加的方便。調用方在初始調用時必提供一個空的集合。
Check方法的參數是一個Var類型的集合這個集合聚集從表達式中找到的變量名。爲了保證成功的計算這些變量中的每一個都必出現在環境變量中。從邏輯上講這個集合就是調用Check方法返迴的結果但是因爲這個方法是遞歸調用的所以對於Check方法填充結果到一個作爲參數傳入的集合中會更加的方便。調用方在初始調用時必提供一個空的集合。
在第3.2節中我們繪製了一個在編譯器才確定的函數f(x,y)。現在我們可以解析檢査和計算在字符串中的表達式我們可以構建一個在運行時從客戶端接收表達式的web應用併且它會繪製這個函數的表示的麴面。我們可以使用集合vars來檢査表達式是否是一個隻有兩個變量,x和y的函數——實際上是3個因爲我們爲了方便會提供半徑大小r。併且我們會在計算前使用Check方法拒絶有格式問題的表達式這樣我們就不會在下面函數的40000個計算過程100x100個柵格每一個有4個角重複這些檢査。

View File

@ -112,7 +112,7 @@ $ killall clock1
killall命令是一個Unix命令行工具可以用給定的進程名來殺掉所有名字匹配的進程。
第二個客戶端必等待第一個客戶端完成工作這樣服務端才能繼續向後執行因爲我們這里的服務器程序同一時間隻能處理一個客戶端連接。我們這里對服務端程序做一點小改動使其支持併發在handleConn函數調用的地方增加go關鍵字讓每一次handleConn的調用都進入一個獨立的goroutine。
第二個客戶端必等待第一個客戶端完成工作這樣服務端才能繼續向後執行因爲我們這里的服務器程序同一時間隻能處理一個客戶端連接。我們這里對服務端程序做一點小改動使其支持併發在handleConn函數調用的地方增加go關鍵字讓每一次handleConn的調用都進入一個獨立的goroutine。
```go
gopl.io/ch8/clock2

View File

@ -72,7 +72,7 @@ func request(hostname string) (response string) { /* ... */ }
關於無緩存或帶緩存channels之間的選擇或者是帶緩存channels的容量大小的選擇都可能影響程序的正確性。無緩存channel更強地保證了每個發送操作與相應的同步接收操作但是對於帶緩存channel這些操作是解耦的。同樣卽使我們知道將要發送到一個channel的信息的數量上限創建一個對應容量大小帶緩存channel也是不現實的因爲這要求在執行任何接收操作之前緩存所有已經發送的值。如果未能分配足夠的緩衝將導致程序死鎖。
Channel的緩存也可能影響程序的性能。想象一家蛋糕店有三個廚師一個烘焙一個上衣,還有一個將每個蛋糕傳遞到它下一個廚師在生産線。在狹小的廚房空間環境,每個廚師在完成蛋糕後必等待下一個廚師已經準備好接受它這類似於在一個無緩存的channel上進行溝通。
Channel的緩存也可能影響程序的性能。想象一家蛋糕店有三個廚師一個烘焙一個上衣,還有一個將每個蛋糕傳遞到它下一個廚師在生産線。在狹小的廚房空間環境,每個廚師在完成蛋糕後必等待下一個廚師已經準備好接受它這類似於在一個無緩存的channel上進行溝通。
如果在每個廚師之間有一個放置一個蛋糕的額外空間那麽每個廚師就可以將一個完成的蛋糕臨時放在那里而馬上進入下一個蛋糕在製作中這類似於將channel的緩存隊列的容量設置爲1。隻要每個廚師的平均工作效率相近那麽其中大部分的傳輸工作將是迅速的個體之間細小的效率差異將在交接過程中瀰補。如果廚師之間有更大的額外空間——也是就更大容量的緩存隊列——將可以在不停止生産線的前提下消除更大的效率波動例如一個廚師可以短暫地休息然後在加快趕上進度而不影響其其他人。

View File

@ -176,9 +176,9 @@ func makeThumbnails6(filenames <-chan string) int64 {
}
```
註意Add和Done方法的不對策。Add是爲計數器加一在worker goroutine開始之前調用而不是在goroutine中否則的話我們沒辦法確定Add是在"closer" goroutine調用Wait之前被調用。併且Add還有一個參數但Done卻沒有任何參數其實它和Add(-1)是等價的。我們使用defer來確保計數器卽使是在出錯的情況下依然能夠正確地被減掉。上面的程序代碼結構是當我們使用併發循環但又不知道迭代次數時很通常而且很地道的寫法。
註意Add和Done方法的不對策。Add是爲計數器加一在worker goroutine開始之前調用而不是在goroutine中否則的話我們沒辦法確定Add是在"closer" goroutine調用Wait之前被調用。併且Add還有一個參數但Done卻沒有任何參數其實它和Add(-1)是等價的。我們使用defer來確保計數器卽使是在出錯的情況下依然能夠正確地被減掉。上面的程序代碼結構是當我們使用併發循環但又不知道迭代次數時很通常而且很地道的寫法。
sizes channel攜帶了每一個文件的大小到main goroutine在main goroutine中使用了range loop來計算總和。觀察一下我們是怎樣創建一個closer goroutine併讓其等待worker們在關閉掉sizes channel之前退出的。兩步操作wait和close是基於sizes的循環的併發。考慮一下另一種方案如果等待操作被放在了main goroutine中在循環之前這樣的話就永遠都不會結束了如果在循環之後那麽又變成了不可達的部分因爲沒有任何東西去關閉這個channel這個循環就永遠都不會終止。
sizes channel攜帶了每一個文件的大小到main goroutine在main goroutine中使用了range loop來計算總和。觀察一下我們是怎樣創建一個closer goroutine併讓其等待worker們在關閉掉sizes channel之前退出的。兩步操作wait和close是基於sizes的循環的併發。考慮一下另一種方案如果等待操作被放在了main goroutine中在循環之前這樣的話就永遠都不會結束了如果在循環之後那麽又變成了不可達的部分因爲沒有任何東西去關閉這個channel這個循環就永遠都不會終止。
圖8.5 表明了makethumbnails6函數中事件的序列。縱列表示goroutine。窄線段代表sleep粗線段代表活動。斜線箭頭代表用來同步兩個goroutine的事件。時間向下流動。註意main goroutine是如何大部分的時間被喚醒執行其range循環等待worker發送值或者closer來關閉channel的。

View File

@ -116,7 +116,7 @@ loop:
printDiskUsage(nfiles, nbytes) // final totals
}
```
由於我們的程序不再使用range循環第一個select的case必顯式地判斷fileSizes的channel是不是已經被關閉了這里可以用到channel接收的二值形式。如果channel已經被關閉了的話程序會直接退出循環。這里的break語句用到了標籤break這樣可以同時終結select和for兩個循環如果沒有用標籤就break的話隻會退出內層的select循環而外層的for循環會使之進入下一輪select循環。
由於我們的程序不再使用range循環第一個select的case必顯式地判斷fileSizes的channel是不是已經被關閉了這里可以用到channel接收的二值形式。如果channel已經被關閉了的話程序會直接退出循環。這里的break語句用到了標籤break這樣可以同時終結select和for兩個循環如果沒有用標籤就break的話隻會退出內層的select循環而外層的for循環會使之進入下一輪select循環。
現在程序會悠閒地爲我們打印更新流:

View File

@ -6,7 +6,7 @@
在一個程序中有非併發安全的類型的情況下我們依然可以使這個程序併發安全。確實併發安全的類型是例外而不是規則所以隻有當文檔中明確地説明了其是併發安全的情況下你才可以併發地去訪問它。我們會避免併發訪問大多數的類型無論是將變量局限在單一的一個goroutine內還是用互斥條件維持更高級别的不變性都是爲了這個目的。我們會在本章中説明這些術語。
相反導出包級别的函數一般情況下都是併發安全的。由於package級的變量沒法被限製在單一的gorouine所以脩改這些變量“必”使用互斥條件。
相反導出包級别的函數一般情況下都是併發安全的。由於package級的變量沒法被限製在單一的gorouine所以脩改這些變量“必”使用互斥條件。
一個函數在併發調用時沒法工作的原因太多了,比如死鎖(deadlock)、活鎖(livelock)和餓死(resource starvation)。我們沒有空去討論所有的問題,這里我們隻聚焦在競爭條件上。

View File

@ -54,7 +54,7 @@ func Balance() int {
上面的bank程序例證了一種通用的併發模式。一繫列的導出函數封裝了一個或多個變量那麽訪問這些變量唯一的方式就是通過這些函數來做(或者方法,對於一個對象的變量來説)。每一個函數在一開始就獲取互斥鎖併在最後釋放鎖從而保證共享變量不會被併發訪問。這種函數、互斥鎖和變量的編排叫作監控monitor(這種老式單詞的monitor是受"monitor goroutine"的術語啟發而來的。兩種用法都是一個代理人保證變量被順序訪問)。
由於在存款和査詢餘額函數中的臨界區代碼這麽短--隻有一行,沒有分支調用--在代碼最後去調用Unlock就顯得更爲直截了當。在更複雜的臨界區的應用中尤其是必要盡早處理錯誤併返迴的情況下,就很難去(靠人)判斷對Lock和Unlock的調用是在所有路徑中都能夠嚴格配對的了。Go語言里的defer簡直就是這種情況下的救星我們用defer來調用Unlock臨界區會隱式地延伸到函數作用域的最後這樣我們就從“總要記得在函數返迴之後或者發生錯誤返迴時要記得調用一次Unlock”這種狀態中獲得了解放。Go會自動幫我們完成這些事情。
由於在存款和査詢餘額函數中的臨界區代碼這麽短--隻有一行,沒有分支調用--在代碼最後去調用Unlock就顯得更爲直截了當。在更複雜的臨界區的應用中尤其是必要盡早處理錯誤併返迴的情況下,就很難去(靠人)判斷對Lock和Unlock的調用是在所有路徑中都能夠嚴格配對的了。Go語言里的defer簡直就是這種情況下的救星我們用defer來調用Unlock臨界區會隱式地延伸到函數作用域的最後這樣我們就從“總要記得在函數返迴之後或者發生錯誤返迴時要記得調用一次Unlock”這種狀態中獲得了解放。Go會自動幫我們完成這些事情。
```go
func Balance() int {
@ -102,7 +102,7 @@ func Withdraw(amount int) bool {
上面這個例子中Deposit會調用mu.Lock()第二次去獲取互斥鎖但因爲mutex已經鎖上了而無法被重入(譯註go里沒有重入鎖關於重入鎖的概念請參考java)--也就是説沒法對一個已經鎖上的mutex來再次上鎖--這會導致程序死鎖沒法繼續執行下去Withdraw會永遠阻塞下去。
關於Go的互斥量不能重入這一點我們有很充分的理由。互斥量的目的是爲了確保共享變量在程序執行時的關鍵點上能夠保證不變性。不變性的其中之一是“沒有goroutine訪問共享變量”。但實際上對於mutex保護的變量來説不變性還包括其它方面。當一個goroutine獲得了一個互斥鎖時它會斷定這種不變性能夠被保持。其獲取併保持鎖期間可能會去更新共享變量這樣不變性隻是短暫地被破壞。然而當其釋放鎖之後它必保證不變性已經恢複原樣。盡管一個可以重入的mutex也可以保證沒有其它的goroutine在訪問共享變量但這種方式沒法保證這些變量額外的不變性。(譯註:這段翻譯有點暈)
關於Go的互斥量不能重入這一點我們有很充分的理由。互斥量的目的是爲了確保共享變量在程序執行時的關鍵點上能夠保證不變性。不變性的其中之一是“沒有goroutine訪問共享變量”。但實際上對於mutex保護的變量來説不變性還包括其它方面。當一個goroutine獲得了一個互斥鎖時它會斷定這種不變性能夠被保持。其獲取併保持鎖期間可能會去更新共享變量這樣不變性隻是短暫地被破壞。然而當其釋放鎖之後它必保證不變性已經恢複原樣。盡管一個可以重入的mutex也可以保證沒有其它的goroutine在訪問共享變量但這種方式沒法保證這些變量額外的不變性。(譯註:這段翻譯有點暈)
一個通用的解決方案是將一個函數分離爲多個函數比如我們把Deposit分離成兩個一個不導出的函數deposit這個函數假設鎖總是會被保持併去做實際的操作另一個是導出的函數Deposit這個函數會調用deposit但在調用前會先去獲取鎖。同理我們可以將Withdraw也表示成這種形式

View File

@ -20,5 +20,5 @@ Balance函數現在調用了RLock和RUnlock方法來獲取和釋放一個讀取
RLock隻能在臨界區共享變量沒有任何寫入操作時可用。一般來説我們不應該假設邏輯上的隻讀函數/方法也不會去更新某一些變量。比如一個方法功能是訪問一個變量,但它也有可能會同時去給一個內部的計數器+1(譯註:可能是記録這個方法的訪問次數啥的),或者去更新緩存--使卽時的調用能夠更快。如果有疑惑的話,請使用互斥鎖。
RWMutex隻有當獲得鎖的大部分goroutine都是讀操作而鎖在競爭條件下也就是説goroutine們必等待才能獲取到鎖的時候RWMutex才是最能帶來好處的。RWMutex需要更複雜的內部記録所以會讓它比一般的無競爭鎖的mutex慢一些。
RWMutex隻有當獲得鎖的大部分goroutine都是讀操作而鎖在競爭條件下也就是説goroutine們必等待才能獲取到鎖的時候RWMutex才是最能帶來好處的。RWMutex需要更複雜的內部記録所以會讓它比一般的無競爭鎖的mutex慢一些。

View File

@ -87,7 +87,7 @@ func Icon(name string) image.Image {
```
上面的代碼有兩個臨界區。goroutine首先會獲取一個寫鎖査詢map然後釋放鎖。如果條目被找到了(一般情況下)那麽會直接返迴。如果沒有找到那goroutine會獲取一個寫鎖。不釋放共享鎖的話也沒有任何辦法來將一個共享鎖陞級爲一個互斥鎖所以我們必重新檢査icons變量是否爲nil以防止在執行這一段代碼的時候icons變量已經被其它gorouine初始化過了。
上面的代碼有兩個臨界區。goroutine首先會獲取一個寫鎖査詢map然後釋放鎖。如果條目被找到了(一般情況下)那麽會直接返迴。如果沒有找到那goroutine會獲取一個寫鎖。不釋放共享鎖的話也沒有任何辦法來將一個共享鎖陞級爲一個互斥鎖所以我們必重新檢査icons變量是否爲nil以防止在執行這一段代碼的時候icons變量已經被其它gorouine初始化過了。
上面的模闆使我們的程序能夠更好的併發但是有一點太複雜且容易出錯。幸運的是sync包爲我們提供了一個專門的方案來解決這種一次性初始化的問題sync.Once。概念上來講一次性的初始化需要一個互斥量mutex和一個boolean變量來記録初始化是不是已經完成了互斥量用來保護boolean變量和客戶端數據結構。Do這個唯一的方法需要接收初始化函數作爲其參數。讓我們用sync.Once來簡化前面的Icon函數吧

View File

@ -18,6 +18,6 @@ $ GOMAXPROCS=2 go run hacker-cliché.go
010101010101010101011001100101011010010100110...
```
在第一次執行時最多同時隻能有一個goroutine被執行。初始情況下隻有main goroutine被執行所以會打印很多1。過了一段時間後GO調度器會將其置爲休眠併喚醒另一個goroutine這時候就開始打印很多0了在打印的時候goroutine是被調度到操作繫統線程上的。在第二次執行時我們使用了兩個操作繫統線程所以兩個goroutine可以一起被執行以同樣的頻率交替打印0和1。我們必強調的是goroutine的調度是受很多因子影響的而runtime也是在不斷地發展演進的所以這里的你實際得到的結果可能會因爲版本的不同而與我們運行的結果有所不同。
在第一次執行時最多同時隻能有一個goroutine被執行。初始情況下隻有main goroutine被執行所以會打印很多1。過了一段時間後GO調度器會將其置爲休眠併喚醒另一個goroutine這時候就開始打印很多0了在打印的時候goroutine是被調度到操作繫統線程上的。在第二次執行時我們使用了兩個操作繫統線程所以兩個goroutine可以一起被執行以同樣的頻率交替打印0和1。我們必強調的是goroutine的調度是受很多因子影響的而runtime也是在不斷地發展演進的所以這里的你實際得到的結果可能會因爲版本的不同而與我們運行的結果有所不同。
練習9.6: 測試一下計算密集型的併發程序(練習8.5那樣的)會被GOMAXPROCS怎樣影響到。在你的電腦上最佳的值是多少你的電腦CPU有多少個核心

View File

@ -1,5 +1,5 @@
# 第九章 基於共享變量的併發
前一章我們介紹了一些使用goroutine和channel這樣直接而自然的方式來實現併發的方法。然而這樣做我們實際上屏蔽掉了在寫併發代碼時必處理的一些重要而且細微的問題。
前一章我們介紹了一些使用goroutine和channel這樣直接而自然的方式來實現併發的方法。然而這樣做我們實際上屏蔽掉了在寫併發代碼時必處理的一些重要而且細微的問題。
在本章中我們會細致地了解併發機製。尤其是在多goroutine之間的共享變量併發問題的分析手段以及解決這些問題的基本模式。最後我們會解釋goroutine和操作繫統線程之間的技術上的一些區别。

View File

@ -31,7 +31,7 @@ Go語言聖經 [《The Go Programming Language》](http://gopl.io) 中文版本
在C語言發明之後約5年的時間之後1978年[Brian W. Kernighan](http://www.cs.princeton.edu/~bwk/)和[Dennis M. Ritchie](http://genius.cat-v.org/dennis-ritchie/)合作編寫出版了C語言方面的經典敎材《[The C Programming Language](http://s3-us-west-2.amazonaws.com/belllabs-microsite-dritchie/cbook/index.html)》該書被譽爲C語言程序員的聖經作者也被大家親切地稱爲[K&R](https://en.wikipedia.org/wiki/K%26R)。同樣在Go語言正式發布2009年約5年之後2014年開始寫作2015年出版由Go語言核心糰隊成員[Alan A. A. Donovan](https://github.com/adonovan)和[K&R](https://en.wikipedia.org/wiki/K%26R)中的[Brian W. Kernighan](http://www.cs.princeton.edu/~bwk/)合作編寫了Go語言方面的經典敎材《[The Go Programming Language](http://gopl.io)》。Go語言被譽爲21世紀的C語言如果説[K&R](https://en.wikipedia.org/wiki/K%26R)所著的是聖經的舊約那麽D&K所著的必將成爲聖經的新約。該書介紹了Go語言幾乎全部特性併且隨着語言的深入層層遞進對每個細節都解讀得非常細致每一節內容都精綵不容錯過是廣大Gopher的必讀書目。大部分Go語言核心糰隊的成員都參與了該書校對工作因此該書的質量是可以完全放心的。
同時,單憑閲讀和學習其語法結構併不能眞正地掌握一門編程語言,必進行足夠多的編程實踐——親自編寫一些程序併研究學習别人寫的程序。要從利用Go語言良好的特性使得程序模塊化充分利用Go的標準函數庫以Go語言自己的風格來編寫程序。書中包含了上百個精心挑選的習題希望大家能先用自己的方式嚐試完成習題然後再參考官方給出的解決方案。
同時,單憑閲讀和學習其語法結構併不能眞正地掌握一門編程語言,必進行足夠多的編程實踐——親自編寫一些程序併研究學習别人寫的程序。要從利用Go語言良好的特性使得程序模塊化充分利用Go的標準函數庫以Go語言自己的風格來編寫程序。書中包含了上百個精心挑選的習題希望大家能先用自己的方式嚐試完成習題然後再參考官方給出的解決方案。
該書英文版約從2015年10月開始公開發售其中日文版本最早參與翻譯和審校參考致謝部分。在2015年10月我們併不知道中文版是否會及時引進、將由哪家出版社引進、引進將由何人來翻譯、何時能出版這些信息都成了一個祕密。中国的Go語言社區是全球最大的Go語言社區我們從一開始就始終緊跟着Go語言的發展腳步。我們應該也完全有能力以中国Go語言社區的力量同步完成Go語言聖經中文版的翻譯工作。與此同時国內有很多Go語言愛好者也在積極關註該書本人也在第一時間購買了紙質版本[亞馬遜價格314人民幣](http://www.amazon.cn/The-Go-Programming-Language-Donovan-Alan-A-A/dp/0134190440/)。爲了Go語言的學習和交流大家決定合作免費翻譯該書。

View File

@ -207,6 +207,8 @@ var zh2twMapPatch = map[rune]rune{
'国': '国',
'获': '獲',
'核': '核',
'糖': '糖',
'须': '須',
}
var _TSCharactersMap = map[rune]rune{