mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2025-08-08 16:21:43 +00:00
回到简体
This commit is contained in:
@@ -1,19 +1,19 @@
|
||||
## 5.6. 匿名函數
|
||||
## 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),这意味着在函数中定义的内部函数可以引用该函数的变量,如下例所示:
|
||||
|
||||
<u><i>gopl.io/ch5/squares</i></u>
|
||||
```Go
|
||||
// squares返迴一個匿名函數。
|
||||
// 該匿名函數每次被調用時都會返迴下一個數的平方。
|
||||
// squares返回一个匿名函数。
|
||||
// 该匿名函数每次被调用时都会返回下一个数的平方。
|
||||
func squares() func() int {
|
||||
var x int
|
||||
return func() int {
|
||||
@@ -30,17 +30,17 @@ 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中。
|
||||
|
||||
接下來,我們討論一個有點學術性的例子,考慮這樣一個問題:給定一些計算機課程,每個課程都有前置課程,隻有完成了前置課程才可以開始當前課程的學習;我們的目標是選擇出一組課程,這組課程必須確保按順序學習時,能全部被完成。每個課程的前置課程如下:
|
||||
接下来,我们讨论一个有点学术性的例子,考虑这样一个问题:给定一些计算机课程,每个课程都有前置课程,只有完成了前置课程才可以开始当前课程的学习;我们的目标是选择出一组课程,这组课程必须确保按顺序学习时,能全部被完成。每个课程的前置课程如下:
|
||||
|
||||
<u><i>gopl.io/ch5/toposort</i></u>
|
||||
```Go
|
||||
// prereqs記録了每個課程的前置課程
|
||||
// prereqs记录了每个课程的前置课程
|
||||
var prereqs = map[string][]string{
|
||||
"algorithms": {"data structures"},
|
||||
"calculus": {"linear algebra"},
|
||||
@@ -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,7 +119,7 @@ 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。
|
||||
|
||||
<u><i>gopl.io/ch5/links</i></u>
|
||||
```Go
|
||||
@@ -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。
|
||||
|
||||
<u><i>gopl.io/ch5/findlinks3</i></u>
|
||||
```Go
|
||||
@@ -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 开始,下面是程序的输出结果:
|
||||
|
||||
```
|
||||
$ go build gopl.io/ch5/findlinks3
|
||||
@@ -232,16 +232,16 @@ https://www.youtube.com/watch?v=XCsL89YtqCs
|
||||
http://research.swtch.com/gotour
|
||||
```
|
||||
|
||||
當所有發現的鏈接都已經被訪問或電腦的內存耗盡時,程序運行結束。
|
||||
当所有发现的链接都已经被访问或电脑的内存耗尽时,程序运行结束。
|
||||
|
||||
**練習5.10:** 重寫topoSort函數,用map代替切片併移除對key的排序代碼。驗證結果的正確性(結果不唯一)。
|
||||
**练习5.10:** 重写topoSort函数,用map代替切片并移除对key的排序代码。验证结果的正确性(结果不唯一)。
|
||||
|
||||
**練習5.11:** 現在線性代數的老師把微積分設爲了前置課程。完善topSort,使其能檢測有向圖中的環。
|
||||
**练习5.11:** 现在线性代数的老师把微积分设为了前置课程。完善topSort,使其能检测有向图中的环。
|
||||
|
||||
**練習5.12:** gopl.io/ch5/outline2(5.5節)的startElement和endElement共用了全局變量depth,將它們脩改爲匿名函數,使其共享outline中的局部變量。
|
||||
**练习5.12:** gopl.io/ch5/outline2(5.5节)的startElement和endElement共用了全局变量depth,将它们修改为匿名函数,使其共享outline中的局部变量。
|
||||
|
||||
**練習5.13:** 脩改crawl,使其能保存發現的頁面,必要時,可以創建目録來保存這些頁面。隻保存來自原始域名下的頁面。假設初始頁面在golang.org下,就不要保存vimeo.com下的頁面。
|
||||
**练习5.13:** 修改crawl,使其能保存发现的页面,必要时,可以创建目录来保存这些页面。只保存来自原始域名下的页面。假设初始页面在golang.org下,就不要保存vimeo.com下的页面。
|
||||
|
||||
**練習5.14:** 使用breadthFirst遍歷其他數據結構。比如,topoSort例子中的課程依賴關繫(有向圖),個人計算機的文件層次結構(樹),你所在城市的公交或地鐵線路(無向圖)。
|
||||
**练习5.14:** 使用breadthFirst遍历其他数据结构。比如,topoSort例子中的课程依赖关系(有向图),个人计算机的文件层次结构(树),你所在城市的公交或地铁线路(无向图)。
|
||||
|
||||
{% include "./ch5-06-1.md" %}
|
||||
|
Reference in New Issue
Block a user