mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2024-12-25 14:28:58 +00:00
ch5: fix code path
This commit is contained in:
parent
3666d2f0e8
commit
20a8cf71b9
@ -6,8 +6,8 @@
|
||||
|
||||
例子中調用golang.org/x/net/html的部分api如下所示。html.Parse函數讀入一組bytes.解析後,返迴html.node類型的HTML頁面樹狀結構根節點。HTML擁有很多類型的結點如text(文本),commnets(註釋)類型,在下面的例子中,我們 隻關註< name key='value' >形式的結點。
|
||||
|
||||
<u><i>golang.org/x/net/html</i></u>
|
||||
```Go
|
||||
golang.org/x/net/html
|
||||
package html
|
||||
|
||||
type Node struct {
|
||||
@ -37,8 +37,8 @@ func Parse(r io.Reader) (*Node, error)
|
||||
|
||||
main函數解析HTML標準輸入,通過遞歸函數visit獲得links(鏈接),併打印出這些links:
|
||||
|
||||
<u></i>gopl.io/ch5/findlinks1</i></u>
|
||||
```Go
|
||||
gopl.io/ch5/findlinks1
|
||||
// Findlinks1 prints the links in an HTML document read from standard input.
|
||||
package main
|
||||
|
||||
@ -106,8 +106,8 @@ http://www.google.com/intl/en/policies/privacy/
|
||||
|
||||
在函數outline中,我們通過遞歸的方式遍歷整個HTML結點樹,併輸出樹的結構。在outline內部,每遇到一個HTML元素標籤,就將其入棧,併輸出。
|
||||
|
||||
<u><i>gopl.io/ch5/outline</i></u>
|
||||
```Go
|
||||
gopl.io/ch5/outline
|
||||
func main() {
|
||||
doc, err := html.Parse(os.Stdin)
|
||||
if err != nil {
|
||||
|
@ -4,8 +4,8 @@
|
||||
|
||||
下面的程序是findlinks的改進版本。脩改後的findlinks可以自己發起HTTP請求,這樣我們就不必再運行fetch。因爲HTTP請求和解析操作可能會失敗,因此findlinks聲明了2個返迴值:鏈接列表和錯誤信息。一般而言,HTML的解析器可以處理HTML頁面的錯誤結點,構造出HTML頁面結構,所以解析HTML很少失敗。這意味着如果findlinks函數失敗了,很可能是由於I/O的錯誤導致的。
|
||||
|
||||
<u><i>gopl.io/ch5/findlinks2</i></u>
|
||||
```Go
|
||||
gopl.io/ch5/findlinks2
|
||||
func main() {
|
||||
for _, url := range os.Args[1:] {
|
||||
links, err := findLinks(url)
|
||||
|
@ -37,8 +37,8 @@ genesis: crashed: no parachute: G-switch failed: bad relay orientation
|
||||
|
||||
讓我們來看看處理錯誤的第二種策略。如果錯誤的發生是偶然性的,或由不可預知的問題導致的。一個明智的選擇是重新嚐試失敗的操作。在重試時,我們需要限製重試的時間間隔或重試的次數,防止無限製的重試。
|
||||
|
||||
<u><i>gopl.io/ch5/wait</i></u>
|
||||
```Go
|
||||
gopl.io/ch5/wait
|
||||
// WaitForServer attempts to contact the server of a URL.
|
||||
// It tries for one minute using exponential back-off.
|
||||
// It reports an error if all attempts fail.
|
||||
|
@ -47,8 +47,8 @@
|
||||
|
||||
5.2節的findLinks函數使用了輔助函數visit,遍歷和操作了HTML頁面的所有結點。使用函數值,我們可以將遍歷結點的邏輯和操作結點的邏輯分離,使得我們可以複用遍歷的邏輯,從而對結點進行不同的操作。
|
||||
|
||||
<u><i>gopl.io/ch5/outline2</i></u>
|
||||
```Go
|
||||
gopl.io/ch5/outline2
|
||||
// forEachNode針對每個結點x,都會調用pre(x)和post(x)。
|
||||
// pre和post都是可選的。
|
||||
// 遍歷孩子結點之前,pre被調用
|
||||
|
@ -10,8 +10,8 @@ strings.Map(func(r rune) rune { return r + 1 }, "HAL-9000")
|
||||
|
||||
更爲重要的是,通過這種方式定義的函數可以訪問完整的詞法環境(lexical environment),這意味着在函數中定義的內部函數可以引用該函數的變量,如下例所示:
|
||||
|
||||
<u><i>gopl.io/ch5/squares</i></u>
|
||||
```Go
|
||||
gopl.io/ch5/squares
|
||||
// squares返迴一個匿名函數。
|
||||
// 該匿名函數每次被調用時都會返迴下一個數的平方。
|
||||
func squares() func() int {
|
||||
@ -38,8 +38,8 @@ squares的例子證明,函數值不僅僅是一串代碼,還記録了狀態
|
||||
|
||||
接下來,我們討論一個有點學術性的例子,考慮這樣一個問題:給定一些計算機課程,每個課程都有前置課程,隻有完成了前置課程才可以開始當前課程的學習;我們的目標是選擇出一組課程,這組課程必須確保按順序學習時,能全部被完成。每個課程的前置課程如下:
|
||||
|
||||
<u><i>gopl.io/ch5/toposort</i></u>
|
||||
```Go
|
||||
gopl.io/ch5/toposort
|
||||
// prereqs記録了每個課程的前置課程
|
||||
var prereqs = map[string][]string{
|
||||
"algorithms": {"data structures"},
|
||||
@ -121,8 +121,8 @@ visitAll := func(items []string) {
|
||||
|
||||
讓我們迴到findLinks這個例子。我們將代碼移動到了links包下,將函數重命名爲Extract,在第八章我們會再次用到這個函數。新的匿名函數被引入,用於替換原來的visit函數。該匿名函數負責將新連接添加到切片中。在Extract中,使用forEachNode遍歷HTML頁面,由於Extract隻需要在遍歷結點前操作結點,所以forEachNode的post參數被傳入nil。
|
||||
|
||||
<u><i>gopl.io/ch5/links</i></u>
|
||||
```Go
|
||||
gopl.io/ch5/links
|
||||
// Package links provides a link-extraction function.
|
||||
package links
|
||||
import (
|
||||
@ -172,8 +172,8 @@ func Extract(url string) ([]string, error) {
|
||||
|
||||
下面的函數實現了廣度優先算法。調用者需要輸入一個初始的待訪問列表和一個函數f。待訪問列表中的每個元素被定義爲string類型。廣度優先算法會爲每個元素調用一次f。每次f執行完畢後,會返迴一組待訪問元素。這些元素會被加入到待訪問列表中。當待訪問列表中的所有元素都被訪問後,breadthFirst函數運行結束。爲了避免同一個元素被訪問兩次,代碼中維護了一個map。
|
||||
|
||||
<u><i>gopl.io/ch5/findlinks3</i></u>
|
||||
```Go
|
||||
gopl.io/ch5/findlinks3
|
||||
// breadthFirst calls f for each item in the worklist.
|
||||
// Any items returned by f are added to the worklist.
|
||||
// f is called at most once for each item.
|
||||
@ -219,7 +219,7 @@ func main() {
|
||||
|
||||
讓我們從 https://golang.org 開始,下面是程序的輸出結果:
|
||||
|
||||
```bash
|
||||
```
|
||||
$ go build gopl.io/ch5/findlinks3
|
||||
$ ./findlinks3 https://golang.org
|
||||
https://golang.org/
|
||||
|
@ -4,8 +4,8 @@
|
||||
|
||||
在聲明可變參數函數時,需要在參數列表的最後一個參數類型之前加上省略符號“...”,這表示該函數會接收任意數量的該類型參數。
|
||||
|
||||
<u><i>gopl.io/ch5/sum</i></u>
|
||||
```Go
|
||||
gopl.io/ch5/sum
|
||||
func sum(vals...int) int {
|
||||
total := 0
|
||||
for _, val := range vals {
|
||||
@ -14,6 +14,7 @@ func sum(vals...int) int {
|
||||
return total
|
||||
}
|
||||
```
|
||||
|
||||
sum函數返迴任意個int型參數的和。在函數體中,vals被看作是類型爲[] int的切片。sum可以接收任意數量的int型參數:
|
||||
|
||||
```Go
|
||||
|
@ -4,8 +4,8 @@
|
||||
|
||||
下面的例子獲取HTML頁面併輸出頁面的標題。title函數會檢査服務器返迴的Content-Type字段,如果發現頁面不是HTML,將終止函數運行,返迴錯誤。
|
||||
|
||||
<u><i>gopl.io/ch5/title1</i></u>
|
||||
```Go
|
||||
gopl.io/ch5/title1
|
||||
func title(url string) error {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
@ -34,7 +34,7 @@ func title(url string) error {
|
||||
|
||||
下面展示了運行效果:
|
||||
|
||||
```powershell
|
||||
```
|
||||
$ go build gopl.io/ch5/title1
|
||||
$ ./title1 http://gopl.io
|
||||
The Go Programming Language
|
||||
@ -50,8 +50,8 @@ resp.Body.close調用了多次,這是爲了確保title在所有執行路徑下
|
||||
|
||||
defer語句經常被用於處理成對的操作,如打開、關閉、連接、斷開連接、加鎖、釋放鎖。通過defer機製,不論函數邏輯多複雜,都能保證在任何執行路徑下,資源被釋放。釋放資源的defer應該直接跟在請求資源的語句後。在下面的代碼中,一條defer語句替代了之前的所有resp.Body.Close
|
||||
|
||||
<u><i>gopl.io/ch5/title2</i></u>
|
||||
```Go
|
||||
gopl.io/ch5/title2
|
||||
func title(url string) error {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
@ -73,8 +73,8 @@ func title(url string) error {
|
||||
|
||||
在處理其他資源時,也可以采用defer機製,比如對文件的操作:
|
||||
|
||||
<u><i>io/ioutil</i></u>
|
||||
```Go
|
||||
io/ioutil
|
||||
package ioutil
|
||||
func ReadFile(filename string) ([]byte, error) {
|
||||
f, err := os.Open(filename)
|
||||
@ -100,8 +100,8 @@ func lookup(key string) int {
|
||||
|
||||
調試複雜程序時,defer機製也常被用於記録何時進入和退出函數。下例中的bigSlowOperation函數,直接調用trace記録函數的被調情況。bigSlowOperation被調時,trace會返迴一個函數值,該函數值會在bigSlowOperation退出時被調用。通過這種方式, 我們可以隻通過一條語句控製函數的入口和所有的出口,甚至可以記録函數的運行時間,如例子中的start。需要註意一點:不要忘記defer語句後的圓括號,否則本該在進入時執行的操作會在退出時執行,而本該在退出時執行的,永遠不會被執行。
|
||||
|
||||
<u><i>gopl.io/ch5/trace</i></u>
|
||||
```Go
|
||||
gopl.io/ch5/trace
|
||||
func bigSlowOperation() {
|
||||
defer trace("bigSlowOperation")() // don't forget the
|
||||
extra parentheses
|
||||
@ -195,8 +195,8 @@ func doFile(filename string) error {
|
||||
|
||||
下面的代碼是fetch(1.5節)的改進版,我們將http響應信息寫入本地文件而不是從標準輸出流輸出。我們通過path.Base提出url路徑的最後一段作爲文件名。
|
||||
|
||||
<u><i>gopl.io/ch5/fetch</i></u>
|
||||
```Go
|
||||
gopl.io/ch5/fetch
|
||||
// Fetch downloads the URL and returns the
|
||||
// name and length of the local file.
|
||||
func fetch(url string) (filename string, n int64, err error) {
|
||||
|
Loading…
Reference in New Issue
Block a user