make loop

This commit is contained in:
chai2010
2016-01-08 15:42:18 +08:00
parent 92f44beecc
commit a73964c305
3 changed files with 40 additions and 40 deletions

View File

@@ -1,8 +1,8 @@
### 5.6.1. 警告:捕獲迭代變量
节,将介绍Go法作用域的一陷阱。请务必仔细的阅读,弄清楚发生问题的原因。使是经验丰富的程序员也会在这个问题上犯错误
節,將介紹Go法作用域的一陷阱。請務必仔細的閲讀,弄清楚發生問題的原因。使是經驗豐富的程序員也會在這個問題上犯錯誤
虑这个样一个问题:你被要求首先建一些目,再将目录删除。在下面的例子中我用函数值来完成除操作。下面的示例代需要引入os包。了使代码简单,我忽略了所有的异常处理。
慮這個樣一個問題:你被要求首先建一些目,再將目録刪除。在下面的例子中我用函數值來完成除操作。下面的示例代需要引入os包。了使代碼簡單,我忽略了所有的異常處理。
```Go
var rmdirs []func()
@@ -19,7 +19,7 @@ for _, rmdir := range rmdirs {
}
```
你可能感到惑,为什么要在循环体中用循环变量d值一新的局部量,而不是像下面的代码一样直接使用循环变量dir。需要意,下面的代码是错误的。
你可能感到惑,爲什麽要在循環體中用循環變量d值一新的局部量,而不是像下面的代碼一樣直接使用循環變量dir。需要意,下面的代碼是錯誤的。
```go
var rmdirs []func()
@@ -31,9 +31,9 @@ for _, dir := range tempDirs() {
}
```
问题的原因在于循环变量的作用域。在上面的程序中for循环语句引入了新的词法块循环变量dir在这个词法块中被明。在该循环中生成的所有函值都共享相同的循环变量。需要意,函值中记录的是循环变量的存地址,而不是循环变量某一刻的值。以dir例,后续的迭代会不断更新dir的值当删除操作执行时for循已完成dir中存的值等于最后一次迭代的值。意味着,每次os.RemoveAll的调用删除的都是相同的目
問題的原因在於循環變量的作用域。在上面的程序中for循環語句引入了新的詞法塊循環變量dir在這個詞法塊中被明。在該循環中生成的所有函值都共享相同的循環變量。需要意,函值中記録的是循環變量的存地址,而不是循環變量某一刻的值。以dir例,後續的迭代會不斷更新dir的值當刪除操作執行時for循已完成dir中存的值等於最後一次迭代的值。意味着,每次os.RemoveAll的調用刪除的都是相同的目
通常,了解决这个问题,我们会引入一个与循环变量同名的局部量,作为循环变量的副本。比如下面的量dir虽然这看起很奇怪,但很有用。
通常,了解決這個問題,我們會引入一個與循環變量同名的局部量,作爲循環變量的副本。比如下面的量dir雖然這看起很奇怪,但很有用。
```Go
for _, dir := range tempDirs() {
@@ -42,7 +42,7 @@ for _, dir := range tempDirs() {
}
```
这个问题不仅存在基range的循,在下面的例子中,对循环变量i的使用也存在同样的问题
這個問題不僅存在基range的循,在下面的例子中,對循環變量i的使用也存在同樣的問題
```Go
var rmdirs []func()
@@ -55,4 +55,4 @@ for i := 0; i < len(dirs); i++ {
}
```
如果你使用go第八章或者defer5.8节)会经常遇到此类问题。这不是go或defer本身致的,而是因为它们都会等待循环结束后,再行函值。
如果你使用go第八章或者defer5.8節)會經常遇到此類問題。這不是go或defer本身致的,而是因爲它們都會等待循環結束後,再行函值。

View File

@@ -1,19 +1,19 @@
## 5.6. 匿名函數
有函名的函数只能在包级语法块中被明,通过函数字面量function literal们可绕过这一限,在任何表式中表示一个函数值。函字面量的法和函数声明相似,别在func关键字后没有函名。函值字面量是一种表达式,它的值被成匿名函anonymous function
有函名的函數隻能在包級語法塊中被明,通過函數字面量function literal們可繞過這一限,在任何表式中表示一個函數值。函字面量的法和函數聲明相似,别在func關鍵字後沒有函名。函值字面量是一種表達式,它的值被成匿名函anonymous function
字面量允许我们在使用时函数时,再定它。通过这种技巧,我可以改之前strings.Map的用:
字面量允許我們在使用時函數時,再定它。通過這種技巧,我可以改之前strings.Map的調用:
```Go
strings.Map(func(r rune) rune { return r + 1 }, "HAL-9000")
```
重要的是,通过这种方式定的函可以访问完整的词法环lexical environment意味着在函中定义的内部函可以引用该函数的变量,如下例所示:
重要的是,通過這種方式定的函可以訪問完整的詞法環lexical environment意味着在函中定義的內部函可以引用該函數的變量,如下例所示:
```Go
gopl.io/ch5/squares
// squares返回一个匿名函
// 匿名函每次被调用时都会返回下一个数的平方。
// squares返迴一個匿名函
// 匿名函每次被調用時都會返迴下一個數的平方。
func squares() func() int {
var x int
return func() int {
@@ -30,24 +30,24 @@ func main() {
}
```
squares返另一个类型为 func() int 的函数。对squares的一次调用会生成一局部量x并返回一个匿名函。每次调用时匿名函数时,该函数都会先使x的值加1再返x的平方。第二次用squares时,会生成第二个x变量并返回一个新的匿名函。新匿名函操作的是第二个x变量。
squares返另一個類型爲 func() int 的函數。對squares的一次調用會生成一局部量x併返迴一個匿名函。每次調用時匿名函數時,該函數都會先使x的值加1再返x的平方。第二次調用squares時,會生成第二個x變量併返迴一個新的匿名函。新匿名函操作的是第二個x變量。
squares的例子明,函值不仅仅是一串代码,还记录了状态。在squares中定的匿名部函可以访问和更新squares中的局部量,意味着匿名函和squares中存在量引用。就是函数值属于引用型和函值不可比的原因。Go使用closures术实现函数Go程序也把函值叫做包。
squares的例子明,函值不僅僅是一串代碼,還記録了狀態。在squares中定的匿名部函可以訪問和更新squares中的局部量,意味着匿名函和squares中存在量引用。就是函數值屬於引用型和函值不可比的原因。Go使用closures術實現函數Go程序也把函值叫做包。
过这个例子,我看到量的生命期不由它的作用域squares返回后,变量x仍然式的存在f中。
過這個例子,我看到量的生命期不由它的作用域squares返迴後,變量x仍然式的存在f中。
接下,我们讨论一个有点学术性的例子,考虑这样一个问题:给定一些计算机课程,每个课程都有前置程,有完成了前置程才可以开始当前课程的学习;我的目标是选择出一组课程,这组课程必须确保按顺序学习时,能全部被完成。每个课程的前置程如下:
接下,我們討論一個有點學術性的例子,考慮這樣一個問題:給定一些計算機課程,每個課程都有前置程,有完成了前置程才可以開始當前課程的學習;我的目標是選擇出一組課程,這組課程必鬚確保按順序學習時,能全部被完成。每個課程的前置程如下:
```Go
gopl.io/ch5/toposort
// prereqs记录了每个课程的前置
// prereqs記録了每個課程的前置
var prereqs = map[string][]string{
"algorithms": {"data structures"},
"calculus": {"linear algebra"},
"compilers": {
"data structures",
"formal languages",
"computer organization",
"data structures",
"formal languages",
"computer organization",
},
"data structures": {"discrete math"},
"databases": {"data structures"},
@@ -59,7 +59,7 @@ var prereqs = map[string][]string{
}
```
这类问题被称作拓排序。概念上,前置件可以成有向图。图中的顶点表示程,表示课程间的依赖关系。显然,图中应该无环,这也就是说从某点出发的边,最终不会回到该点。下面的代用深度优先搜索了整张图,获得了符合要求的程序列。
這類問題被稱作拓排序。概念上,前置件可以成有向圖。圖中的頂點表示程,表示課程間的依賴關繫。顯然,圖中應該無環,這也就是説從某點出發的邊,最終不會迴到該點。下面的代用深度優先蒐索了整張圖,獲得了符合要求的程序列。
```Go
func main() {
@@ -91,7 +91,7 @@ func topoSort(m map[string][]string) []string {
}
```
匿名函需要被递归调用时,我们必须首先明一个变量(在上面的例子中,我首先明了 visitAll匿名函数赋值给这个变量。如果不分成部,函字面量无法与visitAll定,我们也无法递归调用该匿名函
匿名函需要被遞歸調用時,我們必鬚首先明一個變量(在上面的例子中,我首先明了 visitAll匿名函數賦值給這個變量。如果不分成部,函字面量無法與visitAll定,我們也無法遞歸調用該匿名函
```Go
visitAll := func(items []string) {
@@ -101,7 +101,7 @@ visitAll := func(items []string) {
}
```
在topsort中首先prereqs中的key排序用visitAll。因prereqs映射的是数组而不是更复杂的map所以数据的遍次序是固定的,意味着你每次行topsort得到的出都是一的。 topsort的输出结果如下:
在topsort中首先prereqs中的key排序調用visitAll。因prereqs映射的是數組而不是更複雜的map所以數據的遍次序是固定的,意味着你每次行topsort得到的出都是一的。 topsort的輸出結果如下:
```
1: intro to programming
@@ -119,11 +119,11 @@ visitAll := func(items []string) {
13: programming languages
```
让我们回到findLinks这个例子。我们将代码移动到了links包下将函数重命名Extract在第八章我们会再次用到这个函数。新的匿名函被引入,用于替换原来的visit函数。该匿名函数负责将新连接添加到数组中。在Extract中使用forEachNode遍HTML面,由Extract需要在遍历结点前操作结点所以forEachNode的post参数被传入nil。
讓我們迴到findLinks這個例子。我們將代碼移動到了links包下將函數重命名Extract在第八章我們會再次用到這個函數。新的匿名函被引入,用於替換原來的visit函數。該匿名函數負責將新連接添加到數組中。在Extract中使用forEachNode遍HTML面,由Extract需要在遍歷結點前操作結點所以forEachNode的post參數被傳入nil。
```Go
gopl.io/ch5/links
// Package links provides a link-extraction function.
// Package links provides a link-extraction function.
package links
import (
"fmt"
@@ -166,11 +166,11 @@ func Extract(url string) ([]string, error) {
}
```
上面的代码对之前的版本做了改进,现在links中存的不是href性的原始值,而是通resp.Request.URL解析的值。解析后,这些连接以绝对路径的形式存在可以直接被http.Get访问
上面的代碼對之前的版本做了改進,現在links中存的不是href性的原始值,而是通resp.Request.URL解析的值。解析後,這些連接以絶對路徑的形式存在可以直接被http.Get訪問
网页抓取的核心问题就是如何遍历图。在topoSort的例子中展示了深度先遍,在网页抓取中,我们会展示如何用广度优先遍历图。在第8章们会介绍如何深度先和广度优先结合使用。
網頁抓取的核心問題就是如何遍歷圖。在topoSort的例子中展示了深度先遍,在網頁抓取中,我們會展示如何用廣度優先遍歷圖。在第8章們會介紹如何深度先和廣度優先結合使用。
下面的函数实现了广度优先算法。用者需要入一初始的待访问列表和一个函数f。待访问列表中的每元素被定义为string型。广度优先算法会为每个元素用一次f。每次f行完毕后,会返回一组待访问元素。些元素被加入到待访问列表中。当待访问列表中的所有元素都被访问后breadthFirst函数运行结束。了避免同一元素被访问两次,代码中维护了一map。
下面的函數實現了廣度優先算法。調用者需要入一初始的待訪問列表和一個函數f。待訪問列表中的每元素被定義爲string型。廣度優先算法會爲每個元素調用一次f。每次f行完畢後,會返迴一組待訪問元素。些元素被加入到待訪問列表中。當待訪問列表中的所有元素都被訪問後breadthFirst函數運行結束。了避免同一元素被訪問兩次,代碼中維護了一map。
```Go
gopl.io/ch5/findlinks3
@@ -192,9 +192,9 @@ func breadthFirst(f func(item string) []string, worklist []string) {
}
```
就像我在章3解的那append的参数“f(item)...”,会将f返的一元素一个个添加到worklist中。
就像我在章3解的那append的參數“f(item)...”,會將f返的一元素一個個添加到worklist中。
在我们网页抓取器中,元素的型是url。crawl函数会将URL出,提取其中的新接,并将这些新接返。我们会将crawl作为参数传递给breadthFirst。
在我們網頁抓取器中,元素的型是url。crawl函數會將URL出,提取其中的新接,併將這些新接返。我們會將crawl作爲參數傳遞給breadthFirst。
```go
func crawl(url string) []string {
@@ -207,7 +207,7 @@ func crawl(url string) []string {
}
```
了使抓取器开始运行,我用命令行入的参数作为初始的待访问url。
了使抓取器開始運行,我用命令行入的參數作爲初始的待訪問url。
```Go
func main() {
@@ -217,7 +217,7 @@ func main() {
}
```
让我们从https://golang.org始,下面是程序的输出结果:
讓我們從https://golang.org始,下面是程序的輸出結果:
```bash
$ go build gopl.io/ch5/findlinks3
@@ -232,12 +232,12 @@ https://www.youtube.com/watch?v=XCsL89YtqCs
http://research.swtch.com/gotour
```
所有发现的链接都已经被访问或电脑的内存耗尽时,程序运行结束。
所有發現的鏈接都已經被訪問或電腦的內存耗盡時,程序運行結束。
**练习5.10**重topoSort函用map代替数组并移除key的排序代码。验证结果的正性(果不唯一)。
**练习5.11**现在线性代的老把微积分设为了前置程。完善topSort使其能检测有向中的
**练习5.12**gopl.io/ch5/outline25.5的startElement和endElement共用了全局量depth将它们修改为匿名函使其共享outline中的局部量。
**练习5.13**改crawl使其能保存发现的页面,必要,可以建目录来保存这些页面。保存自原始域名下的面。假初始面在golang.org下就不要保存vimeo.com下的面。
**练习5.14**使用breadthFirst遍其他数据结构。比如topoSort例子中的程依赖关系(有向,个人计算机的文件层次结构(树),你所在城市的公交或地铁线路(无向图)。
**練習5.10**重topoSort函用map代替數組併移除key的排序代碼。驗證結果的正性(果不唯一)。
**練習5.11**現在線性代的老把微積分設爲了前置程。完善topSort使其能檢測有向中的
**練習5.12**gopl.io/ch5/outline25.5的startElement和endElement共用了全局量depth將它們脩改爲匿名函使其共享outline中的局部量。
**練習5.13**改crawl使其能保存發現的頁面,必要,可以建目録來保存這些頁面。保存自原始域名下的面。假初始面在golang.org下就不要保存vimeo.com下的面。
**練習5.14**使用breadthFirst遍其他數據結構。比如topoSort例子中的程依賴關繫(有向,個人計算機的文件層次結構(樹),你所在城市的公交或地鐵線路(無向圖)。
{% include "./ch5-06-1.md" %}
{% include "./ch5-06-1.md" %}