diff --git a/ch7/ch7-01.md b/ch7/ch7-01.md index 4407943..d437e6c 100644 --- a/ch7/ch7-01.md +++ b/ch7/ch7-01.md @@ -8,6 +8,7 @@ ``` go package fmt + func Fprintf(w io.Writer, format string, args ...interface{}) (int, error) func Printf(format string, args ...interface{}) (int, error) { return Fprintf(os.Stdout, format, args...) @@ -26,6 +27,7 @@ Fprintf的前綴F表示文件(File)也表明格式化輸出結果應該被寫入 ``` go package io + // Writer is the interface that wraps the basic Write method. type Writer interface { // Write writes len(p) bytes from p to the underlying data stream. @@ -45,21 +47,23 @@ io.Writer類型定義了函數Fprintf和這個函數調用者之間的約定。 讓我們通過一個新的類型來進行校驗,下面\*ByteCounter類型里的Write方法,僅僅在丟失寫向它的字節前統計它們的長度。(在這個+=賦值語句中,讓len(p)的類型和\*c的類型匹配的轉換是必須的。) +gopl.io/ch7/bytecounter ```go -// gopl.io/ch7/bytecounter type ByteCounter int + func (c *ByteCounter) Write(p []byte) (int, error) { - *c += ByteCounter(len(p)) // convert int to ByteCounter - return len(p), nil + *c += ByteCounter(len(p)) // convert int to ByteCounter + return len(p), nil } ``` + 因爲*ByteCounter滿足io.Writer的約定,我們可以把它傳入Fprintf函數中;Fprintf函數執行字符串格式化的過程不會去關註ByteCounter正確的纍加結果的長度。 ```go var c ByteCounter c.Write([]byte("hello")) fmt.Println(c) // "5", = len("hello") -c = 0 // reset the counter +c = 0 // reset the counter var name = "Dolly" fmt.Fprintf(&c, "hello, %s", name) fmt.Println(c) // "12", = len("hello, Dolly") @@ -69,11 +73,12 @@ fmt.Println(c) // "12", = len("hello, Dolly") ```go package fmt + // The String method is used to print values passed // as an operand to any format that accepts a string // or to an unformatted printer such as Print. type Stringer interface { - String() string + String() string } ``` diff --git a/ch7/ch7-02.md b/ch7/ch7-02.md index f28fe79..e3b14b8 100644 --- a/ch7/ch7-02.md +++ b/ch7/ch7-02.md @@ -1,4 +1,5 @@ ## 7.2. 接口類型 + 接口類型具體描述了一繫列方法的集合,一個實現了這些方法的具體類型是這個接口類型的實例。 io.Writer類型是用的最廣泛的接口之一,因爲它提供了所有的類型寫入bytes的抽象,包括文件類型,內存緩衝區,網絡鏈接,HTTP客戶端,壓縮工具,哈希等等。io包中定義了很多其它有用的接口類型。Reader可以代表任意可以讀取bytes的類型,Closer可以是任意可以關閉的值,例如一個文件或是網絡鏈接。(到現在你可能註意到了很多Go語言中單方法接口的命名習慣) @@ -6,10 +7,10 @@ io.Writer類型是用的最廣泛的接口之一,因爲它提供了所有的 ```go package io type Reader interface { - Read(p []byte) (n int, err error) + Read(p []byte) (n int, err error) } type Closer interface { - Close() error + Close() error } ``` @@ -17,21 +18,21 @@ type Closer interface { ```go type ReadWriter interface { - Reader - Writer + Reader + Writer } type ReadWriteCloser interface { - Reader - Writer - Closer + Reader + Writer + Closer } ``` 上面用到的語法和結構內嵌相似,我們可以用這種方式以一個簡寫命名另一個接口,而不用聲明它所有的方法。這種方式本稱爲接口內嵌。盡管略失簡潔,我們可以像下面這樣,不使用內嵌來聲明io.Writer接口。 ```go type ReadWriter interface { - Read(p []byte) (n int, err error) - Write(p []byte) (n int, err error) + Read(p []byte) (n int, err error) + Write(p []byte) (n int, err error) } ``` @@ -39,8 +40,8 @@ type ReadWriter interface { ```go type ReadWriter interface { - Read(p []byte) (n int, err error) - Writer + Read(p []byte) (n int, err error) + Writer } ``` diff --git a/ch7/ch7-03.md b/ch7/ch7-03.md index fa39259..4128d8d 100644 --- a/ch7/ch7-03.md +++ b/ch7/ch7-03.md @@ -1,4 +1,5 @@ ## 7.3. 實現接口的條件 + 一個類型如果擁有一個接口需要的所有方法,那麽這個類型就實現了這個接口。例如,\*os.File類型實現了io.Reader,Writer,Closer,和ReadWriter接口。\*bytes.Buffer實現了Reader,Writer,和ReadWriter這些接口,但是它沒有實現Closer接口因爲它不具有Close方法。Go的程序員經常會簡要的把一個具體的類型描述成一個特定的接口類型。舉個例子,\*bytes.Buffer是io.Writer;\*os.Files是io.ReadWriter。 接口指定的規則非常簡單:表達一個類型屬於某個接口隻要這個類型實現這個接口。所以: @@ -112,29 +113,29 @@ Track ```go type Artifact interface { - Title() string - Creators() []string - Created() time.Time + Title() string + Creators() []string + Created() time.Time } ``` 其它的一些特性隻對特定類型的文化産品才有。和文字排版特性相關的隻有books和magazines,還有隻有movies和TV劇集和屏幕分辨率相關。 ```go type Text interface { - Pages() int - Words() int - PageSize() int + Pages() int + Words() int + PageSize() int } type Audio interface { - Stream() (io.ReadCloser, error) - RunningTime() time.Duration - Format() string // e.g., "MP3", "WAV" + Stream() (io.ReadCloser, error) + RunningTime() time.Duration + Format() string // e.g., "MP3", "WAV" } type Video interface { - Stream() (io.ReadCloser, error) - RunningTime() time.Duration - Format() string // e.g., "MP4", "WMV" - Resolution() (x, y int) + Stream() (io.ReadCloser, error) + RunningTime() time.Duration + Format() string // e.g., "MP4", "WMV" + Resolution() (x, y int) } ``` @@ -142,9 +143,9 @@ type Video interface { ```go type Streamer interface { - Stream() (io.ReadCloser, error) - RunningTime() time.Duration - Format() string + Stream() (io.ReadCloser, error) + RunningTime() time.Duration + Format() string } ``` diff --git a/ch7/ch7-04.md b/ch7/ch7-04.md index 097b8d1..05f8cd7 100644 --- a/ch7/ch7-04.md +++ b/ch7/ch7-04.md @@ -2,8 +2,8 @@ 在本章,我們會學到另一個標準的接口類型flag.Value是怎麽幫助命令行標記定義新的符號的。思考下面這個會休眠特定時間的程序: +gopl.io/ch7/sleep ```go -// gopl.io/ch7/sleep var period = flag.Duration("period", 1*time.Second, "sleep period") func main() { @@ -51,8 +51,8 @@ String方法格式化標記的值用在命令行幫組消息中;這樣每一 讓我們定義一個允許通過攝氏度或者華氏溫度變換的形式指定溫度的celsiusFlag類型。註意celsiusFlag內嵌了一個Celsius類型(§2.5),因此不用實現本身就已經有String方法了。爲了實現flag.Value,我們隻需要定義Set方法: +gopl.io/ch7/tempconv ```go -// gopl.io/ch7/tempconv // *celsiusFlag satisfies the flag.Value interface. type celsiusFlag struct{ Celsius } @@ -89,8 +89,8 @@ func CelsiusFlag(name string, value Celsius, usage string) *Celsius { 現在我們可以開始在我們的程序中使用新的標記: +gopl.io/ch7/tempflag ```go -// gopl.io/ch7/tempflag var temp = tempconv.CelsiusFlag("temp", 20.0, "the temperature") func main() { diff --git a/ch7/ch7-05-1.md b/ch7/ch7-05-1.md index b1e1f0f..a18cd25 100644 --- a/ch7/ch7-05-1.md +++ b/ch7/ch7-05-1.md @@ -8,22 +8,22 @@ const debug = true func main() { - var buf *bytes.Buffer - if debug { - buf = new(bytes.Buffer) // enable collection of output - } - f(buf) // NOTE: subtly incorrect! - if debug { - // ...use buf... - } + var buf *bytes.Buffer + if debug { + buf = new(bytes.Buffer) // enable collection of output + } + f(buf) // NOTE: subtly incorrect! + if debug { + // ...use buf... + } } // If out is non-nil, output will be written to it. func f(out io.Writer) { - // ...do something... - if out != nil { - out.Write([]byte("done!\n")) - } + // ...do something... + if out != nil { + out.Write([]byte("done!\n")) + } } ``` @@ -31,7 +31,7 @@ func f(out io.Writer) { ```go if out != nil { - out.Write([]byte("done!\n")) // panic: nil pointer dereference + out.Write([]byte("done!\n")) // panic: nil pointer dereference } ``` @@ -46,7 +46,7 @@ if out != nil { ```go var buf io.Writer if debug { - buf = new(bytes.Buffer) // enable collection of output + buf = new(bytes.Buffer) // enable collection of output } f(buf) // OK ``` diff --git a/ch7/ch7-06.md b/ch7/ch7-06.md index 2d61d90..9e60bde 100644 --- a/ch7/ch7-06.md +++ b/ch7/ch7-06.md @@ -29,6 +29,7 @@ func (p StringSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } ```go sort.Sort(StringSlice(names)) ``` + 這個轉換得到一個相同長度,容量,和基於names數組的切片值;併且這個切片值的類型有三個排序需要的方法。 對字符串切片的排序是很常用的需要,所以sort包提供了StringSlice類型,也提供了Strings函數能讓上面這些調用簡化成sort.Strings(names)。 @@ -39,8 +40,8 @@ sort.Sort(StringSlice(names)) 下面的變量tracks包好了一個播放列表。(One of the authors apologizes for the other author’s musical tastes.)每個元素都不是Track本身而是指向它的指針。盡管我們在下面的代碼中直接存儲Tracks也可以工作,sort函數會交換很多對元素,所以如果每個元素都是指針會更快而不是全部Track類型,指針是一個機器字碼長度而Track類型可能是八個或更多。 +gopl.io/ch7/sorting ```go -//gopl.io/ch7/sorting type Track struct { Title string Artist string @@ -210,6 +211,7 @@ sort.Sort(sort.Reverse(sort.IntSlice(values))) fmt.Println(values) // "[4 3 1 1]" fmt.Println(sort.IntsAreSorted(values)) // "false" ``` + 爲了使用方便,sort包爲[]int,[]string和[]float64的正常排序提供了特定版本的函數和類型。對於其他類型,例如[]int64或者[]uint,盡管路徑也很簡單,還是依賴我們自己實現。 **練習 7.8:** 很多圖形界面提供了一個有狀態的多重排序表格插件:主要的排序鍵是最近一次點擊過列頭的列,第二個排序鍵是第二最近點擊過列頭的列,等等。定義一個sort.Interface的實現用在這樣的表格中。比較這個實現方式和重複使用sort.Stable來排序的方式。 diff --git a/ch7/ch7-07.md b/ch7/ch7-07.md index e37058b..2247a9b 100644 --- a/ch7/ch7-07.md +++ b/ch7/ch7-07.md @@ -2,8 +2,8 @@ 在第一章中,我們粗略的了解了怎麽用net/http包去實現網絡客戶端(§1.5)和服務器(§1.7)。在這個小節中,我們會對那些基於http.Handler接口的服務器API做更進一步的學習: +net/http ```go -net/http package http type Handler interface { @@ -17,8 +17,8 @@ ListenAndServe函數需要一個例如“localhost:8000”的服務器地址, 想象一個電子商務網站,爲了銷售它的數據庫將它物品的價格映射成美元。下面這個程序可能是能想到的最簡單的實現了。它將庫存清單模型化爲一個命名爲database的map類型,我們給這個類型一個ServeHttp方法,這樣它可以滿足http.Handler接口。這個handler會遍歷整個map併輸出物品信息。 +gopl.io/ch7/http1 ```go -gopl.io/ch7/http1 func main() { db := database{"shoes": 50, "socks": 5} log.Fatal(http.ListenAndServe("localhost:8000", db)) @@ -55,8 +55,8 @@ socks: $5.00 目前爲止,這個服務器不考慮URL隻能爲每個請求列出它全部的庫存清單。更眞實的服務器會定義多個不同的URL,每一個都會觸發一個不同的行爲。讓我們使用/list來調用已經存在的這個行爲併且增加另一個/price調用表明單個貨品的價格,像這樣/price?item=socks來指定一個請求參數。 +gopl.io/ch7/http2 ```go -gopl.io/ch7/http2 func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) { switch req.URL.Path { case "/list": @@ -114,8 +114,8 @@ no such page: /help 在下面的程序中,我們創建一個ServeMux併且使用它將URL和相應處理/list和/price操作的handler聯繫起來,這些操作邏輯都已經被分到不同的方法中。然後我門在調用ListenAndServe函數中使用ServeMux最爲主要的handler。 +gopl.io/ch7/http3 ```go -gopl.io/ch7/http3 func main() { db := database{"shoes": 50, "socks": 5} mux := http.NewServeMux() @@ -154,8 +154,8 @@ func(w http.ResponseWriter, req *http.Request) 語句http.HandlerFunc(db.list)是一個轉換而非一個函數調用,因爲http.HandlerFunc是一個類型。它有如下的定義: +net/http ```go -net/http package http type HandlerFunc func(w ResponseWriter, r *Request) @@ -169,8 +169,8 @@ HandlerFunc顯示了在Go語言接口機製中一些不同尋常的特點。這 因爲handler通過這種方式註冊非常普遍,ServeMux有一個方便的HandleFunc方法,它幫我們簡化handler註冊代碼成這樣: +gopl.io/ch7/http3a ```go -gopl.io/ch7/http3a mux.HandleFunc("/list", db.list) mux.HandleFunc("/price", db.price) ``` @@ -181,8 +181,8 @@ mux.HandleFunc("/price", db.price) 然後服務器的主函數可以簡化成: +gopl.io/ch7/http4 ```go -gopl.io/ch7/http4 func main() { db := database{"shoes": 50, "socks": 5} http.HandleFunc("/list", db.list) diff --git a/ch7/ch7-08.md b/ch7/ch7-08.md index 99772d8..a2da37c 100644 --- a/ch7/ch7-08.md +++ b/ch7/ch7-08.md @@ -34,7 +34,7 @@ package fmt import "errors" func Errorf(format string, args ...interface{}) error { - return errors.New(Sprintf(format, args...)) + return errors.New(Sprintf(format, args...)) } ``` diff --git a/ch7/ch7-09.md b/ch7/ch7-09.md index 19509e3..fa0000e 100644 --- a/ch7/ch7-09.md +++ b/ch7/ch7-09.md @@ -17,8 +17,8 @@ pow(x, 3) + pow(y, 3) 下面的五個具體類型表示了具體的表達式類型。Var類型表示對一個變量的引用。(我們很快會知道爲什麽它可以被輸出。)literal類型表示一個浮點型常量。unary和binary類型表示有一到兩個運算對象的運算符表達式,這些操作數可以是任意的Expr類型。call類型表示對一個函數的調用;我們限製它的fn字段隻能是pow,sin或者sqrt。 +gopl.io/ch7/eval ```go -gopl.io/ch7/eval // A Var identifies a variable, e.g., x. type Var string @@ -254,8 +254,8 @@ Check方法的參數是一個Var類型的集合,這個集合聚集從表達式 這個ParseAndCheck函數混合了解析和檢査步驟的過程: +gopl.io/ch7/surface ```go -gopl.io/ch7/surface import "gopl.io/ch7/eval" func parseAndCheck(s string) (eval.Expr, error) { diff --git a/ch7/ch7-11.md b/ch7/ch7-11.md index a5c5832..5c53460 100644 --- a/ch7/ch7-11.md +++ b/ch7/ch7-11.md @@ -1,5 +1,7 @@ ## 7.11. 基於類型斷言區别錯誤類型 + 思考在os包中文件操作返迴的錯誤集合。I/O可以因爲任何數量的原因失敗,但是有三種經常的錯誤必須進行不同的處理:文件已經存在(對於創建操作),找不到文件(對於讀取操作),和權限拒絶。os包中提供了這三個幫助函數來對給定的錯誤值表示的失敗進行分類: + ```go package os @@ -7,31 +9,37 @@ func IsExist(err error) bool func IsNotExist(err error) bool func IsPermission(err error) bool ``` + 對這些判斷的一個缺乏經驗的實現可能會去檢査錯誤消息是否包含了特定的子字符串, + ```go func IsNotExist(err error) bool { - // NOTE: not robust! - return strings.Contains(err.Error(), "file does not exist") + // NOTE: not robust! + return strings.Contains(err.Error(), "file does not exist") } ``` + 但是處理I/O錯誤的邏輯可能一個和另一個平台非常的不同,所以這種方案併不健壯併且對相同的失敗可能會報出各種不同的錯誤消息。在測試的過程中,通過檢査錯誤消息的子字符串來保證特定的函數以期望的方式失敗是非常有用的,但對於線上的代碼是不夠的。 一個更可靠的方式是使用一個專門的類型來描述結構化的錯誤。os包中定義了一個PathError類型來描述在文件路徑操作中涉及到的失敗,像Open或者Delete操作,併且定義了一個叫LinkError的變體來描述涉及到兩個文件路徑的操作,像Symlink和Rename。這下面是os.PathError: + ```go package os // PathError records an error and the operation and file path that caused it. type PathError struct { - Op string - Path string - Err error + Op string + Path string + Err error } func (e *PathError) Error() string { - return e.Op + " " + e.Path + ": " + e.Err.Error() + return e.Op + " " + e.Path + ": " + e.Err.Error() } ``` + 大多數調用方都不知道PathError併且通過調用錯誤本身的Error方法來統一處理所有的錯誤。盡管PathError的Error方法簡單地把這些字段連接起來生成錯誤消息,PathError的結構保護了內部的錯誤組件。調用方需要使用類型斷言來檢測錯誤的具體類型以便將一種失敗和另一種區分開;具體的類型比字符串可以提供更多的細節。 + ```go _, err := os.Open("/no/such/file") fmt.Println(err) // "open /no/such/file: No such file or directory" @@ -39,11 +47,13 @@ fmt.Printf("%#v\n", err) // Output: // &os.PathError{Op:"open", Path:"/no/such/file", Err:0x2} ``` + 這就是三個幫助函數是怎麽工作的。例如下面展示的IsNotExist,它會報出是否一個錯誤和syscall.ENOENT(§7.8)或者和有名的錯誤os.ErrNotExist相等(可以在§5.4.2中找到io.EOF);或者是一個*PathError,它內部的錯誤是syscall.ENOENT和os.ErrNotExist其中之一。 + ```go import ( - "errors" - "syscall" + "errors" + "syscall" ) var ErrNotExist = errors.New("file does not exist") @@ -52,15 +62,18 @@ var ErrNotExist = errors.New("file does not exist") // report that a file or directory does not exist. It is satisfied by // ErrNotExist as well as some syscall errors. func IsNotExist(err error) bool { - if pe, ok := err.(*PathError); ok { - err = pe.Err - } - return err == syscall.ENOENT || err == ErrNotExist + if pe, ok := err.(*PathError); ok { + err = pe.Err + } + return err == syscall.ENOENT || err == ErrNotExist } ``` + 下面這里是它的實際使用: + ```go _, err := os.Open("/no/such/file") fmt.Println(os.IsNotExist(err)) // "true" ``` + 如果錯誤消息結合成一個更大的字符串,當然PathError的結構就不再爲人所知,例如通過一個對fmt.Errorf函數的調用。區别錯誤通常必須在失敗操作後,錯誤傳迴調用者前進行。 diff --git a/ch7/ch7-12.md b/ch7/ch7-12.md index 622dc42..c1483b8 100644 --- a/ch7/ch7-12.md +++ b/ch7/ch7-12.md @@ -4,13 +4,13 @@ ```go func writeHeader(w io.Writer, contentType string) error { - if _, err := w.Write([]byte("Content-Type: ")); err != nil { - return err - } - if _, err := w.Write([]byte(contentType)); err != nil { - return err - } - // ... + if _, err := w.Write([]byte("Content-Type: ")); err != nil { + return err + } + if _, err := w.Write([]byte(contentType)); err != nil { + return err + } + // ... } ``` @@ -24,23 +24,23 @@ func writeHeader(w io.Writer, contentType string) error { // writeString writes s to w. // If w has a WriteString method, it is invoked instead of w.Write. func writeString(w io.Writer, s string) (n int, err error) { - type stringWriter interface { - WriteString(string) (n int, err error) - } - if sw, ok := w.(stringWriter); ok { - return sw.WriteString(s) // avoid a copy - } - return w.Write([]byte(s)) // allocate temporary copy + type stringWriter interface { + WriteString(string) (n int, err error) + } + if sw, ok := w.(stringWriter); ok { + return sw.WriteString(s) // avoid a copy + } + return w.Write([]byte(s)) // allocate temporary copy } func writeHeader(w io.Writer, contentType string) error { - if _, err := writeString(w, "Content-Type: "); err != nil { - return err - } - if _, err := writeString(w, contentType); err != nil { - return err - } - // ... + if _, err := writeString(w, "Content-Type: "); err != nil { + return err + } + if _, err := writeString(w, contentType); err != nil { + return err + } + // ... } ``` @@ -50,8 +50,8 @@ func writeHeader(w io.Writer, contentType string) error { ```go interface { - io.Writer - WriteString(s string) (n int, err error) + io.Writer + WriteString(s string) (n int, err error) } ``` @@ -65,13 +65,13 @@ interface { package fmt func formatOneValue(x interface{}) string { - if err, ok := x.(error); ok { - return err.Error() - } - if str, ok := x.(Stringer); ok { - return str.String() - } - // ...all other types... + if err, ok := x.(error); ok { + return err.Error() + } + if str, ok := x.(Stringer); ok { + return str.String() + } + // ...all other types... } ``` diff --git a/ch7/ch7-13.md b/ch7/ch7-13.md index 362d000..62bef8c 100644 --- a/ch7/ch7-13.md +++ b/ch7/ch7-13.md @@ -12,10 +12,10 @@ import "database/sql" func listTracks(db sql.DB, artist string, minYear, maxYear int) { - result, err := db.Exec( - "SELECT * FROM tracks WHERE artist = ? AND ? <= year AND year <= ?", - artist, minYear, maxYear) - // ... + result, err := db.Exec( + "SELECT * FROM tracks WHERE artist = ? AND ? <= year AND year <= ?", + artist, minYear, maxYear) + // ... } ``` @@ -23,22 +23,22 @@ Exec方法使用SQL字面量替換在査詢字符串中的每個'?';SQL字面 ```go func sqlQuote(x interface{}) string { - if x == nil { - return "NULL" - } else if _, ok := x.(int); ok { - return fmt.Sprintf("%d", x) - } else if _, ok := x.(uint); ok { - return fmt.Sprintf("%d", x) - } else if b, ok := x.(bool); ok { - if b { - return "TRUE" - } - return "FALSE" - } else if s, ok := x.(string); ok { - return sqlQuoteString(s) // (not shown) - } else { - panic(fmt.Sprintf("unexpected type %T: %v", x, x)) - } + if x == nil { + return "NULL" + } else if _, ok := x.(int); ok { + return fmt.Sprintf("%d", x) + } else if _, ok := x.(uint); ok { + return fmt.Sprintf("%d", x) + } else if b, ok := x.(bool); ok { + if b { + return "TRUE" + } + return "FALSE" + } else if s, ok := x.(string); ok { + return sqlQuoteString(s) // (not shown) + } else { + panic(fmt.Sprintf("unexpected type %T: %v", x, x)) + } } ``` @@ -48,11 +48,11 @@ switch語句可以簡化if-else鏈,如果這個if-else鏈對一連串值做相 ```go switch x.(type) { - case nil: // ... - case int, uint: // ... - case bool: // ... - case string: // ... - default: // ... + case nil: // ... + case int, uint: // ... + case bool: // ... + case string: // ... + default: // ... } ``` @@ -70,21 +70,21 @@ switch x := x.(type) { /* ... */ } ```go func sqlQuote(x interface{}) string { - switch x := x.(type) { - case nil: - return "NULL" - case int, uint: - return fmt.Sprintf("%d", x) // x has type interface{} here. - case bool: - if x { - return "TRUE" - } - return "FALSE" - case string: - return sqlQuoteString(x) // (not shown) - default: - panic(fmt.Sprintf("unexpected type %T: %v", x, x)) - } + switch x := x.(type) { + case nil: + return "NULL" + case int, uint: + return fmt.Sprintf("%d", x) // x has type interface{} here. + case bool: + if x { + return "TRUE" + } + return "FALSE" + case string: + return sqlQuoteString(x) // (not shown) + default: + panic(fmt.Sprintf("unexpected type %T: %v", x, x)) + } } ``` diff --git a/ch7/ch7-14.md b/ch7/ch7-14.md index 26fff91..9599f82 100644 --- a/ch7/ch7-14.md +++ b/ch7/ch7-14.md @@ -4,17 +4,17 @@ 這里顯示的是和這個API相關的部分: +encoding/xml ```go -encoding/xml package xml type Name struct { - Local string // e.g., "Title" or "id" + Local string // e.g., "Title" or "id" } type Attr struct { // e.g., name="value" - Name Name - Value string + Name Name + Value string } // A Token includes StartElement, EndElement, CharData, @@ -37,55 +37,55 @@ func (*Decoder) Token() (Token, error) // returns next Token in sequence 下面的xmlselect程序獲取和打印在一個XML文檔樹中確定的元素下找到的文本。使用上面的API,它可以在輸入上一次完成它的工作而從來不要具體化這個文檔樹。 +gopl.io/ch7/xmlselect ```go -gopl.io/ch7/xmlselect // Xmlselect prints the text of selected elements of an XML document. package main import ( - "encoding/xml" - "fmt" - "io" - "os" - "strings" + "encoding/xml" + "fmt" + "io" + "os" + "strings" ) func main() { - dec := xml.NewDecoder(os.Stdin) - var stack []string // stack of element names - for { - tok, err := dec.Token() - if err == io.EOF { - break - } else if err != nil { - fmt.Fprintf(os.Stderr, "xmlselect: %v\n", err) - os.Exit(1) - } - switch tok := tok.(type) { - case xml.StartElement: - stack = append(stack, tok.Name.Local) // push - case xml.EndElement: - stack = stack[:len(stack)-1] // pop - case xml.CharData: - if containsAll(stack, os.Args[1:]) { - fmt.Printf("%s: %s\n", strings.Join(stack, " "), tok) - } - } - } + dec := xml.NewDecoder(os.Stdin) + var stack []string // stack of element names + for { + tok, err := dec.Token() + if err == io.EOF { + break + } else if err != nil { + fmt.Fprintf(os.Stderr, "xmlselect: %v\n", err) + os.Exit(1) + } + switch tok := tok.(type) { + case xml.StartElement: + stack = append(stack, tok.Name.Local) // push + case xml.EndElement: + stack = stack[:len(stack)-1] // pop + case xml.CharData: + if containsAll(stack, os.Args[1:]) { + fmt.Printf("%s: %s\n", strings.Join(stack, " "), tok) + } + } + } } // containsAll reports whether x contains the elements of y, in order. func containsAll(x, y []string) bool { - for len(y) <= len(x) { - if len(y) == 0 { - return true - } - if x[0] == y[0] { - y = y[1:] - } - x = x[1:] - } - return false + for len(y) <= len(x) { + if len(y) == 0 { + return true + } + if x[0] == y[0] { + y = y[1:] + } + x = x[1:] + } + return false } ``` @@ -122,8 +122,8 @@ type Node interface{} // CharData or *Element type CharData string type Element struct { - Type xml.Name - Attr []xml.Attr - Children []Node + Type xml.Name + Attr []xml.Attr + Children []Node } ```