This commit is contained in:
chai2010 2016-01-18 10:52:21 +08:00
parent edd55ba1f3
commit a91355f5f1
4 changed files with 210 additions and 198 deletions

View File

@ -3,7 +3,7 @@
在第一章中我們粗略的了解了怎麽用net/http包去實現網絡客戶端(§1.5)和服務器(§1.7)。在這個小節中我們會對那些基於http.Handler接口的服務器API做更進一步的學習 在第一章中我們粗略的了解了怎麽用net/http包去實現網絡客戶端(§1.5)和服務器(§1.7)。在這個小節中我們會對那些基於http.Handler接口的服務器API做更進一步的學習
```go ```go
// net/http net/http
package http package http
type Handler interface { type Handler interface {
@ -18,7 +18,7 @@ ListenAndServe函數需要一個例如“localhost:8000”的服務器地址
想象一個電子商務網站爲了銷售它的數據庫將它物品的價格映射成美元。下面這個程序可能是能想到的最簡單的實現了。它將庫存清單模型化爲一個命名爲database的map類型我們給這個類型一個ServeHttp方法這樣它可以滿足http.Handler接口。這個handler會遍歷整個map併輸出物品信息。 想象一個電子商務網站爲了銷售它的數據庫將它物品的價格映射成美元。下面這個程序可能是能想到的最簡單的實現了。它將庫存清單模型化爲一個命名爲database的map類型我們給這個類型一個ServeHttp方法這樣它可以滿足http.Handler接口。這個handler會遍歷整個map併輸出物品信息。
```go ```go
// gopl.io/ch7/http1 gopl.io/ch7/http1
func main() { func main() {
db := database{"shoes": 50, "socks": 5} db := database{"shoes": 50, "socks": 5}
log.Fatal(http.ListenAndServe("localhost:8000", db)) log.Fatal(http.ListenAndServe("localhost:8000", db))
@ -56,7 +56,7 @@ socks: $5.00
目前爲止這個服務器不考慮URL隻能爲每個請求列出它全部的庫存清單。更眞實的服務器會定義多個不同的URL每一個都會觸發一個不同的行爲。讓我們使用/list來調用已經存在的這個行爲併且增加另一個/price調用表明單個貨品的價格像這樣/price?item=socks來指定一個請求參數。 目前爲止這個服務器不考慮URL隻能爲每個請求列出它全部的庫存清單。更眞實的服務器會定義多個不同的URL每一個都會觸發一個不同的行爲。讓我們使用/list來調用已經存在的這個行爲併且增加另一個/price調用表明單個貨品的價格像這樣/price?item=socks來指定一個請求參數。
```go ```go
// gopl.io/ch7/http2 gopl.io/ch7/http2
func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) {
switch req.URL.Path { switch req.URL.Path {
case "/list": case "/list":
@ -115,7 +115,7 @@ no such page: /help
在下面的程序中我們創建一個ServeMux併且使用它將URL和相應處理/list和/price操作的handler聯繫起來這些操作邏輯都已經被分到不同的方法中。然後我門在調用ListenAndServe函數中使用ServeMux最爲主要的handler。 在下面的程序中我們創建一個ServeMux併且使用它將URL和相應處理/list和/price操作的handler聯繫起來這些操作邏輯都已經被分到不同的方法中。然後我門在調用ListenAndServe函數中使用ServeMux最爲主要的handler。
```go ```go
// gopl.io/ch7/http3 gopl.io/ch7/http3
func main() { func main() {
db := database{"shoes": 50, "socks": 5} db := database{"shoes": 50, "socks": 5}
mux := http.NewServeMux() mux := http.NewServeMux()
@ -155,7 +155,7 @@ func(w http.ResponseWriter, req *http.Request)
語句http.HandlerFunc(db.list)是一個轉換而非一個函數調用因爲http.HandlerFunc是一個類型。它有如下的定義 語句http.HandlerFunc(db.list)是一個轉換而非一個函數調用因爲http.HandlerFunc是一個類型。它有如下的定義
```go ```go
// net/http net/http
package http package http
type HandlerFunc func(w ResponseWriter, r *Request) type HandlerFunc func(w ResponseWriter, r *Request)
@ -170,7 +170,7 @@ HandlerFunc顯示了在Go語言接口機製中一些不同尋常的特點。這
因爲handler通過這種方式註冊非常普遍ServeMux有一個方便的HandleFunc方法它幫我們簡化handler註冊代碼成這樣 因爲handler通過這種方式註冊非常普遍ServeMux有一個方便的HandleFunc方法它幫我們簡化handler註冊代碼成這樣
```go ```go
// gopl.io/ch7/http3a gopl.io/ch7/http3a
mux.HandleFunc("/list", db.list) mux.HandleFunc("/list", db.list)
mux.HandleFunc("/price", db.price) mux.HandleFunc("/price", db.price)
``` ```
@ -182,7 +182,7 @@ mux.HandleFunc("/price", db.price)
然後服務器的主函數可以簡化成: 然後服務器的主函數可以簡化成:
```go ```go
// gopl.io/ch7/http4 gopl.io/ch7/http4
func main() { func main() {
db := database{"shoes": 50, "socks": 5} db := database{"shoes": 50, "socks": 5}
http.HandleFunc("/list", db.list) http.HandleFunc("/list", db.list)
@ -193,6 +193,6 @@ func main() {
最後一個重要的提示就像我們在1.7節中提到的web服務器在一個新的協程中調用每一個handler所以當handler獲取其它協程或者這個handler本身的其它請求也可以訪問的變量時一定要使用預防措施比如鎖機製。我們後面的兩章中講到併發相關的知識。 最後一個重要的提示就像我們在1.7節中提到的web服務器在一個新的協程中調用每一個handler所以當handler獲取其它協程或者這個handler本身的其它請求也可以訪問的變量時一定要使用預防措施比如鎖機製。我們後面的兩章中講到併發相關的知識。
練習 7.11增加額外的handler讓客服端可以創建讀取更新和刪除數據庫記録。例如一個形如/update?item=socks&price=6的請求會更新庫存清單里一個貨品的價格併且當這個貨品不存在或價格無效時返迴一個錯誤值。註意這個脩改會引入變量同時更新的問題 **練習 7.11** 增加額外的handler讓客服端可以創建讀取更新和刪除數據庫記録。例如一個形如/update?item=socks&price=6的請求會更新庫存清單里一個貨品的價格併且當這個貨品不存在或價格無效時返迴一個錯誤值。註意這個脩改會引入變量同時更新的問題
練習 7.12:脩改/list的handler讓它把輸出打印成一個HTML的表格而不是文本。html/template包(§4.6)可能會對你有幫助。 **練習 7.12** 脩改/list的handler讓它把輸出打印成一個HTML的表格而不是文本。html/template包(§4.6)可能會對你有幫助。

View File

@ -18,7 +18,7 @@ pow(x, 3) + pow(y, 3)
下面的五個具體類型表示了具體的表達式類型。Var類型表示對一個變量的引用。我們很快會知道爲什麽它可以被輸出。literal類型表示一個浮點型常量。unary和binary類型表示有一到兩個運算對象的運算符表達式這些操作數可以是任意的Expr類型。call類型表示對一個函數的調用我們限製它的fn字段隻能是powsin或者sqrt。 下面的五個具體類型表示了具體的表達式類型。Var類型表示對一個變量的引用。我們很快會知道爲什麽它可以被輸出。literal類型表示一個浮點型常量。unary和binary類型表示有一到兩個運算對象的運算符表達式這些操作數可以是任意的Expr類型。call類型表示對一個函數的調用我們限製它的fn字段隻能是powsin或者sqrt。
```go ```go
// gopl.io/ch7/eval gopl.io/ch7/eval
// A Var identifies a variable, e.g., x. // A Var identifies a variable, e.g., x.
type Var string type Var string
@ -255,7 +255,7 @@ Check方法的參數是一個Var類型的集合這個集合聚集從表達式
這個ParseAndCheck函數混合了解析和檢査步驟的過程 這個ParseAndCheck函數混合了解析和檢査步驟的過程
```go ```go
// gopl.io/ch7/surface gopl.io/ch7/surface
import "gopl.io/ch7/eval" import "gopl.io/ch7/eval"
func parseAndCheck(s string) (eval.Expr, error) { func parseAndCheck(s string) (eval.Expr, error) {
@ -301,10 +301,10 @@ func plot(w http.ResponseWriter, r *http.Request) {
這個plot函數解析和檢査在HTTP請求中指定的表達式併且用它來創建一個兩個變量的匿名函數。這個匿名函數和來自原來surface-plotting程序中的固定函數f有相同的籤名但是它計算一個用戶提供的表達式。環境變量中定義了xy和半徑r。最後plot調用surface函數它就是gopl.io/ch3/surface中的主要函數脩改後它可以接受plot中的函數和輸出io.Writer作爲參數而不是使用固定的函數f和os.Stdout。圖7.7中顯示了通過程序産生的3個麴面。 這個plot函數解析和檢査在HTTP請求中指定的表達式併且用它來創建一個兩個變量的匿名函數。這個匿名函數和來自原來surface-plotting程序中的固定函數f有相同的籤名但是它計算一個用戶提供的表達式。環境變量中定義了xy和半徑r。最後plot調用surface函數它就是gopl.io/ch3/surface中的主要函數脩改後它可以接受plot中的函數和輸出io.Writer作爲參數而不是使用固定的函數f和os.Stdout。圖7.7中顯示了通過程序産生的3個麴面。
練習7.13爲Expr增加一個String方法來打印美觀的語法樹。當再一次解析的時候檢査它的結果是否生成相同的語法樹。 **練習 7.13** 爲Expr增加一個String方法來打印美觀的語法樹。當再一次解析的時候檢査它的結果是否生成相同的語法樹。
練習7.14定義一個新的滿足Expr接口的具體類型併且提供一個新的操作例如對它運算單元中的最小值的計算。因爲Parse函數不會創建這個新類型的實例爲了使用它你可能需要直接構造一個語法樹或者繼承parser接口 **練習 7.14** 定義一個新的滿足Expr接口的具體類型併且提供一個新的操作例如對它運算單元中的最小值的計算。因爲Parse函數不會創建這個新類型的實例爲了使用它你可能需要直接構造一個語法樹或者繼承parser接口
練習7.15:編寫一個從標準輸入中讀取一個單一表達式的程序,用戶及時地提供對於任意變量的值,然後在結果環境變量中計算表達式的值。優雅的處理所有遇到的錯誤。 **練習 7.15** 編寫一個從標準輸入中讀取一個單一表達式的程序,用戶及時地提供對於任意變量的值,然後在結果環境變量中計算表達式的值。優雅的處理所有遇到的錯誤。
練習7.16編寫一個基於web的計算器程序。 **練習 7.16** 編寫一個基於web的計算器程序。

View File

@ -1,16 +1,20 @@
## 7.10. 類型斷言 ## 7.10. 類型斷言
類型斷言是一個使用在接口值上的操作。語法上它看起來像x.(T)被稱爲斷言類型這里x表示一個接口的類型和T表示一個類型。一個類型斷言檢査它操作對象的動態類型是否和斷言的類型匹配。 類型斷言是一個使用在接口值上的操作。語法上它看起來像x.(T)被稱爲斷言類型這里x表示一個接口的類型和T表示一個類型。一個類型斷言檢査它操作對象的動態類型是否和斷言的類型匹配。
這里有兩種可能。第一種如果斷言的類型T是一個具體類型然後類型斷言檢査x的動態類型是否和T相同。如果這個檢査成功了類型斷言的結果是x的動態值當然它的類型是T。換句話説具體類型的類型斷言從它的操作對象中獲得具體的值。如果檢査失敗接下來這個操作會拋出panic。例如 這里有兩種可能。第一種如果斷言的類型T是一個具體類型然後類型斷言檢査x的動態類型是否和T相同。如果這個檢査成功了類型斷言的結果是x的動態值當然它的類型是T。換句話説具體類型的類型斷言從它的操作對象中獲得具體的值。如果檢査失敗接下來這個操作會拋出panic。例如
```go ```go
var w io.Writer var w io.Writer
w = os.Stdout w = os.Stdout
f := w.(*os.File) // success: f == os.Stdout f := w.(*os.File) // success: f == os.Stdout
c := w.(*bytes.Buffer) // panic: interface holds *os.File, not *bytes.Buffer c := w.(*bytes.Buffer) // panic: interface holds *os.File, not *bytes.Buffer
``` ```
第二種如果相反斷言的類型T是一個接口類型然後類型斷言檢査是否x的動態類型滿足T。如果這個檢査成功了動態值沒有獲取到這個結果仍然是一個有相同類型和值部分的接口值但是結果有類型T。換句話説對一個接口類型的類型斷言改變了類型的表述方式改變了可以獲取的方法集合通常更大但是它保護了接口值內部的動態類型和值的部分。 第二種如果相反斷言的類型T是一個接口類型然後類型斷言檢査是否x的動態類型滿足T。如果這個檢査成功了動態值沒有獲取到這個結果仍然是一個有相同類型和值部分的接口值但是結果有類型T。換句話説對一個接口類型的類型斷言改變了類型的表述方式改變了可以獲取的方法集合通常更大但是它保護了接口值內部的動態類型和值的部分。
在下面的第一個類型斷言後w和rw都持有os.Stdout因此它們每個有一個動態類型*os.File但是變量w是一個io.Writer類型隻對外公開出文件的Write方法然而rw變量也隻公開它的Read方法。 在下面的第一個類型斷言後w和rw都持有os.Stdout因此它們每個有一個動態類型*os.File但是變量w是一個io.Writer類型隻對外公開出文件的Write方法然而rw變量也隻公開它的Read方法。
```go ```go
var w io.Writer var w io.Writer
w = os.Stdout w = os.Stdout
@ -18,26 +22,34 @@ rw := w.(io.ReadWriter) // success: *os.File has both Read and Write
w = new(ByteCounter) w = new(ByteCounter)
rw = w.(io.ReadWriter) // panic: *ByteCounter has no Read method rw = w.(io.ReadWriter) // panic: *ByteCounter has no Read method
``` ```
如果斷言操作的對象是一個nil接口值那麽不論被斷言的類型是什麽這個類型斷言都會失敗。我們幾乎不需要對一個更少限製性的接口類型更少的方法集合做斷言因爲它表現的就像賦值操作一樣除了對於nil接口值的情況。 如果斷言操作的對象是一個nil接口值那麽不論被斷言的類型是什麽這個類型斷言都會失敗。我們幾乎不需要對一個更少限製性的接口類型更少的方法集合做斷言因爲它表現的就像賦值操作一樣除了對於nil接口值的情況。
```go ```go
w = rw // io.ReadWriter is assignable to io.Writer w = rw // io.ReadWriter is assignable to io.Writer
w = rw.(io.Writer) // fails only if rw == nil w = rw.(io.Writer) // fails only if rw == nil
``` ```
經常地我們對一個接口值的動態類型是不確定的併且我們更願意去檢驗它是否是一些特定的類型。如果類型斷言出現在一個預期有兩個結果的賦值操作中例如如下的定義這個操作不會在失敗的時候發生panic但是代替地返迴一個額外的第二個結果這個結果是一個標識成功的布爾值 經常地我們對一個接口值的動態類型是不確定的併且我們更願意去檢驗它是否是一些特定的類型。如果類型斷言出現在一個預期有兩個結果的賦值操作中例如如下的定義這個操作不會在失敗的時候發生panic但是代替地返迴一個額外的第二個結果這個結果是一個標識成功的布爾值
```go ```go
var w io.Writer = os.Stdout var w io.Writer = os.Stdout
f, ok := w.(*os.File) // success: ok, f == os.Stdout f, ok := w.(*os.File) // success: ok, f == os.Stdout
b, ok := w.(*bytes.Buffer) // failure: !ok, b == nil b, ok := w.(*bytes.Buffer) // failure: !ok, b == nil
``` ```
第二個結果常規地賦值給一個命名爲ok的變量。如果這個操作失敗了那麽ok就是false值第一個結果等於被斷言類型的零值在這個例子中就是一個nil的*bytes.Buffer類型。 第二個結果常規地賦值給一個命名爲ok的變量。如果這個操作失敗了那麽ok就是false值第一個結果等於被斷言類型的零值在這個例子中就是一個nil的*bytes.Buffer類型。
這個ok結果經常立卽用於決定程序下面做什麽。if語句的擴展格式讓這個變的很簡潔 這個ok結果經常立卽用於決定程序下面做什麽。if語句的擴展格式讓這個變的很簡潔
```go ```go
if f, ok := w.(*os.File); ok { if f, ok := w.(*os.File); ok {
// ...use f... // ...use f...
} }
``` ```
當類型斷言的操作對象是一個變量,你有時會看見原來的變量名重用而不是聲明一個新的本地變量,這個重用的變量會覆蓋原來的值,如下面這樣: 當類型斷言的操作對象是一個變量,你有時會看見原來的變量名重用而不是聲明一個新的本地變量,這個重用的變量會覆蓋原來的值,如下面這樣:
```go ```go
if w, ok := w.(*os.File); ok { if w, ok := w.(*os.File); ok {
// ...use w... // ...use w...