第七章

  • 接口即约定
  • 接口类型
  • 实现接口
  • 使用flag.Value来解析参数
  • 接口值
  • 使用Sort.Interface来排序
  • http.Handler接口
  • error接口
  • 示例: 表达式求值器
  • 类型断言
  • 使用类型断言来识别错误
  • 通过接口类型断言来查询特性
  • 类型分支
  • 示例: 基于标记的XML解析
  • 一些建议

这章是重点

练习7.1:

使用类似ByteCounter的想法,实现单词和行的计数器.实现时考虑使用bufio.ScanWords

  • 创建一个结构体,实现Writer方法,分词得到写入的个数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
"fmt"
"bufio"
"strings"
)

type ByteCounter int

func (c *ByteCounter) Write(p []byte) (int, error) {
scan := bufio.NewScanner(strings.NewReader(string(p)))
scan.Split(bufio.ScanWords)

for scan.Scan() {
*c += 1
}
return len(p), nil
}

func main() {
var c ByteCounter

var name = "Dolly"
fmt.Fprintf(&c, "hello, %s", name)
fmt.Println(c)
}

练习7.2:

实现一个满足如下前面的CountingWriter函数,输入一个io.Writer,输出一个封装了输入值的新Writer,以及一个指向int64的指针,该指针对应的值是新的Writer写入的字节数
func CountingWriter(w io.Writer) (io.Writer, *int64)

  • 这个地方使用了一个全局的变量统计写入的字节数,CountingWriter好像再外面封装了一层一样,仅仅只是为了记一个数,需要新建一个类型,然后让这个类型满足接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "io"

var position int64

type Counting struct {
W io.Writer
}

func (c *Counting) Write(p []byte) (n int, err error) {
n, err = c.W.Write(p)
position += int64(n)
return n, err
}

func CountingWriter(w io.Writer) (io.Writer, *int64) {
return &Counting{w}, &position
}

练习7.3:

为gopl.io/ch4/treesort中的*tree类型写一个String方法,用于展示其中的值序列

  • 这个练习题比较直白了,估计是想说类型可以自己添加方法,这个地方直接使用广度优先保存下了列表然后打印一下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func (t *tree) String() string {
var deque []*tree
var ret []int
deque = append(deque, t)
for len(deque) > 0 {
current := deque[0]
deque = deque[1:]
if current.left != nil {
deque = append(deque, current.left)
}
if current.right != nil {
deque = append(deque, current.right)
}
ret = append(ret, current.value)
}

var buf bytes.Buffer
buf.Write([]byte("{"))
for i, v := range ret {
if i == len(ret)-1 {
buf.Write([]byte(fmt.Sprintf("%d", v)))
} else {
buf.Write([]byte(fmt.Sprintf("%d, ", v)))
}
}
buf.Write([]byte("}"))
return buf.String()
}

练习7.4:

strings.NewReader函数输入一个字符串,返回一个从字符串读取数据并满足io.Reader接口的值.请自己实现该函数,并且通过它来让HTML分析器支持以字符串作为输入

  • Golang里面使用Newxxx这样的函数特别普遍,一个常用的套路,它初始化了一个struct,然后该结构体满足某接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main

import (
"io"
"io/ioutil"
"fmt"
)

type S struct {
R string
current int
}

func (s *S) Read(p []byte) (n int, err error) {
if s.current >= len(s.R) {
return 0, io.EOF
}
l := copy(p, s.R[s.current:])
s.current = l
return l, nil
}

func NewReader(s string) io.Reader {
return &S{s, 0}
}


func main() {
r := NewReader("123")
s, err := ioutil.ReadAll(r)
if err != nil {
fmt.Println(s)
}
fmt.Println(string(s))
}

练习7.5:

io包中的LimitReader函数接受io.Reader r和字节数n,返回一个Reader,该返回值从r读取数据,但在读取n字节后报告文件结束.请实现该函数
func LimitReader(r io.Reader,n int64) io.Reader

  • 感觉和上面的New差不多,只不过这个地方的初始化是使用了一个接口类型值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import "io"

type LimitR struct {
R io.Reader
N int64
}

func (l *LimitR) Read(p []byte) (n int, err error) {
if l.N <= 0 {
return 0, io.EOF
}
if int64(len(p)) > l.N {
p = p[0:l.N]
}
n, err = l.R.Read(p)
l.N -= int64(n)
return n, err
}

func LimitReader(r io.Reader, n int64) io.Reader {
return &LimitR{r, n}
}

练习7.6:

在tempflag中支持热力学温度

  • 这题没什么意义,添加一个case判断就可以了
  • 本小节主要讲解了flag.Value的原理,先有一个struct结构体,然后一个函数xxFlag(name string,value Type,usage string) *Type
  • 上述函数调用flag.CommandLine.Var进行注册,当调用flag.Parse的时候调用结构体相应的Set方法,达到了解析命令行参数的目的

练习7.7:

请解释为什么默认值20.0没有写单位,而在帮助消息中却包含单位

  • 接上一题,因为除了String方法还有Set方法,查看帮助的时候会调用它,在celsiusFlag结构体中String方法直接添加了单位

练习7.8:

很多图形界面提供了一个表格空间,它支持有状态的多层排序,先按照最近单击的列表排序,接着是上一次单击的列,依次类推.请定义sort.Interface接口来实现如上需求,试比较这个方法和多次使用sort.Stable排序的异同

  • 这一节主要讲利用接口实现排序,需要排序的对象提供三个方法Len、Swap、Less
  • 用一个slice来维持状态,Less函数使用倒序遍历加上switch开实现
  • 对比sort.Stable来说,sort.Stable是稳定排序,一个对象有A/B两个属性,当使用稳定排序的时候假如A属性值相同,则对象相对位置不会改变
  • 代码可能写的有点失败~~
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    package main

    import (
    "time"
    "fmt"
    "sort"
    )

    type Track struct {
    Title string
    Artist string
    Album string
    Year int
    Length time.Duration
    }

    var tracks = []*Track{
    {"Go", "Delilah", "From the Roots Up", 2012, length("3m38s")},
    {"Go", "Moby", "Moby", 1992, length("3m37s")},
    {"Go Ahead", "Alicia Keys", "As I Am", 2007, length("4m36s")},
    {"Ready 2 Go", "Martin Solveig", "Smash", 2011, length("4m24s")},
    }



    func length(s string) time.Duration {
    d, err := time.ParseDuration(s)
    if err != nil {
    panic(s)
    }
    return d
    }

    type customSort struct {
    t []*Track
    r []string
    }

    var c = customSort{tracks,[]string{}}

    func (x customSort) Len() int {
    return len(x.t)
    }

    func (x customSort) Less(i, j int) bool {
    for i := len(x.r) - 1; i >= 0; i-- {
    switch x.r[i] {
    case "Title":
    if x.t[i].Title != x.t[j].Title {
    return x.t[i].Title < x.t[j].Title
    }
    case "Artist":
    if x.t[i].Artist != x.t[j].Artist {
    return x.t[i].Artist < x.t[j].Artist
    }
    case "Album":
    if x.t[i].Artist != x.t[j].Artist {
    return x.t[i].Artist < x.t[j].Artist
    }
    case "Year":
    if x.t[i].Year != x.t[j].Year {
    return x.t[i].Year < x.t[j].Year
    }
    default:
    panic("Not support field")
    }
    }
    return false
    }

    func (x customSort) Swap(i, j int) {
    x.t[i], x.t[j] = x.t[j], x.t[i]
    }


    func Sort(s string){
    c.r = append(c.r,s)
    if len(c.r) > 4{
    c.r = c.r[:4]
    }
    sort.Sort(c)
    }

    func main() {
    Sort("Title")
    for _,v := range tracks{
    fmt.Println(*v)
    }
    fmt.Println("=======")
    Sort("Artist")
    for _,v := range tracks{
    fmt.Println(*v)
    }
    fmt.Println("=======")
    Sort("Album")
    for _,v := range tracks{
    fmt.Println(*v)
    }
    }

练习7.9:

利用html/template包来替换printTracks函数,使用HTML表格来显示音乐列表,结合上一个练习,来实现通过单击列头来发送HTTP请求,进而对表格排序

  • 对这题完全没兴趣,可以练习写template包

练习7.10:

sort.Interface也可以用于其他用户.试写一个函数IsPalindrome(s sort.Interface)bool来判断一个序列是否是回文,即序列反转后保持不变.可以假定对于下标分别为i、j的元素,如果!s.Less(i,j)&&!s.Less(j,i),那么两个元素相等

  • 左右向中间比较,都相等则表示为回文,主要是用接口方法的Len和Less判断是否相等
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main

import (
"sort"
"fmt"
)

type Demo []int

func (d Demo) Len() int {
return len(d)
}

func (d Demo) Less(i, j int) bool {
return d[i] < d[j]
}

func (d Demo) Swap(i, j int) {
d[i], d[j] = d[j], d[i]
}

func IsPalindrome(s sort.Interface) bool {
for i := 0; i < s.Len()/2; i++ {
x, y := i, s.Len()-1-i
if !(!s.Less(x, y) && !s.Less(y, x)) {
return false
}
}
return true
}

func main() {
fmt.Println(IsPalindrome(Demo{1, 2, 1}))
fmt.Println(IsPalindrome(Demo{1, 2, 3}))
}

练习7.11:

增加额外的处理程序,来支持创建、读取、更新和删除数据库条目.比如,/update?item=socks&prince=6 这样的请求将更新仓库中物品的价格,如果商品不存在或者价格无效就返回错误

  • 使用多个http.HandleFunc
  • 使用锁防止并发修改
  • 给struct添加多个方法就好了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package main

import (
"fmt"
"log"
"net/http"
"sync"
"strconv"
)

func main() {
db := database{map[string]dollars{"shoes": 50, "socks": 5}, sync.Mutex{}}
http.HandleFunc("/list", db.list)
http.HandleFunc("/price", db.price)
http.HandleFunc("/update", db.update)
http.HandleFunc("/delete", db.delete)
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}

type dollars float32

func (d dollars) String() string { return fmt.Sprintf("$%.2f", d) }

type database struct {
R map[string]dollars
mutex sync.Mutex
}

func (db database) list(w http.ResponseWriter, req *http.Request) {
for item, price := range db.R {
fmt.Fprintf(w, "%s: %s\n", item, price)
}
}

func (db database) update(w http.ResponseWriter, req *http.Request) {
db.mutex.Lock()
defer db.mutex.Unlock()

item := req.URL.Query().Get("item")
price := req.URL.Query().Get("price")

p, err := strconv.ParseFloat(price, 64)
if err != nil {
fmt.Fprint(w, "invalid price")
} else {
db.R[item] = dollars(p)
fmt.Fprint(w, "Update price success")
}
}

func (db database) delete(w http.ResponseWriter, req *http.Request) {
db.mutex.Lock()
defer db.mutex.Unlock()

item := req.URL.Query().Get("item")
delete(db.R, item)
fmt.Fprint(w, "Success")
}

func (db database) price(w http.ResponseWriter, req *http.Request) {
item := req.URL.Query().Get("item")
if price, ok := db.R[item]; ok {
fmt.Fprintf(w, "%s\n", price)
} else {
w.WriteHeader(http.StatusNotFound) // 404
fmt.Fprintf(w, "no such item: %q\n", item)
}
}

练习7.12:

修改/list的处理程序,改为输出HTML表格,而不是纯文本.可以考虑使用html/template包

  • 这本书我已经数不清有多少个template包的练习了,老哥,这个模块是不是你写的!!!!!!
  • restful当道的年代,没多少机会拼html啦
  • 即使拼html,这也相当于在python里面不用Jinja这种模板引擎,而跑去用python自带的string.Template一样