🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
## 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 {} ``` 任何类型都默认实现了空接口,我们可以用空接口来保存任意类型。