Go程序設計語言練習7.1-7.5

源代碼主要參考自 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
待續

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章