第五章
- 函数声明
- 递归
- 多返回值
- 错误
- 函数变量
- 匿名函数
- 变长函数
- 延迟函数调用
- 宕机
- 恢复
练习5.1
改变findlinks程序,使用递归调用visit(而不是循环)遍历n.FirstChild链表
- 可怕的递归ヽ(*。>Д<)o゜可怕
- 循环还挺好理解的,可是改成递归真。。。不好理解
- 本题就修改一句就好了
1
| return visit(visit(links, n.FirstChild), n.NextSibling)
|
练习5.2
写一个函数,用于统计HTML文档树内所有的元素个数,如p,div,span等
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
| 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, "findelems: %v\n", err) os.Exit(1) } elements := map[string]int{} visit(elements, doc) for elem, count := range elements { fmt.Printf("%s\t%d\n", elem, count) } } func visit(e map[string]int, n *html.Node) { if n.Type == html.ElementNode { e[n.Data]++ } for c := n.FirstChild; c != nil; c = c.NextSibling { visit(e, c) } }
|
练习5.3
写一个函数,用于输出HTML文档树种所有文本节点的内容.但不包括<script>
或<style>
元素,因为这些内容在web浏览器中是不可见的
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
| 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, "findtexts: %v\n", err) os.Exit(1) } visit(doc) } func visit(n *html.Node) { if n != nil && n.Type == html.ElementNode { if n.Data == "script" || n.Data == "style" { return } } if n.Type == html.TextNode { fmt.Println(n.Data) } for c := n.FirstChild; c != nil; c = c.NextSibling { visit(c) } }
|
练习5.4:
扩展visit函数,使之能够获得到其他种类的链接地址,比如图片、脚本或样式表的链接
练习5.5:
实现函数countWordsAndImages
- ElementNode、TextNode、ComentNode、DoctypeNode、ErrorNode
- 判断为TextNode的时候统计字符个数,n.Data为img的时候图片数目累加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| func countWordsAndImages(n *html.Node) (words, images int) { if n.Type == html.TextNode{ scanner := bufio.NewScanner(strings.NewReader(n.Data)) scanner.Split(bufio.ScanWords) for scanner.Scan() { words++ } } if n.Type == html.ElementNode && n.Data == "img" { images++ } for c := n.FirstChild; c != nil; c = c.NextSibling { ws, is := countWordsAndImages(c) words += ws images += is } return words, images }
|
练习5.6:
修改gopl.io/ch3/surface中的函数corner,以使用命名的结果以及裸返回语句
- 最简单的概念题了,返回参数规范化
- return的时候忽略后面的语句,不过我想我写代码的时候可能不会选择忽略
1 2 3 4 5 6 7 8 9 10
| func corner(i, j int) (sx, sy float64) { x := xyrange * (float64(i)/cells - 0.5) y := xyrange * (float64(j)/cells - 0.5) z := f(x, y) sx = width/2 + (x-y)*cos30*xyscale sy = height/2 + (x+y)*sin30*xyscale - z*zscale return }
|
练习5.7:
开发startElement和endElement函数并应用到一个普通的HTML输出代码中.输出注释节点、文本节点和所有元素属性(<a href='...'>)
.当一个元素没有子节点时,使用简短的形式,比如<img/>
而不是<img></img>
.写一个测试程序保证输出可以正确解析
练习5.8:
修改forEachNode使得pre和post函数返回一个布尔型的结果来确定遍历是否继续下去.使用它写一个函数ElementByID,该函数使用下面的函数签名并且找到第一个符合id属性的HTML元素.函数在找到符合条件的元素时应该尽快停止遍历.func 使用它写一个函数ElementByID(doc html.Node, id string) html.Node
- 终止递归,每次执行pre、post的时候选择判断返回值,如果为真表示找到直接进行return
- 因为传递给forEachNode的是html.Node的指针,当return的时候即为找到的第一个id,如果遍历完了还没有,最终返回值为n.FirstChild会为nil
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| func ElementByID(doc *html.Node, id string) *html.Node { var elem *html.Node forEachNode(doc, func(n *html.Node) bool { if n.Type == html.ElementNode { for _, a := range n.Attr { if a.Key == "id" && a.Val == id { elem = n return false } } } return true }, nil) return elem }
|
练习5.9:
写一个函数expand(s string,f func(string)string)string,该函数替换参数s中每一个子字符串”$foo”为f(“foo”)的返回值
- 感觉应该是类似于
${foo}
这样,否则$后面会有很长的字符串,不利于模板进行分割
1 2 3 4 5 6 7 8
| package main import "strings" func Expand(s string, f func(string) string) string { ret := strings.Replace(s, "$foo", f("foo"), 1024) return ret }
|
练习5.10:
重写topSort以使用map代替slice并去掉开头的排序.结果不是唯一的,验证这个结果是合法的拓扑排序
练习5.11:
现在有”线性代数”这门课程,它的先决课程是”微积分”.拓展topSort以函数输出结果
练习5.12:
5.5节(gopl.io/ch5/outline2)的startElement和endElement函数共享一个全局变量depth.把它们变为匿名函数以共享outline函数的一个局部变量
练习5.13:
修改crawl函数保存找到的页面,根据需要创建目录.不要保存不同域名下的页面.比如本来的页面来自golang.org,那么就把它们保存下来但是不要保存vimeo.com下的页面
练习5.14:
使用广度优先遍历搜索一个不同的拓扑结构.比如,你可以借鉴拓扑排序的例子里的课程依赖关系,计算机文件系统的分层结构,或者从当前城市的官网上下载公共汽车或者地铁的路线图.
练习5.15:
模仿sum写两个变长函数max和min.当不带任何参数调用这些函数的时候应该怎么应对?编写类似函数的变种,要求至少需要一个参数
- 当缺少参数的时候直接panic
- 比较的时候直接使用math模块的最大值最小值使用
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
| package main import "math" func Max(in ...int) int { if len(in) == 0 { panic("At least one element") } ret := math.MinInt64 for _, v := range in { if v > ret { ret = v } } return ret } func Min(in ...int) int { if len(in) == 0 { panic("At least one element") } ret := math.MaxInt64 for _, v := range in { if v < ret { ret = v } } return ret }
|
练习5.16:
写一个变长版本的strings.Join函数
- 可以直接使用strings.Join返回
- 选择使用bytes.Buffer模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package main import ( "strings" "bytes" "fmt" ) func Join(in ...string) string { return strings.Join(in, "") } func Join2(in ...string) string { var buf bytes.Buffer for _, v := range in { buf.Write([]byte(v)) } return buf.String() } func main() { fmt.Println(Join2("a", "b", "c")) }
|
练习5.17:
写一个变长函数ElementsByTagname,已知一个HTML节点树和零个或多个名字,返回所有符合给出名字的元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package main import "golang.org/x/net/html" func ElementsByTagName(doc *html.Node, name ...string) []*html.Node { if len(name) == 0 { return nil } var visit func(elems []*html.Node, n *html.Node) []*html.Node visit = func(elems []*html.Node, n *html.Node) []*html.Node { for _, tag := range name { if n.Type == html.ElementNode && n.Data == tag { elems = append(elems, n) } } for c := n.FirstChild; c != nil; c = c.NextSibling { elems = visit(elems, c) } return elems } return visit(nil, doc) }
|
练习5.18:
不改变原本的行为,重写fetch函数以使用defer语句关闭打开的可写的文件
- 基本不变,直接将原有的写法放置到嵌套的defer函数内部,这样关闭失败的时候同样的逻辑会写入err
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| func fetch(url string) (filename string, n int64, err error) { resp, err := http.Get(url) if err != nil { return "", 0, err } defer resp.Body.Close() local := path.Base(resp.Request.URL.Path) if local == "/" { local = "index.html" } f, err := os.Create(local) if err != nil { return "", 0, err } defer func() { if closeErr := f.Close(); err == nil { err = closeErr } }() n, err = io.Copy(f, resp.Body) return local, n, err }
|
练习5.19:
使用panic和recover写一个函数,它没有return语句,但是能够返回一个非零的值
- 利用defer语句的作用域和函数同级,在defer语句中执行recover再设置函数返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package main import "fmt" func NoReturn() (r int) { defer func() { if p := recover(); p != nil { r = 1 } else { r = 2 } }() panic(1) } func main() { fmt.Println(NoReturn()) }
|