## 4.10\. Rotting cats
在我們上面創建的file包(package)基礎之上,實現一個簡單的Unix工具 "cat(1)", "progs/cat.go":
```
05 package main
07 import (
08 "./file"
09 "flag"
10 "fmt"
11 "os"
12 )
14 func cat(f *file.File) {
15 const NBUF = 512
16 var buf [NBUF]byte
17 for {
18 switch nr, er := f.Read(buf[:]); true {
19 case nr < 0:
20 fmt.Fprintf(os.Stderr, "cat: error reading from %s: %s\n", f.String(), er.String())
21 os.Exit(1)
22 case nr == 0: // EOF
23 return
24 case nr > 0:
25 if nw, ew := file.Stdout.Write(buf[0:nr]); nw != nr {
26 fmt.Fprintf(os.Stderr, "cat: error writing from %s: %s\n", f.String(), ew.String())
27 }
28 }
29 }
30 }
32 func main() {
33 flag.Parse() // Scans the arg list and sets up flags
34 if flag.NArg() == 0 {
35 cat(file.Stdin)
36 }
37 for i := 0; i < flag.NArg(); i++ {
38 f, err := file.Open(flag.Arg(i), 0, 0)
39 if f == nil {
40 fmt.Fprintf(os.Stderr, "cat: can't open %s: error %s\n", flag.Arg(i), err)
41 os.Exit(1)
42 }
43 cat(f)
44 f.Close()
45 }
46 }
```
現在應該很容易被理解,但是還有些新的語法"switch". 比如: 包括了"for"循環, "if"和 "switch"初始化的語句。在"switch"語句的18行用了"f.Read()"函數的返回值"nr"和"er"做為 變量(25行中的"if"也采用同樣的方法)。這里的"switch"語法和其他語言語法基本相同,每個分支(cases) 從上到下查找是否與相關的表達式相同,分支(case)的表達式不僅僅是常量(constants)或整數(integers), 它可以是你想到的任意類型。
這個"switch"的值永遠是"真(true)", 我們會一直執行它, 就像"for"語句,不寫值默認是"真"(true). 事實上,"switch"是從"if-else"由來的。在這里我們要說明, "switch"語句中的每個"分支"(case)都 默認隱藏了"break".
在25行中調用"Write()"采用了slicing來取得buffer數據. 在標準的GO中提供了Slices對I/O buffers的操作。
現在讓我們做一個"cat"的升級版讓"rot13"來處理輸入, 就是個簡單的字符處理,但是要 采用GO的新特性"接口(interface)"來實現。
這個"cat()"使用了2個子程序"f":"Read()"和"String", 讓我們定義這2個接口, 源碼參考 "progs/cat_rot13.go"
```
26 type reader interface {
27 Read(b []byte) (ret int, err os.Error)
28 String() string
29 }
```
任何類型的方法都有 reader 這兩個方法 —— 也就是說實現了這兩個方法, 任何類型的方法都能使用。由于 file.File 實現了 reader 接口,我們就可以讓 cat 的子程序訪問 reader 從而取代了 *file.File 并且能正常工作,讓我們來些第二個類型實現 reader , 一個關注現有的 reader ,另一個 rot13 只關注數據。我們只是定義了這個類型和 實現了這個方法并沒有做其他的內部處理, 我們實現了第二個 reader 接口.
```
31 type rotate13 struct {
32 source reader
33 }
35 func newRotate13(source reader) *rotate13 {
36 return &rotate13{source}
37 }
39 func (r13 *rotate13) Read(b []byte) (ret int, err os.Error) {
40 r, e := r13.source.Read(b)
41 for i := 0; i < r; i++ {
42 b[i] = rot13(b[i])
43 }
44 return r, e
45 }
47 func (r13 *rotate13) String() string {
48 return r13.source.String()
49 }
50 // end of rotate13 implementation
```
(42行的"rot13"函數非常簡單,沒有必要在這里進行討論)
為了使用新的特性,我們定義了一個標記(flag):
```
14 var rot13Flag = flag.Bool("rot13", false, "rot13 the input")
```
用它基本上不需要修改"cat()"這個函數:
```
52 func cat(r reader) {
53 const NBUF = 512
54 var buf [NBUF]byte
56 if *rot13Flag {
57 r = newRotate13(r)
58 }
59 for {
60 switch nr, er := r.Read(buf[:]); {
61 case nr < 0:
62 fmt.Fprintf(os.Stderr, "cat: error reading from %s: %s\n", r.String(), er.String())
63 os.Exit(1)
64 case nr == 0: // EOF
65 return
66 case nr > 0:
67 nw, ew := file.Stdout.Write(buf[0:nr])
68 if nw != nr {
69 fmt.Fprintf(os.Stderr, "cat: error writing from %s: %s\n", r.String(), ew.String())
70 }
71 }
72 }
73 }
```
(我們應該對 main 和 cat 單獨做些封裝,不僅僅是對類型參數的修改,就當是練習)從56行到 58行: 如果 rot13 標記是真,封裝的 reader 就會接受數據并傳給 rotate13 并處理. 注意: 這個接口的值是變量,不是指針,這個參數是 reader 類型,不是 *reader , 盡管后面轉換為 指向結構體的指針。
這里是執行結果:
```
% echo abcdefghijklmnopqrstuvwxyz | ./cat
abcdefghijklmnopqrstuvwxyz
% echo abcdefghijklmnopqrstuvwxyz | ./cat --rot13
nopqrstuvwxyzabcdefghijklm
%
```
也許你會說使用注入依賴(dependency injection)能輕松的讓接口以一個文件描述符執行。
接口(interfaces)是Go的一個特性,一個接口是由類型實現的,接口就是聲明該類型的所有方法。 也就是說一個類型可以實現多個不同的接口, 沒有任何類型的限制,就像我們的例子"rot13". "file.File"這個類型實現了"reader", 它也能實現"writer", 或通過其他的方法來實現這個接口。 參考空接口(empty interface)
```
type Empty interface {}
```
任何類型都默認實現了空接口,我們可以用空接口來保存任意類型。
- 1. 關于本文
- 2. Go語言簡介
- 3. 安裝go環境
- 3.1. 簡介
- 3.2. 安裝C語言工具
- 3.3. 安裝Mercurial
- 3.4. 獲取代碼
- 3.5. 安裝Go
- 3.6. 編寫程序
- 3.7. 進一步學習
- 3.8. 更新go到新版本
- 3.9. 社區資源
- 3.10. 環境變量
- 4. Go語言入門
- 4.1. 簡介
- 4.2. Hello,世界
- 4.3. 分號(Semicolons)
- 4.4. 編譯
- 4.5. Echo
- 4.6. 類型簡介
- 4.7. 申請內存
- 4.8. 常量
- 4.9. I/O包
- 4.10. Rotting cats
- 4.11. Sorting
- 4.12. 打印輸出
- 4.13. 生成素數
- 4.14. Multiplexing
- 5. Effective Go
- 5.1. 簡介
- 5.2. 格式化
- 5.3. 注釋
- 5.4. 命名
- 5.5. 分號
- 5.6. 控制流
- 5.7. 函數
- 5.8. 數據
- 5.9. 初始化
- 5.10. 方法
- 5.11. 接口和其他類型
- 5.12. 內置
- 5.13. 并發
- 5.14. 錯誤處理
- 5.15. Web服務器
- 6. 如何編寫Go程序
- 6.1. 簡介
- 6.2. 社區資源
- 6.3. 新建一個包
- 6.4. 測試
- 6.5. 一個帶測試的演示包
- 7. Codelab: 編寫Web程序
- 7.1. 簡介
- 7.2. 開始
- 7.3. 數據結構
- 7.4. 使用http包
- 7.5. 基于http提供wiki頁面
- 7.6. 編輯頁面
- 7.7. template包
- 7.8. 處理不存在的頁面
- 7.9. 儲存頁面
- 7.10. 錯誤處理
- 7.11. 模板緩存
- 7.12. 驗證
- 7.13. 函數文本和閉包
- 7.14. 試試!
- 7.15. 其他任務
- 8. 針對C++程序員指南
- 8.1. 概念差異
- 8.2. 語法
- 8.3. 常量
- 8.4. Slices(切片)
- 8.5. 構造值對象
- 8.6. Interfaces(接口)
- 8.7. Goroutines
- 8.8. Channels(管道)
- 9. 內存模型
- 9.1. 簡介
- 9.2. Happens Before
- 9.3. 同步(Synchronization)
- 9.4. 錯誤的同步方式
- 10. 附錄
- 10.1. 命令行工具
- 10.2. 視頻和講座
- 10.3. Release History
- 10.4. Go Roadmap
- 10.5. 相關資源