源代码主要参考自 https://www.cnblogs.com/ling-diary/p/10294916.html
结合参考 https://xingdl2007.gitbooks.io/gopl-soljutions/chapter-7-interfaces.html
仅供学习,如有侵权,请联系删除。
练习7.1:使用类似ByteCounter的想法,实现单词和行的计数器,实现时考虑使用bufio.ScanWords。
API
1.func ScanBytes(data []byte, atEOF bool) (advance int, token []byte, err error)
2.func ScanLines(data []byte, atEOF bool) (advance int, token []byte, err error)
思路
每次读取一行或一个单词,统计后,修改开始的位置,重复直至读完。
代码如下:
package main
import (
"bufio"
"fmt"
)
type WordsCounter int
func (c *WordsCounter) Write(content []byte) (int, error) {
for start := 0; start < len(content); {
//跳过开头的space,返回遇到第一个word后下次scan的开始index
//Hello Worlds 调用bufio.ScanWords返回
//6 [Hello的字节slice] nil
advance, _, err := bufio.ScanWords(content[start:], true)
if err != nil {
return 0, err
}
start += advance
(*c)++
}
return int(*c), nil
}
type LinesCounter int
func (c *LinesCounter) Write(content []byte) (int, error) {
for start := 0; start < len(content); {
advance, _, err := bufio.ScanLines(content[start:], true)
if err != nil {
return 0, err
}
start += advance
(*c)++
}
return int(*c), nil
}
func main() {
var wc WordsCounter
wc.Write([]byte("Hello Worlds Test Me"))
fmt.Println(wc) // 4
wc.Write([]byte("append something to the end"))
fmt.Println(wc) // 9
var lc LinesCounter
fmt.Fprintf(&lc, "%s\n%s\n%s\n", "Hello World", "Second Line", "Third Line")
fmt.Println(lc) // 3
fmt.Fprintf(&lc, "%s\n%s\n%s", "第4行", "第5行", "")
fmt.Println(lc) // 5
}
练习 7.2: 写一个带有如下函数签名的函数CountingWriter,传入一个io.Writer接口类型,返回一个新的Writer类型把原来的Writer封装在里面和一个表示写入新的Writer字节数的int64类型指针
思路
如题目所示。
代码如下
package main
import (
"fmt"
"io"
"os"
)
type CountWriter struct {
Writer io.Writer
Count int
}
func (cw *CountWriter) Write(content []byte) (int, error) {
n, err := cw.Writer.Write(content)
if err != nil {
return n, err
}
cw.Count += n
return n, nil
}
func CountingWriter(writer io.Writer) (io.Writer, *int) {
cw := CountWriter{
Writer: writer,
}
return &cw, &(cw.Count)
}
func main() {
cw, counter := CountingWriter(os.Stdout)
fmt.Fprintf(cw, "%s", "Print somethind to the screen...")
fmt.Println(*counter)
cw.Write([]byte("Append soething..."))
fmt.Println(*counter)
}
练习7.3: 为在gopl.io/ch4/treesort (§4.4)的*tree类型实现一个String方法去展示tree类型的值序列。
代码
package main
import (
"fmt"
"math/rand"
)
type tree struct {
value int
left, right *tree
}
func (t *tree) String() string {
res := ""
if t == nil {
return res
}
res += t.left.String() // 左树
res = fmt.Sprintf("%s %d", res, t.value) // 左树 空格 当前值 空格 右树
res += t.right.String()
return res
}
func buildTree(data []int) *tree {
var root = new(tree)
for _, v := range data {
root = add(root, v)
}
return root
}
func add(t *tree, e int) *tree {
if t == nil {
t = new(tree)
t.value = e
return t
}
if e < t.value {
t.left = add(t.left, e)
} else {
t.right = add(t.right, e)
}
return t
}
func main() {
data := make([]int, 50)
for i := range data {
data[i] = rand.Int() % 50
}
root := buildTree(data)
fmt.Println(root)
//空指针
fmt.Println(new(tree))
//只有根节点
root = new(tree)
root.value = 100
fmt.Println(root)
//没有右子树
data = []int{5, 4, 3, 2, 1}
root = buildTree(data)
fmt.Println(root)
//没有左子树
data = []int{1, 3, 2, 4, 5}
root = buildTree(data)
fmt.Println(root)
}
练习 7.4: strings.NewReader函数通过读取一个string参数返回一个满足io.Reader接口类型的值(和其它值)。实现一个简单版本的NewReader,并用它来构造一个接收字符串输入的HTML解析器(§5.2)
接口
func NewReader(s string) *Reader
type Reader interface { Read(p []byte) (n int, err error) }
func Copy(dst Writer, src Reader) (written int64, err error)
代码如下
HTML解析器没做。
此处代码结合了 https://xingdl2007.gitbooks.io/gopl-soljutions/chapter-7-interfaces.html
主要判断b长度为0,且考虑读完返回EOF,修改测试读取逻辑。
package main
import (
"fmt"
"io"
)
type StringReader struct {
data string
current int
}
func (sr *StringReader) Read(b []byte) (n int, err error) {
if len(b) == 0 { // 不需要读入
return 0, nil
}
// copy() guarantee copy min(len(b),len(sr.data[sr.current:])) bytes
n = copy(b, sr.data[sr.current:])
if sr.current += n; sr.current >= len(sr.data) { // 已读完
err = io.EOF
}
return
}
func NewReader(in string) *StringReader {
sr := new(StringReader)
sr.data = in
return sr
}
func main() {
str := "Hello World"
sr := NewReader(str)
data := make([]byte, 10) // 每次最多读10个byte
n, err := sr.Read(data[:0]) // 初始化
for err == nil{
n, err = sr.Read(data)
fmt.Println(n, string(data[0:n])) // 重新取切片,因为最后一次结果data[n:]含有上一轮的结果
}
//output:
// 10 Hello Worl
// 1 d
}
7.5 io包里面的LimitReader函数接收一个io.Reader接口类型的r和字节数n,并且返回另一个从r中读取字节但是当读完n个字节后就表示读到文件结束的Reader。实现这个 LimitReader函数:
代码如下:
稍微改动,维护一个当前剩余的长度。
package main
import (
"fmt"
"io"
"os"
)
type LimitedReader struct {
Reader io.Reader
Limit int
}
func (r *LimitedReader) Read(b []byte) (n int, err error) {
if r.Limit <= 0 {
return 0, io.EOF
}
if len(b) > r.Limit {
b = b[:r.Limit]
}
n, err = r.Reader.Read(b)
r.Limit -= n
return
}
func LimitReader(r io.Reader, limit int) io.Reader {
return &LimitedReader{
Reader: r,
Limit: limit,
}
}
func main() {
file, err := os.Open("limit.txt") // 1234567890
if err != nil {
panic(err)
}
defer file.Close()
lr := LimitReader(file, 5)
buf := make([]byte, 10)
n, err := lr.Read(buf)
if err != nil {
panic(err)
}
fmt.Println(n, buf, string(buf)) // 5 [49 50 51 52 53 0 0 0 0 0] 12345
}
7.6 对tempFlag加入支持开尔文温度。
func KToC(k Kelvin) Celsius { return Celsius(k-273.15) }
仿着写就ok了。
7.7 解释为什么帮助信息在它的默认值是20.0没有包含°C的情况下输出了°C。
func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) }
Celsius实现了String方法,输出时会在数字后自动加上°C
注:
关于flag的讲解可以参考 https://blog.51cto.com/steed/2363801
调用 Var 方法是会把 *celsiusFlag 实参赋给 flag.Value 形参,编译器会在此时检查 *celsiusFlag 类型是否有 flag.Value 所必需的方法
func CelsiusFlag(name string, value Celsius, usage string) *Celsius {
f := celsiusFlag{value}
flag.CommandLine.Var(&f, name, usage)
return &f.Celsius
}
7.8 ~ 7.10
待续