diff --git a/ch4/ch4-02-2.md b/ch4/ch4-02-2.md index 4b4dcd1..8455f40 100644 --- a/ch4/ch4-02-2.md +++ b/ch4/ch4-02-2.md @@ -1,6 +1,6 @@ ### 4.2.2. Slice內存技巧 -讓我們看看更多的例子,比如镟轉slice、反轉slice或在slice原有內存空間脩改元素。給定一個字符串列表,下面的nonempty函數將在原有slice內存空間之上返迴不包含空字符串的列表: +讓我們看看更多的例子,比如鏇轉slice、反轉slice或在slice原有內存空間脩改元素。給定一個字符串列表,下面的nonempty函數將在原有slice內存空間之上返迴不包含空字符串的列表: ```Go gopl.io/ch4/nonempty @@ -98,7 +98,7 @@ func main() { **練習 4.3:** 重寫reverse函數,使用數組指針代替slice。 -**練習 4.4:** 編寫一個rotate函數,通過一次循環完成镟轉。 +**練習 4.4:** 編寫一個rotate函數,通過一次循環完成鏇轉。 **練習 4.5:** 寫一個函數在原地完成消除[]string中相鄰重複的字符串的操作。 diff --git a/ch4/ch4-02.md b/ch4/ch4-02.md index de14908..063173b 100644 --- a/ch4/ch4-02.md +++ b/ch4/ch4-02.md @@ -67,7 +67,7 @@ reverse(a[:]) fmt.Println(a) // "[5 4 3 2 1 0]" ``` -一種將slice元素循環向左镟轉n個元素的方法是三次調用reverse反轉函數,第一次是反轉開頭的n個元素,然後是反轉剩下的元素,最後是反轉整個slice的元素。(如果是向右循環镟轉,則將第三個函數調用移到第一個調用位置就可以了。) +一種將slice元素循環向左鏇轉n個元素的方法是三次調用reverse反轉函數,第一次是反轉開頭的n個元素,然後是反轉剩下的元素,最後是反轉整個slice的元素。(如果是向右循環鏇轉,則將第三個函數調用移到第一個調用位置就可以了。) ```Go s := []int{0, 1, 2, 3, 4, 5} diff --git a/ch5/ch5-02.md b/ch5/ch5-02.md index 2cf577e..29af3f8 100644 --- a/ch5/ch5-02.md +++ b/ch5/ch5-02.md @@ -1,3 +1,150 @@ ## 5.2. 遞歸 -TODO +函數可以是遞歸的,這意味着函數可以直接或間接的調用自身。對許多問題而言,遞歸是一種強有力的技術,例如處理遞歸的數據結構。在4.4節,我們通過遍歷二叉樹來實現簡單的插入排序,在本章節,我們再次使用它來處理HTML文件。 + +下文的示例代碼使用了非標準包 golang.org/x/net/html ,解析HTML。golang.org/x/... 目録下存儲了一些由Go糰隊設計、維護,對網絡編程、国際化文件處理、移動平台、圖像處理、加密解密、開發者工具提供支持的擴展包。未將這些擴展包加入到標準庫原因有二,一是部分包仍在開發中,二是對大多數Go語言的開發者而言,擴展包提供的功能很少被使用。 + +例子中調用golang.org/x/net/html的部分api如下所示。html.Parse函數讀入一組bytes.解析後,返迴html.node類型的HTML頁面樹狀結構根節點。HTML擁有很多類型的結點如text(文本),commnets(註釋)類型,在下面的例子中,我們 隻關註< name key='value' >形式的結點。 + +``` +golang.org/x/net/html +package html +type Node struct { +Type NodeType +Data string +Attr []Attribute +FirstChild, NextSibling *Node +} +type NodeType int32 + +const ( +ErrorNode NodeType = iota +TextNode +DocumentNode +ElementNode +CommentNode +DoctypeNode +) + +type Attribute struct { +Key, Val string +} +func Parse(r io.Reader) (*Node, error) +``` +main函數解析HTML標準輸入,通過遞歸函數visit獲得links(鏈接),併打印出這些links: + +``` +gopl.io/ch5/findlinks1 +// Findlinks1 prints the links in an HTML document read +from standard input. +package main +import ( +"fmt" +"os" +"golang.org/x/net/html" +) +func main() { +doc, err := html.Parse(os.Stdin) +if err != nil { +fmt.Fprintf(os.Stderr, "findlinks1: %v\n", err) +os.Exit(1) +} +for _, link := range visit(nil, doc) { +fmt.Println(link) +} +} +``` +visit函數遍歷HTML的節點樹,從每一個anchor元素的href屬性獲得link,將這些links存入字符串數組中,併返迴這個字符串數組。 + +``` +// visit appends to links each link found in n and returns +the result. +func visit(links []string, n *html.Node) []string { +if n.Type == html.ElementNode && n.Data == "a" { +for _, a := range n.Attr { +if a.Key == "href" { +links = append(links, a.Val) +} +} +} +for c := n.FirstChild; c != nil; c = c.NextSibling { +links = visit(links, c) +} +return links +} +``` +爲了遍歷結點n的所有後代結點,每次遇到n的孩子結點時,visit遞歸的調用自身。這些孩子結點存放在FirstChild鏈表中。 + +讓我們以Go的主頁(golang.org)作爲目標,運行findlinks。我們以fetch(1.5章)的輸出作爲findlinks的輸入。下面的輸出做了簡化處理。 + +``` +$ go build gopl.io/ch1/fetch +$ go build gopl.io/ch5/findlinks1 +$ ./fetch https://golang.org | ./findlinks1 +# +/doc/ +/pkg/ +/help/ +/blog/ +http://play.golang.org/ +//tour.golang.org/ +https://golang.org/dl/ +//blog.golang.org/ +/LICENSE +/doc/tos.html +http://www.google.com/intl/en/policies/privacy/ +``` +註意在頁面中出現的鏈接格式,在之後我們會介紹如何將這些鏈接,根據根路徑(https://golang.org)生成可以直接訪問的url。 + +在函數outline中,我們通過遞歸的方式遍歷整個HTML結點樹,併輸出樹的結構。在outline內部,每遇到一個HTML元素標籤,就將其入棧,併輸出。 + +``` +gopl.io/ch5/outline +func main() { +doc, err := html.Parse(os.Stdin) +if err != nil { +fmt.Fprintf(os.Stderr, "outline: %v\n", err) +os.Exit(1) +} +outline(nil, doc) +} +func outline(stack []string, n *html.Node) { +if n.Type == html.ElementNode { +stack = append(stack, n.Data) // push tag +fmt.Println(stack) +} +for c := n.FirstChild; c != nil; c = c.NextSibling { +outline(stack, c) +} +} +``` + +有一點值得註意:outline有入棧操作,但沒有相對應的出棧操作。當outline調用自身時,被調用者接收的是stack的拷貝。被調用者的入棧操作,脩改的是stack的拷貝,而不是調用者的stack,因對當函數返迴時,調用者的stack併未被脩改。 + +下面是https://golang.org頁面的簡要結構: + +``` +$ go build gopl.io/ch5/outline +$ ./fetch https://golang.org | ./outline +[html] +[html head] +[html head meta] +[html head title] +[html head link] +[html body] +[html body div] +[html body div] +[html body div div] +[html body div div form] +[html body div div form div] +[html body div div form div a] +... +``` +正如你在上面實驗中所見,大部分HTML頁面隻需幾層遞歸就能被處理,但仍然有些頁面需要深層次的遞歸。 + +大部分編程語言使用固定大小的函數調用棧,常見的大小從64KB到2MB不等。固定大小棧會限製遞歸的深度,當你用遞歸處理大量數據時,需要避免棧溢出;除此之外,還會導致安全性問題。與相反,Go語言使用可變棧,棧的大小按需增加(初始時很小)。這使得我們使用遞歸時不必考慮溢出和安全問題。 + +練習**5.1** :脩改findlinks代碼中遍歷n.FirstChild鏈表的部分,將循環調用visit,改成遞歸調用。 +練習**5.2** : 編寫函數,記録在HTML樹中出現的同名元素的次數。 +練習**5.3** : 編寫函數輸出所有text結點的內容。註意不要訪問< script >和< style >元素,因爲這些元素對瀏覽者是不可見的。 +練習**5.4** : 擴展vist函數,使其能夠處理其他類型的結點,如images、scripts和style sheets。 diff --git a/ch9/ch9.md b/ch9/ch9.md index f5b85c1..5b22909 100644 --- a/ch9/ch9.md +++ b/ch9/ch9.md @@ -1,5 +1,5 @@ # 第九章 基於共享變量的併發 -前一章我们介绍了一些使用goroutine和channel这样直接而自然的方式来实现并发的方法。然而这样做我们实际上屏蔽掉了在写并发代码时必须处理的一些重要而且细微的问题。 +前一章我們介紹了一些使用goroutine和channel這樣直接而自然的方式來實現併發的方法。然而這樣做我們實際上屏蔽掉了在寫併發代碼時必鬚處理的一些重要而且細微的問題。 -在本章中,我们会细致地了解并发机制。尤其是在多goroutine之间的共享变量,并发问题的分析手段,以及解决这些问题的基本模式。最后我们会解释goroutine和操作系统线程之间的技术上的一些区别。 +在本章中,我們會細致地了解併發機製。尤其是在多goroutine之間的共享變量,併發問題的分析手段,以及解決這些問題的基本模式。最後我們會解釋goroutine和操作繫統線程之間的技術上的一些區别。