mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2024-11-16 11:23:40 +00:00
fmt code
This commit is contained in:
parent
edd55ba1f3
commit
a91355f5f1
@ -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)可能會對你有幫助。
|
||||||
|
@ -18,7 +18,7 @@ pow(x, 3) + pow(y, 3)
|
|||||||
下面的五個具體類型表示了具體的表達式類型。Var類型表示對一個變量的引用。(我們很快會知道爲什麽它可以被輸出。)literal類型表示一個浮點型常量。unary和binary類型表示有一到兩個運算對象的運算符表達式,這些操作數可以是任意的Expr類型。call類型表示對一個函數的調用;我們限製它的fn字段隻能是pow,sin或者sqrt。
|
下面的五個具體類型表示了具體的表達式類型。Var類型表示對一個變量的引用。(我們很快會知道爲什麽它可以被輸出。)literal類型表示一個浮點型常量。unary和binary類型表示有一到兩個運算對象的運算符表達式,這些操作數可以是任意的Expr類型。call類型表示對一個函數的調用;我們限製它的fn字段隻能是pow,sin或者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有相同的籤名,但是它計算一個用戶提供的表達式。環境變量中定義了x,y和半徑r。最後plot調用surface函數,它就是gopl.io/ch3/surface中的主要函數,脩改後它可以接受plot中的函數和輸出io.Writer作爲參數,而不是使用固定的函數f和os.Stdout。圖7.7中顯示了通過程序産生的3個麴面。
|
這個plot函數解析和檢査在HTTP請求中指定的表達式併且用它來創建一個兩個變量的匿名函數。這個匿名函數和來自原來surface-plotting程序中的固定函數f有相同的籤名,但是它計算一個用戶提供的表達式。環境變量中定義了x,y和半徑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的計算器程序。
|
||||||
|
@ -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...
|
||||||
|
Loading…
Reference in New Issue
Block a user