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 {}

任何类型都默认实现了空接口,我们可以用空接口来保存任意类型。