回到简体

This commit is contained in:
chai2010
2016-02-15 11:06:34 +08:00
parent 9e878f9944
commit 2b37b23285
177 changed files with 2354 additions and 2354 deletions

View File

@@ -1,6 +1,6 @@
## 5.1. 函數聲
## 5.1. 函数声
數聲明包括函名、形式參數列表、返值列表(可省略)以及函數體
数声明包括函名、形式参数列表、返值列表(可省略)以及函数体
```Go
func name(parameter-list) (result-list) {
@@ -8,8 +8,8 @@ func name(parameter-list) (result-list) {
}
```
形式參數列表描述了函數的參數名以及參數類型。這些參數作爲局部量,其值由參數調用者提供。返值列表描述了函數返迴值的量名以及型。如果函數返迴一個無名變量或者有返值,返值列表的括是可以省略的。如果一個函數聲明不包括返值列表,那麽函數體執行完畢後,不會返迴任何值。
在hypot函中,
形式参数列表描述了函数的参数名以及参数类型。这些参数作为局部量,其值由参数调用者提供。返值列表描述了函数返回值的量名以及型。如果函数返回一个无名变量或者有返值,返值列表的括是可以省略的。如果一个函数声明不包括返值列表,那么函数体执行完毕后,不会返回任何值。
在hypot函中,
```Go
func hypot(x, y float64) float64 {
@@ -18,18 +18,18 @@ func hypot(x, y float64) float64 {
fmt.Println(hypot(3,4)) // "5"
```
x和y是形名,3和4是調用時的傳入的實數,函數返迴了一float64型的值。
值也可以像形式參數一樣被命名。在這種情況下,每個返迴值被明成一局部量,併根據該返迴值的型,其初始化0。
如果一個函數在聲明時,包含返值列表,該函數必須以 return語句結尾,除非函數明顯無法運行到結尾處。例如函數在結尾時調用了panic常或函中存在限循
x和y是形名,3和4是调用时的传入的实数,函数返回了一float64型的值。
值也可以像形式参数一样被命名。在这种情况下,每个返回值被明成一局部量,并根据该返回值的型,其初始化0。
如果一个函数在声明时,包含返值列表,该函数必须以 return语句结尾,除非函数明显无法运行到结尾处。例如函数在结尾时调用了panic常或函中存在限循
正如hypot一,如果一組形參或返值有相同的型,我不必爲每個形參都寫出參數類型。下面2個聲明是等的:
正如hypot一,如果一组形参或返值有相同的型,我不必为每个形参都写出参数类型。下面2个声明是等的:
```Go
func f(i, j, k int, s, t string) { /* ... */ }
func f(i int, j int, k int, s string, t string) { /* ... */ }
```
下面,我們給出4方法聲明擁有2int型參數和1int型返值的函.blank identifier(譯者註:卽下文的_符)可以強調某個參數未被使用。
下面,我们给出4方法声明拥有2int型参数和1int型返值的函.blank identifier(译者注:即下文的_符)可以强调某个参数未被使用。
```Go
func add(x int, y int) int {return x + y}
@@ -43,15 +43,15 @@ fmt.Printf("%T\n", first) // "func(int, int) int"
fmt.Printf("%T\n", zero) // "func(int, int) int"
```
數的類型被稱爲函數的標識符。如果兩個函數形式參數列表和返值列表中的變量類型一一對應,那麽這兩個函數被認爲有相同的型和標識符。形和返值的量名不影響函數標識符也不影響它們是否可以以省略參數類型的形式表示。
数的类型被称为函数的标识符。如果两个函数形式参数列表和返值列表中的变量类型一一对应,那么这两个函数被认为有相同的型和标识符。形和返值的量名不影响函数标识符也不影响它们是否可以以省略参数类型的形式表示。
每一次函數調用都必按照聲明順序爲所有參數提供實參參數值。在函數調用時Go語言沒有默認參數值,也有任何方法可以通過參數名指定形,因此形和返值的量名對於函數調用者而言有意
每一次函数调用都必按照声明顺序为所有参数提供实参参数值。在函数调用时Go语言没有默认参数值,也有任何方法可以通过参数名指定形,因此形和返值的量名对于函数调用者而言有意
在函數體中,函的形參作爲局部量,被初始化爲調用者提供的值。函的形和有名返值作爲函數最外的局部量,被存在相同的詞法塊中。
在函数体中,函的形参作为局部量,被初始化为调用者提供的值。函的形和有名返值作为函数最外的局部量,被存在相同的词法块中。
實參通過值的方式傳遞,因此函的形參是實參的拷貝。對形參進行脩改不會影響實參。但是,如果實參包括引用型,如指slice(切片)、map、function、channel等型,實參可能會由於函數的簡介引用被改。
实参通过值的方式传递,因此函的形参是实参的拷贝。对形参进行修改不会影响实参。但是,如果实参包括引用型,如指slice(切片)、map、function、channel等型,实参可能会由于函数的简介引用被改。
你可能會偶爾遇到有函數體的函數聲明,表示該函數不是以Go實現的。這樣的聲明定了函數標識符。
你可能会偶尔遇到有函数体的函数声明,表示该函数不是以Go实现的。这样的声明定了函数标识符。
```Go
package math

View File

@@ -1,10 +1,10 @@
## 5.2. 遞歸
## 5.2. 递归
可以是遞歸的,意味着函可以直接或接的調用自身。對許多問題而言,遞歸是一種強有力的技,例如處理遞歸的數據結構。在4.4,我們通過遍歷二叉樹來實現簡單的插入排序,在本章,我再次使用它來處理HTML文件。
可以是递归的,意味着函可以直接或接的用自身。对许多问题而言,递归是一种强有力的技,例如处理递归的数据结构。在4.4,我们通过遍历二叉树来实现简单的插入排序,在本章,我再次使用它来处理HTML文件。
下文的示例代使用了非標準包 golang.org/x/net/html 解析HTML。golang.org/x/... 目下存了一些由Go糰隊設計、維護,對網絡編程、国化文件理、移平台、圖像處理、加密解密、開發者工具提供支持的展包。未將這些擴展包加入到標準庫原因有二,一是部分包仍在開發中,二是大多Go言的開發者而言,展包提供的功能很少被使用。
下文的示例代使用了非标准包 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的部分api如下所示。html.Parse函数读入一bytes.解析,返html.node型的HTML页面树状结构根节点。HTML有很多型的结点如text文本,commnets注释)类型,在下面的例子中,我们 只关注< name key='value' >形式的结点
<u><i>golang.org/x/net/html</i></u>
```Go
@@ -35,7 +35,7 @@ type Attribute struct {
func Parse(r io.Reader) (*Node, error)
```
main函解析HTML標準輸入,通過遞歸函數visit得links接),打印出些links
main函解析HTML标准输入,通过递归函数visit得links接),打印出些links
<u></i>gopl.io/ch5/findlinks1</i></u>
```Go
@@ -61,7 +61,7 @@ func main() {
}
```
visit函數遍歷HTML的節點樹,從每一anchor元素的href屬性獲得link,將這些links存入字符串數組中,併返迴這個字符串數組
visit函数遍历HTML的节点树,从每一anchor元素的href属性获得link,将这些links存入字符串数组中,并返回这个字符串数组
```Go
// visit appends to links each link found in n and returns the result.
@@ -80,9 +80,9 @@ func visit(links []string, n *html.Node) []string {
}
```
了遍歷結點n的所有後代結點每次遇到n的孩子結點時visit遞歸的調用自身。些孩子結點存放在FirstChild表中。
了遍历结点n的所有后代结点每次遇到n的孩子结点时visit递归的调用自身。些孩子结点存放在FirstChild表中。
讓我們以Go的主golang.org爲目標,運行findlinks。我以fetch1.5章)的出作findlinks的入。下面的出做了簡化處理。
让我们以Go的主golang.org为目标,运行findlinks。我以fetch1.5章)的出作findlinks的入。下面的出做了简化处理。
```
$ go build gopl.io/ch1/fetch
@@ -102,9 +102,9 @@ https://golang.org/dl/
http://www.google.com/intl/en/policies/privacy/
```
意在面中出現的鏈接格式,在之後我們會介紹如何將這些鏈接,根根路 https://golang.org )生成可以直接訪問的url。
意在面中出现的链接格式,在之后我们会介绍如何将这些链接,根根路 https://golang.org )生成可以直接访问的url。
在函outline中們通過遞歸的方式遍歷整個HTML結點樹,併輸出樹的結構。在outline部,每遇到一HTML元素標籤,就其入棧,併輸出。
在函outline中们通过递归的方式遍历整个HTML结点树,并输出树的结构。在outline部,每遇到一HTML元素标签,就其入栈,并输出。
<u><i>gopl.io/ch5/outline</i></u>
```Go
@@ -127,9 +127,9 @@ func outline(stack []string, n *html.Node) {
}
```
有一值得outline有入操作,但有相對應的出操作。outline調用自身,被調用者接收的是stack的拷。被調用者的入操作,改的是stack的拷,而不是調用者的stack,因對當函數返迴時,調用者的stack未被改。
有一值得outline有入操作,但有相对应的出操作。outline用自身,被用者接收的是stack的拷。被用者的入操作,改的是stack的拷,而不是用者的stack,因对当函数返回时,调用者的stack未被改。
下面是 https://golang.org 面的簡要結構:
下面是 https://golang.org 面的简要结构:
```
$ go build gopl.io/ch5/outline
@@ -149,14 +149,14 @@ $ ./fetch https://golang.org | ./outline
...
```
正如你在上面實驗中所大部分HTML頁面隻需幾層遞歸就能被理,但仍然有些面需要深次的遞歸
正如你在上面实验中所大部分HTML页面只需几层递归就能被理,但仍然有些面需要深次的递归
大部分編程語言使用固定大小的函數調用棧,常的大小64KB到2MB不等。固定大小棧會限製遞歸的深度,你用遞歸處理大量數據時,需要避免溢出;除此之外,還會導致安全性問題。與相反,Go言使用可變棧,棧的大小按需增加(初始很小)。使得我使用遞歸時不必考溢出和安全問題
大部分编程语言使用固定大小的函数调用栈,常的大小64KB到2MB不等。固定大小栈会限制递归的深度,你用递归处理大量数据时,需要避免溢出;除此之外,还会导致安全性问题。与相反,Go言使用可变栈,栈的大小按需增加(初始很小)。使得我使用递归时不必考溢出和安全问题
**練習 5.1** 改findlinks代中遍n.FirstChild表的部分,將循環調用visit改成遞歸調用。
**练习 5.1** 改findlinks代中遍n.FirstChild表的部分,将循环调用visit改成递归调用。
**練習 5.2** 編寫函數,記録在HTML中出的同名元素的次
**练习 5.2** 编写函数,记录在HTML中出的同名元素的次
**練習 5.3** 編寫函數輸出所有text結點的內容。意不要訪問`<script>``<style>`元素,因爲這些元素對瀏覽者是不可的。
**练习 5.3** 编写函数输出所有text结点的内容。意不要访问`<script>``<style>`元素,因为这些元素对浏览者是不可的。
**練習 5.4** 展vist函,使其能夠處理其他型的結點如images、scripts和style sheets。
**练习 5.4** 展vist函,使其能够处理其他型的结点如images、scripts和style sheets。

View File

@@ -1,8 +1,8 @@
## 5.3. 多返
## 5.3. 多返
在Go中個函數可以返迴多個值。我們已經在之前例子中看到許多標準庫中的函數返迴2個值,一是期望得到的返值,另一是函數出錯時的錯誤信息。下面的例子展示如何編寫多返值的函
在Go中个函数可以返回多个值。我们已经在之前例子中看到许多标准库中的函数返回2个值,一是期望得到的返值,另一是函数出错时的错误信息。下面的例子展示如何编写多返值的函
下面的程序是findlinks的改版本。脩改後的findlinks可以自己起HTTP求,這樣我們就不必再行fetch。因HTTP求和解析操作可能會失敗因此findlinks明了2個返迴值:接列表和錯誤信息。一般而言HTML的解析器可以理HTML面的錯誤結點,構造出HTML頁面結構所以解析HTML很少失敗。這意味着如果findlinks函數失敗了,很可能是由I/O的錯誤導致的。
下面的程序是findlinks的改版本。修改后的findlinks可以自己起HTTP求,这样我们就不必再行fetch。因HTTP求和解析操作可能会失败因此findlinks明了2个返回值:接列表和错误信息。一般而言HTML的解析器可以理HTML面的错误结点,构造出HTML页面结构所以解析HTML很少失败。这意味着如果findlinks函数失败了,很可能是由I/O的错误导致的。
<u><i>gopl.io/ch5/findlinks2</i></u>
```Go
@@ -39,23 +39,23 @@ func findLinks(url string) ([]string, error) {
}
```
在findlinks中有4return句,每一return都返了一值。前三returnhttp和html包中的錯誤信息傳遞給findlinks的調用者。第一return直接返迴錯誤信息,其他兩處通過fmt.Errorf§7.8輸出詳細的錯誤信息。如果findlinks成功束,最的return語句將一組解析得的接返迴給用戶
在findlinks中有4return句,每一return都返了一值。前三returnhttp和html包中的错误信息传递给findlinks的用者。第一return直接返回错误信息,其他两处通过fmt.Errorf§7.8输出详细的错误信息。如果findlinks成功束,最的return语句将一组解析得的接返回给用户
在finallinks中們必須確保resp.Body被關閉,釋放網絡資源。然Go的垃圾迴收機製會迴收不被使用的存,但是不包括操作繫統層面的源,比如打的文件、網絡連接。因此我們必須顯式的釋放這些資源。
在finallinks中们必须确保resp.Body被关闭,释放网络资源。然Go的垃圾回收机制会回收不被使用的存,但是不包括操作系统层面的源,比如打的文件、网络连接。因此我们必须显式的释放这些资源。
調用多返值函數時,返迴給調用者的是一值,調用者必須顯式的將這些值分配給變量:
用多返值函数时,返回给调用者的是一值,用者必须显式的将这些值分配给变量:
```Go
links, err := findLinks(url)
```
如果某值不被使用,可以其分配blank identifier:
如果某值不被使用,可以其分配blank identifier:
```Go
links, _ := findLinks(url) // errors ignored
```
個函數內部可以另一有多返值的函數作爲返迴值,下面的例子展示了findLinks有相同功能的函數,兩者的别在下面的例子先輸出參數
个函数内部可以另一有多返值的函数作为返回值,下面的例子展示了findLinks有相同功能的函数,两者的别在下面的例子先输出参数
```Go
func findLinksLog(url string) ([]string, error) {
@@ -64,7 +64,7 @@ func findLinksLog(url string) ([]string, error) {
}
```
當你調用接受多參數的函數時,可以將一個返迴多參數的函數作爲該函數的參數。雖然這很少出現在實際生産代碼中,但這個特性在debug很方便,我們隻需要一條語句就可以出所有的返值。下面的代是等的:
当你调用接受多参数的函数时,可以将一个返回多参数的函数作为该函数的参数。虽然这很少出现在实际生产代码中,但这个特性在debug很方便,我们只需要一条语句就可以出所有的返值。下面的代是等的:
```Go
log.Println(findLinks(url))
@@ -72,7 +72,7 @@ links, err := findLinks(url)
log.Println(links, err)
```
準確的變量名可以傳達函數返迴值的含。尤其在返值的型都相同,就像下面這樣
准确的变量名可以传达函数返回值的含。尤其在返值的型都相同,就像下面这样
```Go
func Size(rect image.Rectangle) (width, height int)
@@ -80,9 +80,9 @@ func Split(path string) (dir, file string)
func HourMinSec(t time.Time) (hour, minute, second int)
```
然良好的命名很重要,但你也不必每一個返迴值都取一個適當的名字。比如,按照例,函的最後一個bool型的返值表示函是否行成功error型的返值代表函數的錯誤信息,對於這些類似的例,我不必思考合的命名,它們都無需解
然良好的命名很重要,但你也不必每一个返回值都取一个适当的名字。比如,按照例,函的最后一个bool型的返值表示函是否行成功error型的返值代表函数的错误信息,对于这些类似的例,我不必思考合的命名,它们都无需解
如果一個函數將所有的返值都示的量名,那麽該函數的return句可以省略操作數。這稱之爲bare return。
如果一个函数将所有的返值都示的量名,那么该函数的return句可以省略操作数。这称之为bare return。
```Go
// CountWordsAndImages does an HTTP GET request for the HTML
@@ -104,14 +104,14 @@ func CountWordsAndImages(url string) (words, images int, err error) {
func countWordsAndImages(n *html.Node) (words, images int) { /* ... */ }
```
按照返值列表的次序,返所有的返值,在上面的例子中,每一return句等價於
按照返值列表的次序,返所有的返值,在上面的例子中,每一return句等价于
```Go
return words, images, err
```
當一個函數有多return句以及多返迴值時bare return 可以少代的重,但是使得代碼難以被理解。舉個例子,如果你有仔細的審査代碼,很難發現前2return等價於 return 0,0,errGo會將返迴值 words和images在函數體的開始處,根據它們的類型,其初始化0後一處return等價於 return wordsimagenil。基以上原因,不宜度使用bare return。
当一个函数有多return句以及多返回值时bare return 可以少代的重,但是使得代码难以被理解。举个例子,如果你有仔细的审查代码,很难发现前2return等价于 return 0,0,errGo会将返回值 words和images在函数体的开始处,根据它们的类型,其初始化0后一处return等价于 return wordsimagenil。基以上原因,不宜度使用bare return。
**練習 5.5** 實現countWordsAndImages。參考練習4.9如何分
**练习 5.5** 实现countWordsAndImages。参考练习4.9如何分
**練習 5.6** 改gopl.io/ch3/surface (§3.2) 中的corner函數,將返迴值命名,使用bare return。
**练习 5.6** 改gopl.io/ch3/surface (§3.2) 中的corner函数,将返回值命名,使用bare return。

View File

@@ -1,8 +1,8 @@
### 5.4.1. 錯誤處理策略
### 5.4.1. 错误处理策略
一次函數調用返迴錯誤時,調用者有應該選擇何時的方式處理錯誤。根據情況的不同,有很多理方式,讓我們來看看常用的五方式。
一次函数调用返回错误时,调用者有应该选择何时的方式处理错误。根据情况的不同,有很多理方式,让我们来看看常用的五方式。
首先,也是最常用的方式是傳播錯誤。這意味着函中某子程序的失敗,會變成該函數的失。下面,我以5.3的findLinks函數作爲例子。如果findLinkshttp.Get的調用失findLinks直接將這個HTTP錯誤返迴給調用者:
首先,也是最常用的方式是传播错误。这意味着函中某子程序的失败,会变成该函数的失。下面,我以5.3的findLinks函数作为例子。如果findLinkshttp.Get的用失findLinks直接将这个HTTP错误返回给调用者:
```Go
resp, err := http.Get(url)
@@ -11,7 +11,7 @@ if err != nil{
}
```
當對html.Parse的調用失敗時findLinks不直接返html.Parse的錯誤,因缺少兩條重要信息1、錯誤發生在解析器2、url已被解析。些信息有助於錯誤的處findLinks會構造新的錯誤信息返迴給調用者:
当对html.Parse的用失败时findLinks不直接返html.Parse的错误,因缺少两条重要信息1、错误发生在解析器2、url已被解析。些信息有助于错误的处findLinks会构造新的错误信息返回给调用者:
```Go
doc, err := html.Parse(resp.Body)
@@ -21,21 +21,21 @@ if err != nil {
}
```
fmt.Errorf函使用fmt.Sprintf格式化錯誤信息併返迴。我使用該函數前綴添加外的上下文信息到原始錯誤信息。當錯誤最終由main函數處理時,錯誤信息提供清晰的原因到果的因果,就像美国宇航局事故調査時做的那
fmt.Errorf函使用fmt.Sprintf格式化错误信息并返回。我使用该函数前缀添加外的上下文信息到原始错误信息。当错误最终由main函数处理时,错误信息提供清晰的原因到果的因果,就像美国宇航局事故调查时做的那
```
genesis: crashed: no parachute: G-switch failed: bad relay orientation
```
於錯誤信息常是以鏈式組合在一起的,所以錯誤信息中避免大寫和換行符。最終的錯誤信息可能很,我可以通過類似grep的工具處理錯誤信息(譯者註grep是一文本索工具)。
于错误信息常是以链式组合在一起的,所以错误信息中避免大写和换行符。最终的错误信息可能很,我可以通过类似grep的工具处理错误信息(译者注grep是一文本索工具)。
編寫錯誤信息,我們要確保錯誤信息對問題細節的描述是詳盡的。尤其是要註意錯誤信息表的一致性,相同的函或同包的同一組函數返迴的錯誤在構成和理方式上是相似的。
编写错误信息,我们要确保错误信息对问题细节的描述是详尽的。尤其是要注意错误信息表的一致性,相同的函或同包的同一组函数返回的错误在构成和理方式上是相似的。
以OS包OS包保文件操作如os.Open、Read、Write、Close的每個錯誤的描述不僅僅包含錯誤的原因(如無權限,文件目不存在)也包含文件名,這樣調用者在造新的錯誤信息時無需再添加些信息。
以OS包OS包保文件操作如os.Open、Read、Write、Close的每个错误的描述不仅仅包含错误的原因(如无权限,文件目不存在)也包含文件名,这样调用者在造新的错误信息时无需再添加些信息。
一般而言,被調函數f(x)會將調用信息和參數信息作爲發生錯誤時的上下文放在錯誤信息中併返迴給調用者,調用者需要添加一些錯誤信息中不包含的信息比如添加url到html.Parse返迴的錯誤中。
一般而言,被调函数f(x)会将调用信息和参数信息作为发生错误时的上下文放在错误信息中并返回给调用者,用者需要添加一些错误信息中不包含的信息比如添加url到html.Parse返回的错误中。
讓我們來看看處理錯誤的第二策略。如果錯誤的發生是偶然性的,或由不可知的問題導致的。一明智的選擇是重新嚐試失敗的操作。在重試時,我需要限製重試的時間間隔或重的次,防止無限製的重
让我们来看看处理错误的第二策略。如果错误的发生是偶然性的,或由不可知的问题导致的。一明智的选择是重新尝试失败的操作。在重试时,我需要限制重试的时间间隔或重的次,防止无限制的重
<u><i>gopl.io/ch5/wait</i></u>
```Go
@@ -57,7 +57,7 @@ func WaitForServer(url string) error {
}
```
如果錯誤發生後,程序無法繼續運行,我就可以采用第三策略:輸出錯誤信息併結束程序。需要意的是,這種策略隻應在main中行。對庫函數而言,應僅向上傳播錯誤,除非該錯誤意味着程序部包含不一致性,遇到了bug才能在庫函數中結束程序。
如果错误发生后,程序无法继续运行,我就可以采用第三策略:输出错误信息并结束程序。需要意的是,这种策略只应在main中行。对库函数而言,应仅向上传播错误,除非该错误意味着程序部包含不一致性,遇到了bug才能在库函数中结束程序。
```Go
// (In function main.)
@@ -67,7 +67,7 @@ if err := WaitForServer(url); err != nil {
}
```
調用log.Fatalf可以更簡潔的代碼達到與上文相同的效果。log中的所有函,都默認會在錯誤信息之前輸出時間信息。
用log.Fatalf可以更简洁的代码达到与上文相同的效果。log中的所有函,都默认会在错误信息之前输出时间信息。
```Go
if err := WaitForServer(url); err != nil {
@@ -75,21 +75,21 @@ if err := WaitForServer(url); err != nil {
}
```
長時間運行的服器常采用默認的時間格式,而交互式工具很少采用包含如此多信息的格式。
长时间运行的服器常采用默认的时间格式,而交互式工具很少采用包含如此多信息的格式。
```
2006/01/02 15:04:05 Site is down: no such domain:
bad.gopl.io
```
可以置log的前信息屏蔽時間信息,一般而言,前信息會被設置成命令名。
可以置log的前信息屏蔽时间信息,一般而言,前信息会被设置成命令名。
```Go
log.SetPrefix("wait: ")
log.SetFlags(0)
```
第四策略:有,我們隻需要輸出錯誤信息就足了,不需要中程序的行。我可以通log包提供函
第四策略:有,我们只需要输出错误信息就足了,不需要中程序的行。我可以通log包提供函
```Go
if err := Ping(); err != nil {
@@ -97,7 +97,7 @@ if err := Ping(); err != nil {
}
```
或者標準錯誤流輸出錯誤信息。
或者标准错误流输出错误信息。
```Go
if err := Ping(); err != nil {
@@ -105,9 +105,9 @@ if err := Ping(); err != nil {
}
```
log包中的所有函數會爲沒有換行符的字符串增加行符。
log包中的所有函数会为没有换行符的字符串增加行符。
第五,也是最後一種策略:我可以直接忽略掉錯誤
第五,也是最后一种策略:我可以直接忽略掉错误
```Go
dir, err := ioutil.TempDir("", "scratch")
@@ -118,6 +118,6 @@ if err != nil {
os.RemoveAll(dir) // ignore errors; $TMPDIR is cleaned periodically
```
管os.RemoveAll會失敗,但上面的例子併沒有做錯誤處理。是因操作繫統會定期的清理臨時目録。正因如此,然程序沒有處理錯誤,但程序的邏輯不會因此受到影。我們應該在每次函數調用後,都成考慮錯誤處理的習慣,當你決定忽略某個錯誤時,你應該在清晰的記録下你的意
管os.RemoveAll会失败,但上面的例子并没有做错误处理。是因操作系统会定期的清理临时目录。正因如此,然程序没有处理错误,但程序的逻辑不会因此受到影。我们应该在每次函数调用后,都成考虑错误处理的习惯,当你决定忽略某个错误时,你应该在清晰的记录下你的意
在Go中錯誤處理有一套特的編碼風格。檢査某個子函是否失敗後,我通常將處理失敗的邏輯代碼放在理成功的代之前。如果某個錯誤會導致函數返迴,那成功時的邏輯代碼不應放在else語句塊中,而直接放在函數體中。Go中大部分函的代碼結構幾乎相同,首先是一列的初始檢査,防止錯誤發生,之是函數的實際邏輯
在Go中错误处理有一套特的编码风格。检查某个子函是否失败后,我通常将处理失败的逻辑代码放在理成功的代之前。如果某个错误会导致函数返回,那成功时的逻辑代码不应放在else语句块中,而直接放在函数体中。Go中大部分函的代码结构几乎相同,首先是一列的初始检查,防止错误发生,之是函数的实际逻辑

View File

@@ -1,6 +1,6 @@
### 5.4.2. 文件結尾錯誤EOF
### 5.4.2. 文件结尾错误EOF
數經常會返迴多種錯誤,這對終端用戶來説可能很有趣,但程序而言,使得情況變得複雜。很多候,程序必須根據錯誤類型,作出不同的響應。讓我們考慮這樣一個例子:文件中取n個字節。如果n等文件的度,讀取過程的任何錯誤都表示失。如果n小文件的度,調用者會重複的讀取固定大小的數據直到文件束。這會導致調用者必分别理由文件束引起的各種錯誤。基於這樣的原因io包保任何由文件束引起的取失都返同一個錯誤——io.EOF該錯誤在io包中定
数经常会返回多种错误,这对终端用户来说可能很有趣,但程序而言,使得情况变得复杂。很多候,程序必须根据错误类型,作出不同的响应。让我们考虑这样一个例子:文件中取n个字节。如果n等文件的度,读取过程的任何错误都表示失。如果n小文件的度,用者会重复的读取固定大小的数据直到文件束。这会导致调用者必分别理由文件束引起的各种错误。基于这样的原因io包保任何由文件束引起的取失都返同一个错误——io.EOF该错误在io包中定
```Go
package io
@@ -11,7 +11,7 @@ import "errors"
var EOF = errors.New("EOF")
```
調用者需通過簡單的比,就可以檢測出這個錯誤。下面的例子展示了如何從標準輸入中取字符,以及判文件束。4.3的chartcount程序展示了更加複雜的代
用者需通过简单的比,就可以检测出这个错误。下面的例子展示了如何从标准输入中取字符,以及判文件束。4.3的chartcount程序展示了更加复杂的代
```Go
in := bufio.NewReader(os.Stdin)
@@ -27,4 +27,4 @@ for {
}
```
文件結束這種錯誤不需要更多的描述所以io.EOF有固定的錯誤信息——“EOF”。對於其他錯誤,我可能需要在錯誤信息中描述錯誤的類型和量,使得我不能像io.EOF一采用固定的錯誤信息。在7.11中,我們會提出更繫統的方法分某些固定的錯誤值。
文件结束这种错误不需要更多的描述所以io.EOF有固定的错误信息——“EOF”。对于其他错误,我可能需要在错误信息中描述错误的类型和量,使得我不能像io.EOF一采用固定的错误信息。在7.11中,我们会提出更系统的方法分某些固定的错误值。

View File

@@ -1,15 +1,15 @@
## 5.4. 錯誤
## 5.4. 错误
在Go中有一部分函數總是能成功的行。比如strings.Contains和strconv.FormatBool函數,對各種可能的入都做了良好的理,使得運行時幾乎不會失敗,除非遇到災難性的、不可料的情,比如運行時的內存溢出。導致這種錯誤的原因很複雜,難以處理,從錯誤中恢的可能性也很低。
在Go中有一部分函数总是能成功的行。比如strings.Contains和strconv.FormatBool函数,对各种可能的入都做了良好的理,使得运行时几乎不会失败,除非遇到灾难性的、不可料的情,比如运行时的内存溢出。导致这种错误的原因很复杂,难以处理,从错误中恢的可能性也很低。
有一部分函數隻要輸入的參數滿足一定件,也能保證運行成功。比如time.Date函數,該函數將年月日等參數構造成time.Time象,除非最後一個參數時區是nil。這種情況下會引發panic常。panic是自被調函數的信,表示生了某已知的bug。一良好的程序永遠不應該發生panic常。
有一部分函数只要输入的参数满足一定件,也能保证运行成功。比如time.Date函数,该函数将年月日等参数构造成time.Time象,除非最后一个参数时区是nil。这种情况下会引发panic常。panic是自被调函数的信,表示生了某已知的bug。一良好的程序永远不应该发生panic常。
對於大部分函而言,永遠無法確保能否成功行。是因爲錯誤的原因超出了程序的控製。舉個例子,任何行I/O操作的函數都會面臨出現錯誤的可能,隻有沒有經驗的程序員才會相信讀寫操作不會失敗,卽時是簡單的讀寫。因此,當本該可信的操作出乎意料的失敗後,我們必須弄清楚致失的原因。
对于大部分函而言,永远无法确保能否成功行。是因为错误的原因超出了程序的控制。举个例子,任何行I/O操作的函数都会面临出现错误的可能,只有没有经验的程序员才会相信读写操作不会失败,即时是简单的读写。因此,当本该可信的操作出乎意料的失败后,我们必须弄清楚致失的原因。
在Go的錯誤處理中,錯誤是軟件包API和用程序用界面的一重要成部分,程序行失敗僅被認爲是幾個預期的果之一。
在Go的错误处理中,错误是软件包API和用程序用界面的一重要成部分,程序行失败仅被认为是几个预期的果之一。
對於那些將運行失看作是預期結果的函,它們會返迴一個額外的返值,通常是最後一個,來傳遞錯誤信息。如果致失的原因有一個,額外的返值可以是一個布爾值,通常被命名ok。比如cache.Lookup失的唯一原因是key不存在麽代碼可以按照下面的方式組織
对于那些将运行失看作是预期结果的函,它们会返回一个额外的返值,通常是最后一个,来传递错误信息。如果致失的原因有一个,额外的返值可以是一个布尔值,通常被命名ok。比如cache.Lookup失的唯一原因是key不存在么代码可以按照下面的方式组织
```Go
value, ok := cache.Lookup(key)
@@ -18,22 +18,22 @@ if !ok {
}
```
通常,致失的原因不止一,尤其是I/O操作而言需要了解更多的錯誤信息。因此,外的返值不再是簡單的布爾類而是error型。
通常,致失的原因不止一,尤其是I/O操作而言需要了解更多的错误信息。因此,外的返值不再是简单的布尔类而是error型。
置的error是接口型。我們將在第七章了解接口型的含,以及它對錯誤處理的影響。現在我們隻需要明白error型可能是nil或者non-nil。nil意味着函數運行成功non-nil表示失敗。對於non-nil的error型,我可以通過調用error的Error函或者出函數獲得字符串型的錯誤信息。
置的error是接口型。我们将在第七章了解接口型的含,以及它对错误处理的影响。现在我们只需要明白error型可能是nil或者non-nil。nil意味着函数运行成功non-nil表示失败。对于non-nil的error型,我可以通过调用error的Error函或者出函数获得字符串型的错误信息。
```Go
fmt.Println(err)
fmt.Printf("%v", err)
```
通常,當函數返迴non-nil的error,其他的返值是未定的(undefined),些未定的返迴值應該被忽略。然而,有少部分函數在發生錯誤時,仍然會返迴一些有用的返值。比如,當讀取文件發生錯誤時Read函數會返迴可以取的字節數以及錯誤信息。對於這種情況,正確的處理方式應該是先處理這些不完整的數據,再處理錯誤。因此對函數的返值要有清晰的明,以便其他人使用。
通常,当函数返回non-nil的error,其他的返值是未定的(undefined),些未定的返回值应该被忽略。然而,有少部分函数在发生错误时,仍然会返回一些有用的返值。比如,当读取文件发生错误时Read函数会返回可以取的字节数以及错误信息。对于这种情况,正确的处理方式应该是先处理这些不完整的数据,再处理错误。因此对函数的返值要有清晰的明,以便其他人使用。
在Go中數運行失敗時會返迴錯誤信息,這些錯誤信息被認爲是一種預期的值而非exception使得Go有别那些將函數運行失看作是常的言。然Go有各種異常機製,但這些機製僅被使用在理那些未被料到的錯誤,卽bug而不是那些在健程序中應該被避免的程序錯誤。對於Go的異常機製我們將在5.9介
在Go中数运行失败时会返回错误信息,这些错误信息被认为是一种预期的值而非exception使得Go有别那些将函数运行失看作是常的言。然Go有各种异常机制,但这些机制仅被使用在理那些未被料到的错误,即bug而不是那些在健程序中应该被避免的程序错误。对于Go的异常机制我们将在5.9介
Go這樣設計的原因是由於對於某個應該在控流程中理的錯誤而言,將這個錯誤以異常的形式拋出會混亂對錯誤的描述,通常會導致一些糟糕的果。當某個程序錯誤被當作異常處理後,這個錯誤會將堆棧根據信息返迴給終端用戶,這些信息複雜且無用,無法幫助定位錯誤
Go这样设计的原因是由于对于某个应该在控流程中理的错误而言,将这个错误以异常的形式抛出会混乱对错误的描述,通常会导致一些糟糕的果。当某个程序错误被当作异常处理后,这个错误会将堆栈根据信息返回给终端用户,这些信息复杂且无用,无法帮助定位错误
正因此Go使用控製流機製如if和return處理異常,使得編碼人員能更多的關註錯誤處理。
正因此Go使用控制流机制如if和return处理异常,使得编码人员能更多的关注错误处理。
{% include "./ch5-04-1.md" %}

View File

@@ -1,6 +1,6 @@
## 5.5. 函
## 5.5. 函
在Go中被看作第一first-class values像其他值一樣,擁有類型,可以被賦值給其他量,傳遞給函數,從函數返迴。對函數function value調用類似函數調用。例子如下:
在Go中被看作第一first-class values像其他值一样,拥有类型,可以被赋值给其他量,传递给函数,从函数返回。对函数function value调用类似函数调用。例子如下:
```Go
func square(n int) int { return n * n }
@@ -17,14 +17,14 @@
f = product // compile error: can't assign func(int, int) int to func(int) int
```
數類型的零值是nil。調用值nil的函數值會引起panic錯誤
数类型的零值是nil。用值nil的函数值会引起panic错误
```Go
var f func(int) int
f(3) // 此f的值nil, 引起panic錯誤
f(3) // 此f的值nil, 引起panic错误
```
值可以nil比
值可以nil比
```Go
var f func(int) int
@@ -33,9 +33,9 @@
}
```
但是函值之是不可比的,也不能用函值作map的key。
但是函值之是不可比的,也不能用函值作map的key。
值使得我們不僅僅可以通過數據來參數化函,亦可通過行爲。標準庫中包含許多這樣的例子。下面的代展示了如何使用這個技巧。strings.Map字符串中的每字符調用add1函數,併將每個add1函的返迴值組成一新的字符串返迴給調用者。
值使得我们不仅仅可以通过数据来参数化函,亦可通过行为。标准库中包含许多这样的例子。下面的代展示了如何使用这个技巧。strings.Map字符串中的每字符用add1函数,并将每个add1函的返回值组成一新的字符串返回给调用者。
```Go
func add1(r rune) rune { return r + 1 }
@@ -45,14 +45,14 @@
fmt.Println(strings.Map(add1, "Admix")) // "Benjy"
```
5.2的findLinks函使用了助函visit,遍和操作了HTML面的所有結點。使用函值,我可以將遍歷結點的邏輯和操作結點的邏輯分離,使得我可以用遍歷的邏輯,從而對結點進行不同的操作。
5.2的findLinks函使用了助函visit,遍和操作了HTML面的所有结点。使用函值,我可以将遍历结点的逻辑和操作结点的逻辑分离,使得我可以用遍历的逻辑,从而对结点进行不同的操作。
<u><i>gopl.io/ch5/outline2</i></u>
```Go
// forEachNode針對每個結點x,都會調用pre(x)和post(x)。
// pre和post都是可的。
// 遍孩子結點之前,pre被調
// 遍孩子結點之後post被調
// forEachNode针对每个结点x,都会调用pre(x)和post(x)。
// pre和post都是可的。
// 遍孩子结点之前,pre被
// 遍孩子结点之后post被
func forEachNode(n *html.Node, pre, post func(n *html.Node)) {
if pre != nil {
pre(n)
@@ -66,7 +66,7 @@ func forEachNode(n *html.Node, pre, post func(n *html.Node)) {
}
```
該函數接收2個函數作爲參數,分别在結點的孩子被訪問前和訪問後調用。這樣的設計給調用者更大的活性。舉個例子,在我有startElemen和endElement兩個函數用於輸出HTML元素的開始標籤和結束標籤`<b>...</b>`
该函数接收2个函数作为参数,分别在结点的孩子被访问前和访问后调用。这样的设计给调用者更大的活性。举个例子,在我有startElemen和endElement两个函数用于输出HTML元素的开始标签和结束标签`<b>...</b>`
```Go
var depth int
@@ -84,15 +84,15 @@ func endElement(n *html.Node) {
}
```
上面的代利用fmt.Printf的一小技巧控製輸出的縮進`%*s`中的`*`在字符串之前填充一些空格。在例子中,每次輸出會先填充`depth*2`量的空格,再出"",最後再輸出HTML標籤
上面的代利用fmt.Printf的一小技巧控制输出的缩进`%*s`中的`*`在字符串之前填充一些空格。在例子中,每次输出会先填充`depth*2`量的空格,再出"",最后再输出HTML标签
如果我像下面這樣調用forEachNode
如果我像下面这样调用forEachNode
```Go
forEachNode(doc, startElement, endElement)
```
之前的outline程序相比得到了更加詳細的頁面結構
之前的outline程序相比得到了更加详细的页面结构
```
$ go build gopl.io/ch5/outline2
@@ -117,15 +117,15 @@ $ ./outline2 http://gopl.io
...
```
**練習 5.7** 完善startElement和endElement函,使其成通用的HTML出器。要求:輸出註釋結點,文本結點以及每元素的性(< a href='...'>)。使用略格式輸出沒有孩子結點的元素(`<img/>`代替`<img></img>`)。編寫測試,驗證程序出的格式正。(詳見11章
**练习 5.7** 完善startElement和endElement函,使其成通用的HTML出器。要求:输出注释结点,文本结点以及每元素的性(< a href='...'>)。使用略格式输出没有孩子结点的元素(`<img/>`代替`<img></img>`)。编写测试,验证程序出的格式正。(详见11章
**練習 5.8** 改pre和post函,使其返迴布爾類型的返值。返false中止forEachNoded的遍。使用脩改後的代碼編寫ElementByID函,根據用戶輸入的id找第一個擁有該id元素的HTML元素找成功,停止遍
**练习 5.8** 改pre和post函,使其返回布尔类型的返值。返false中止forEachNoded的遍。使用修改后的代码编写ElementByID函,根据用户输入的id找第一个拥有该id元素的HTML元素找成功,停止遍
```Go
func ElementByID(doc *html.Node, id string) *html.Node
```
**練習 5.9** 編寫函數expands中的"foo"替換爲f("foo")的返值。
**练习 5.9** 编写函数expands中的"foo"替换为f("foo")的返值。
```Go
func expand(s string, f func(string) string) string

View File

@@ -1,8 +1,8 @@
### 5.6.1. 警告:捕迭代
### 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. 匿名函
## 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/outline25.5的startElement和endElement共用了全局量depth將它們脩改爲匿名函使其共享outline中的局部量。
**练习5.12** gopl.io/ch5/outline25.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" %}

View File

@@ -1,8 +1,8 @@
## 5.7. 可變參數
## 5.7. 可变参数
參數數量可的函數稱爲爲可變參數函數。典型的例子就是fmt.Printf和似函。Printf首先接收一的必備參數,之接收任意個數的後續參數
参数数量可的函数称为为可变参数函数。典型的例子就是fmt.Printf和似函。Printf首先接收一的必备参数,之接收任意个数的后续参数
明可變參數函數時,需要在參數列表的最後一個參數類型之前加上省略符“...”,表示該函數會接收任意量的該類型參數
明可变参数函数时,需要在参数列表的最后一个参数类型之前加上省略符“...”,表示该函数会接收任意量的该类型参数
<u><i>gopl.io/ch5/sum</i></u>
```Go
@@ -15,7 +15,7 @@ func sum(vals...int) int {
}
```
sum函數返迴任意int型參數的和。在函數體中,vals被看作是類型爲[] int的切片。sum可以接收任意量的int型參數
sum函数返回任意int型参数的和。在函数体中,vals被看作是类型为[] int的切片。sum可以接收任意量的int型参数
```Go
fmt.Println(sum()) // "0"
@@ -23,14 +23,14 @@ fmt.Println(sum(3)) // "3"
fmt.Println(sum(1, 2, 3, 4)) // "10"
```
在上面的代中,調用者式的建一個數組,併將原始參數複製到數組中,再把數組的一切片作爲參數傳給被調函數。如果原始參數已經是切片型,我們該如何傳遞給sum需在最後一個參數後加上省略符。下面的代功能與上個例子中最後一條語句相同。
在上面的代中,用者式的建一个数组,并将原始参数复制到数组中,再把数组的一切片作为参数传给被调函数。如果原始参数已经是切片型,我们该如何传递给sum需在最后一个参数后加上省略符。下面的代功能与上个例子中最后一条语句相同。
```Go
values := []int{1, 2, 3, 4}
fmt.Println(sum(values...)) // "10"
```
然在可變參數函數內部,...int 型參數的行看起很像切片型,但實際上,可變參數函數和以切片作爲參數的函是不同的。
然在可变参数函数内部,...int 型参数的行看起很像切片型,但实际上,可变参数函数和以切片作为参数的函是不同的。
```Go
func f(...int) {}
@@ -39,7 +39,7 @@ fmt.Printf("%T\n", f) // "func(...int)"
fmt.Printf("%T\n", g) // "func([]int)"
```
變參數函數經常被用格式化字符串。下面的errorf函數構造了一以行號開頭的,經過格式化的錯誤信息。函名的後綴f是一通用的命名范,代表該可變參數函數可以接收Printf格的格式化字符串。
变参数函数经常被用格式化字符串。下面的errorf函数构造了一以行号开头的,经过格式化的错误信息。函名的后缀f是一通用的命名范,代表该可变参数函数可以接收Printf格的格式化字符串。
```Go
func errorf(linenum int, format string, args ...interface{}) {
@@ -51,13 +51,13 @@ linenum, name := 12, "count"
errorf(linenum, "undefined: %s", name) // "Line 12: undefined: count"
```
interfac{}表示函的最後一個參數可以接收任意型,我們會在第7章詳細介紹
interfac{}表示函的最后一个参数可以接收任意型,我们会在第7章详细介绍
**練習5.15** 編寫類似sum的可變參數函數max和min。考慮不傳參時max和min如何理,再編寫至少接收1個參數的版本。
**练习5.15** 编写类似sum的可变参数函数max和min。考虑不传参时max和min如何理,再编写至少接收1个参数的版本。
**練習5.16**編寫多參數版本的strings.Join。
**练习5.16**编写多参数版本的strings.Join。
**練習5.17**編寫多參數版本的ElementsByTagName接收一HTML結點樹以及任意量的標籤名,返迴與這些標籤名匹配的所有元素。下面出了2例子:
**练习5.17**编写多参数版本的ElementsByTagName接收一HTML结点树以及任意量的标签名,返回与这些标签名匹配的所有元素。下面出了2例子:
```Go
func ElementsByTagName(doc *html.Node, name...string) []*html.Node

View File

@@ -1,8 +1,8 @@
## 5.8. Deferred函
## 5.8. Deferred函
在findLinks的例子中用http.Get的出作html.Parse的入。有url的容的是HTML格式的html.Parse才可以正常工作實際url指向的容很富,可能是片,文本或是其他。將這些格式的內容傳遞給html.parse會産生不良果。
在findLinks的例子中用http.Get的出作html.Parse的入。有url的容的是HTML格式的html.Parse才可以正常工作实际url指向的容很富,可能是片,文本或是其他。将这些格式的内容传递给html.parse会产生不良果。
下面的例子取HTML頁面併輸出頁面的標題。title函數會檢査服務器返的Content-Type字段如果發現頁面不是HTML將終止函數運行,返迴錯誤
下面的例子取HTML页面并输出页面的标题。title函数会检查服务器返的Content-Type字段如果发现页面不是HTML将终止函数运行,返回错误
<u><i>gopl.io/ch5/title1</i></u>
```Go
@@ -32,7 +32,7 @@ func title(url string) error {
}
```
下面展示了行效果:
下面展示了行效果:
```
$ go build gopl.io/ch5/title1
@@ -44,11 +44,11 @@ $ ./title1 https://golang.org/doc/gopher/frontpage.png
title: https://golang.org/doc/gopher/frontpage.png has type image/png, not text/html
```
resp.Body.close調用了多次,這是爲了確保title在所有行路下(使函數運行失)都關閉了網絡連接。着函數變得複雜,需要理的錯誤也變多,維護清理邏輯變得越來越睏難。而Go語言獨有的defer機製可以事情變得簡單
resp.Body.close用了多次,这是为了确保title在所有行路下(使函数运行失)都关闭了网络连接。着函数变得复杂,需要理的错误也变多,维护清理逻辑变得越来越困难。而Go语言独有的defer机制可以事情变得简单
需要在調用普通函或方法前加上關鍵字defer就完成了defer所需要的法。defer句被執行時跟在defer面的函數會被延遲執行。直到包含defer句的函數執行完畢時defer的函數才會被執行,不包含defer句的函是通return正常束,是由panic致的異常結束。你可以在一個函數中執行多defer句,它們的執行順序與聲明順序相反。
需要在用普通函或方法前加上关键字defer就完成了defer所需要的法。defer句被执行时跟在defer面的函数会被延迟执行。直到包含defer句的函数执行完毕时defer的函数才会被执行,不包含defer句的函是通return正常束,是由panic致的异常结束。你可以在一个函数中执行多defer句,它们的执行顺序与声明顺序相反。
defer語句經常被用於處理成的操作,如打開、關閉、連接、斷開連接、加鎖、釋放鎖。通defer機製,不論函數邏輯多複雜,都能保在任何行路下,源被放。釋放資源的defer應該直接跟在請求資源的語句後。在下面的代中,一defer句替代了之前的所有resp.Body.Close
defer语句经常被用于处理成的操作,如打开、关闭、连接、断开连接、加锁、释放锁。通defer机制,不论函数逻辑多复杂,都能保在任何行路下,源被放。释放资源的defer应该直接跟在请求资源的语句后。在下面的代中,一defer句替代了之前的所有resp.Body.Close
<u><i>gopl.io/ch5/title2</i></u>
```Go
@@ -71,7 +71,7 @@ func title(url string) error {
}
```
理其他資源時也可以采用defer機製,比如文件的操作:
理其他资源时也可以采用defer机制,比如文件的操作:
<u><i>io/ioutil</i></u>
```Go
@@ -86,7 +86,7 @@ func ReadFile(filename string) ([]byte, error) {
}
```
或是理互斥9.2章)
或是理互斥9.2章)
```Go
var mu sync.Mutex
@@ -98,7 +98,7 @@ func lookup(key string) int {
}
```
調試複雜程序defer機製也常被用於記録何時進入和退出函。下例中的bigSlowOperation函,直接調用trace記録函數的被調情況。bigSlowOperation被調時trace會返迴一個函數值,該函數值會在bigSlowOperation退出時被調用。通過這種方式, 我可以隻通過一條語句控製函數的入口和所有的出口,甚至可以記録函數的運行時間如例子中的start。需要意一:不要忘defer語句後的圓括號,否則本該在進入時執行的操作在退出時執行,而本在退出時執行的,永遠不會被執行。
调试复杂程序defer机制也常被用于记录何时进入和退出函。下例中的bigSlowOperation函,直接用trace记录函数的被调情况。bigSlowOperation被调时trace会返回一个函数值,该函数值会在bigSlowOperation退出时被调用。通过这种方式, 我可以只通过一条语句控制函数的入口和所有的出口,甚至可以记录函数的运行时间如例子中的start。需要意一:不要忘defer语句后的圆括号,否则本该在进入时执行的操作在退出时执行,而本在退出时执行的,永远不会被执行。
<u><i>gopl.io/ch5/trace</i></u>
```Go
@@ -118,7 +118,7 @@ func trace(msg string) func() {
}
```
每一次bigSlowOperation被調用,程序都會記録函數的進入,退出,持續時間。(我用time.Sleep模擬一個耗時的操作)
每一次bigSlowOperation被用,程序都会记录函数的进入,退出,持续时间。(我用time.Sleep模拟一个耗时的操作)
```
$ go build gopl.io/ch5/trace
@@ -127,9 +127,9 @@ $ ./trace
2015/11/18 09:53:36 exit bigSlowOperation (10.000589217s)
```
知道defer句中的函數會在return句更新返迴值變量後再執行,又因在函中定的匿名函可以訪問該函數包括返迴值變量在的所有量,所以,匿名函采用defer機製,可以使其察函的返值。
知道defer句中的函数会在return句更新返回值变量后再执行,又因在函中定的匿名函可以访问该函数包括返回值变量在的所有量,所以,匿名函采用defer机制,可以使其察函的返值。
以double函數爲例:
以double函数为例:
```Go
func double(x int) int {
@@ -137,7 +137,7 @@ func double(x int) int {
}
```
們隻需要首先命名double的返再增加defer句,我就可以在double每次被調用時,輸出參數以及返值。
们只需要首先命名double的返再增加defer句,我就可以在double每次被调用时,输出参数以及返值。
```Go
func double(x int) (result int) {
@@ -149,9 +149,9 @@ _ = double(4)
// "double(4) = 8"
```
可能doulbe函數過於簡單,看不出這個小技巧的作用,但對於有許多return句的函而言,這個技巧很有用。
可能doulbe函数过于简单,看不出这个小技巧的作用,但对于有许多return句的函而言,这个技巧很有用。
被延遲執行的匿名函甚至可以改函數返迴給調用者的返值:
被延迟执行的匿名函甚至可以改函数返回给调用者的返值:
```Go
func triple(x int) (result int) {
@@ -161,7 +161,7 @@ func triple(x int) (result int) {
fmt.Println(triple(4)) // "12"
```
在循環體中的defer句需要特别意,因爲隻有在函數執行完畢後,這些被延的函數才會執行。下面的代碼會導致繫統的文件描述符耗,因在所有文件都被理之前,有文件會被關閉
在循环体中的defer句需要特别意,因为只有在函数执行完毕后,这些被延的函数才会执行。下面的代码会导致系统的文件描述符耗,因在所有文件都被理之前,有文件会被关闭
```Go
for _, filename := range filenames {
@@ -175,7 +175,7 @@ for _, filename := range filenames {
}
```
種解決方法是將循環體中的defer句移至另外一個函數。在每次循環時,調用這個函數
种解决方法是将循环体中的defer句移至另外一个函数。在每次循环时,调用这个函数
```Go
for _, filename := range filenames {
@@ -193,7 +193,7 @@ func doFile(filename string) error {
}
```
下面的代是fetch1.5)的改版,我們將http響應信息入本地文件而不是從標準輸出流出。我們通過path.Base提出url路的最一段作文件名。
下面的代是fetch1.5)的改版,我们将http响应信息入本地文件而不是从标准输出流出。我们通过path.Base提出url路的最一段作文件名。
<u><i>gopl.io/ch5/fetch</i></u>
```Go
@@ -222,6 +222,6 @@ func fetch(url string) (filename string, n int64, err error) {
}
```
resp.Body.Close延遲調用我們已經見過了,在此不做解。上例中,通os.Create打文件進行寫入,在關閉文件,我們沒有對f.close采用defer機製,因爲這會産生一些微妙的錯誤。許多文件繫統尤其是NFS入文件時發生的錯誤會被延到文件關閉時反饋。如果沒有檢査文件關閉時的反信息,可能會導致數據丟失,而我們還誤以爲寫入操作成功。如果io.Copy和f.close都失了,我們傾向於將io.Copy的錯誤信息反饋給調用者,因它先f.close生,更有可能接近問題的本
resp.Body.Close延迟调用我们已经见过了,在此不做解。上例中,通os.Create打文件进行写入,在关闭文件,我们没有对f.close采用defer机制,因为这会产生一些微妙的错误。许多文件系统尤其是NFS入文件时发生的错误会被延到文件关闭时反馈。如果没有检查文件关闭时的反信息,可能会导致数据丢失,而我们还误以为写入操作成功。如果io.Copy和f.close都失了,我们倾向于将io.Copy的错误信息反馈给调用者,因它先f.close生,更有可能接近问题的本
**練習5.18**不改fetch的行,重fetch函要求使用defer機製關閉文件。
**练习5.18**不改fetch的行,重fetch函要求使用defer机制关闭文件。

View File

@@ -1,10 +1,10 @@
## 5.9. Panic
## 5.9. Panic
Go的類型繫統會在編譯時捕獲很多錯誤,但有些錯誤隻能在運行時檢査,如數組訪問越界、空指引用等。這些運行時錯誤會引起painc常。
Go的类型系统会在编译时捕获很多错误,但有些错误只能在运行时检查,如数组访问越界、空指引用等。这些运行时错误会引起painc常。
一般而言,panic異常發生時,程序會中斷運行,併立卽執行在goroutine可以先理解成在第8章會詳細介紹)中被延的函defer 機製)。隨後,程序崩潰併輸出日信息。日信息包括panic value和函數調用的堆棧跟蹤信息。panic value通常是某種錯誤信息。對於每個goroutine信息中都會有與之相的,生panic的函數調用堆棧跟蹤信息。通常,我不需要再次行程序去定位問題,日信息已提供了足夠的診斷依據。因此,在我們填寫問題報告時,一般會將panic常和日信息一併記録
一般而言,panic异常发生时,程序会中断运行,并立即执行在goroutine可以先理解成线在第8章会详细介绍)中被延的函defer 机制)。随后,程序崩溃并输出日信息。日信息包括panic value和函数调用的堆栈跟踪信息。panic value通常是某种错误信息。对于每个goroutine信息中都会有与之相的,生panic的函数调用堆栈跟踪信息。通常,我不需要再次行程序去定位问题,日信息已提供了足够的诊断依据。因此,在我们填写问题报告时,一般会将panic常和日信息一并记录
不是所有的panic常都來自運行時,直接調用內置的panic函數也會引發panicpanic函接受任何值作爲參數。當某些不應該發生的場景發生時,我們就應該調用panic。比如程序到了某條邏輯上不可能到的路
不是所有的panic常都来自运行时,直接调用内置的panic函数也会引发panicpanic函接受任何值作为参数。当某些不应该发生的场景发生时,我们就应该调用panic。比如程序到了某条逻辑上不可能到的路
```Go
switch s := suit(drawCard()); s {
@@ -17,7 +17,7 @@ switch s := suit(drawCard()); s {
}
```
言函數必須滿足的前置件是明智的做法,但很容易被用。除非你能提供更多的錯誤信息,或者能更快速的發現錯誤,否不需要使用言,編譯器在運行時會幫你檢査代碼
言函数必须满足的前置件是明智的做法,但很容易被用。除非你能提供更多的错误信息,或者能更快速的发现错误,否不需要使用言,编译器在运行时会帮你检查代码
```Go
func Reset(x *Buffer) {
@@ -28,11 +28,11 @@ func Reset(x *Buffer) {
}
```
然Go的panic機製類似於其他言的但panic的適用場景有一些不同。由panic引起程序的崩因此panic一般用於嚴重錯誤,如程序部的邏輯不一致。勤的程序員認爲任何崩都表明代中存在漏洞,所以對於大部分漏洞,我們應該使用Go提供的錯誤機製而不是panic量避免程序的崩。在健的程序中,任何可以料到的錯誤,如不正確的輸入、錯誤的配置或是失的I/O操作都應該被優雅的理,最好的理方式就是使用Go的錯誤機製
然Go的panic机制类似于其他言的但panic的适用场景有一些不同。由panic引起程序的崩因此panic一般用于严重错误,如程序部的逻辑不一致。勤的程序员认为任何崩都表明代中存在漏洞,所以对于大部分漏洞,我们应该使用Go提供的错误机制而不是panic量避免程序的崩。在健的程序中,任何可以料到的错误,如不正确的输入、错误的配置或是失的I/O操作都应该被优雅的理,最好的理方式就是使用Go的错误机制
regexp.Compile函數,該函數將正則表達式編譯成有效的可匹配格式。當輸入的正則表達式不合法時,該函數會返迴一個錯誤。當調用者明的知道正確的輸入不引起函數錯誤時,要求調用者檢査這個錯誤是不必要和纍贅的。我們應該假設函數的輸入一直合法,就如前面的言一樣:當調用者入了不應該出現的輸入時,觸發panic常。
regexp.Compile函数,该函数将正则表达式编译成有效的可匹配格式。当输入的正则表达式不合法时,该函数会返回一个错误。当调用者明的知道正确的输入不引起函数错误时,要求用者检查这个错误是不必要和累赘的。我们应该假设函数的输入一直合法,就如前面的言一样:当调用者入了不应该出现的输入时,触发panic常。
在程序源中,大多數正則表達式是字符串字面值string literals因此regexp包提供了包裝函數regexp.MustCompile檢査輸入的合法性。
在程序源中,大多数正则表达式是字符串字面值string literals因此regexp包提供了包装函数regexp.MustCompile检查输入的合法性。
```Go
package regexp
@@ -46,13 +46,13 @@ func MustCompile(expr string) *Regexp {
}
```
裝函數使得調用者可以便捷的用一個編譯後的正則表達式爲包級别的變量賦值:
装函数使得用者可以便捷的用一个编译后的正则表达式为包级别的变量赋值:
```Go
var httpSchemeRE = regexp.MustCompile(`^https?:`) //"http:" or "https:"
```
MustCompile不能接收不合法的入。函名中的Must前是一種針對此類函數的命名比如template.Must4.6
MustCompile不能接收不合法的入。函名中的Must前是一种针对此类函数的命名比如template.Must4.6
```Go
func main() {
@@ -65,7 +65,7 @@ func f(x int) {
}
```
上例中的運行輸出如下:
上例中的运行输出如下:
```
f(3)
@@ -76,7 +76,7 @@ defer 2
defer 3
```
f(0)被調用時,發生panic常,之前被延遲執行的的3fmt.Printf被調用。程序中斷執行後panic信息和堆信息會被輸出(下面是化的出):
f(0)被调用时,发生panic常,之前被延迟执行的的3fmt.Printf被用。程序中断执行后panic信息和堆信息会被输出(下面是化的出):
```
panic: runtime error: integer divide by zero
@@ -92,9 +92,9 @@ main.main()
src/gopl.io/ch5/defer1/defer.go:10
```
在下一節將看到,如何使程序panic常中恢,阻止程序的崩
在下一节将看到,如何使程序panic常中恢,阻止程序的崩
了方便診斷問題runtime包允程序員輸出堆信息。在下面的例子中,我們通過在main函中延遲調用printStack出堆信息。
了方便诊断问题runtime包允程序员输出堆信息。在下面的例子中,我们通过在main函中延迟调用printStack出堆信息。
```Go
gopl.io/ch5/defer2
@@ -109,7 +109,7 @@ func printStack() {
}
```
printStack的簡化輸出如下(下面是printStack的不包括panic的日信息):
printStack的简化输出如下(下面是printStack的不包括panic的日信息):
```
goroutine 1 [running]:
@@ -127,4 +127,4 @@ main.main()
src/gopl.io/ch5/defer2/defer.go:15
```
panic機製類比其他語言異常機製的讀者可能會驚訝runtime.Stack何能出已經被釋放函的信息在Go的panic機製中,延遲函數的調用在放堆信息之前。
panic机制类比其他语言异常机制的读者可能会惊讶runtime.Stack何能出已经被释放函的信息在Go的panic机制中,延迟函数的调用在放堆信息之前。

View File

@@ -1,10 +1,10 @@
## 5.10. Recover捕獲異
## 5.10. Recover捕获异
通常來説,不應該對panic常做任何理,但有,也許我們可以從異常中恢,至少我可以在程序崩前,做一些操作。舉個例子,web服器遇到不可料的嚴重問題時,在崩潰前應該將所有的連接關閉;如果不做任何理,使得客端一直處於等待狀態。如果web服務器還在開發階段,服器甚至可以將異常信息反到客端,幫助調試
通常来说,不应该对panic常做任何理,但有,也许我们可以从异常中恢,至少我可以在程序崩前,做一些操作。举个例子,web服器遇到不可料的严重问题时,在崩溃前应该将所有的连接关闭;如果不做任何理,使得客端一直处于等待状态。如果web服务器还在开发阶段,服器甚至可以将异常信息反到客端,帮助调试
如果在deferred函數中調用了置函recover且定義該defer句的函數發生了panicrecover使程序panic中恢複,併返迴panic value。致panic常的函數不會繼續運行,但能正常返。在未生panic時調用recoverrecover會返迴nil。
如果在deferred函数中调用了置函recover且定义该defer句的函数发生了panicrecover使程序panic中恢复,并返回panic value。致panic常的函数不会继续运行,但能正常返。在未生panic时调用recoverrecover会返回nil。
讓我們以語言解析器例,明recover的使用景。考慮到語言解析器的複雜性,使某個語言解析器目前工作正常,也法肯定它有漏洞。因此,當某個異常出現時,我們不會選擇讓解析器崩,而是會將panic異常當作普通的解析錯誤,併附加外信息提醒用戶報告此錯誤
让我们以语言解析器例,明recover的使用景。考虑到语言解析器的复杂性,使某个语言解析器目前工作正常,也法肯定它有漏洞。因此,当某个异常出现时,我们不会选择让解析器崩,而是会将panic异常当作普通的解析错误,并附加外信息提醒用户报告此错误
```Go
func Parse(input string) (s *Syntax, err error) {
@@ -17,17 +17,17 @@ func Parse(input string) (s *Syntax, err error) {
}
```
deferred函數幫助Parsepanic中恢。在deferred函數內panic value被附加到錯誤信息中;用err量接收錯誤信息,返迴給調用者。我也可以通過調用runtime.Stack往錯誤信息中添加完整的堆棧調用信息。
deferred函数帮助Parsepanic中恢。在deferred函数内panic value被附加到错误信息中;用err量接收错误信息,返回给调用者。我也可以通过调用runtime.Stack往错误信息中添加完整的堆栈调用信息。
不加分的恢所有的panic常,不是可取的做法;因在panic之後,無法保證包級變量的狀態仍然和我們預期一致。比如,對數據結構的一次重要更新有被完整完成、文件或者網絡連接沒有被關閉、獲得的鎖沒有被放。此外,如果寫日誌時産生的panic被不加分的恢,可能會導致漏洞被忽略。
不加分的恢所有的panic常,不是可取的做法;因在panic之后,无法保证包级变量的状态仍然和我们预期一致。比如,对数据结构的一次重要更新有被完整完成、文件或者网络连接没有被关闭、获得的锁没有被放。此外,如果写日志时产生的panic被不加分的恢,可能会导致漏洞被忽略。
然把panic的理都集中在一包下,有助於簡化對複雜和不可以預料問題的處理,但作爲被廣泛遵守的范,你不應該試圖去恢其他包引起的panic。公有的API應該將函數的運行失敗作爲error返而不是panic。同的,你也不應該恢複一個由他人開發的函引起的panic比如説調用者入的迴調函數,因爲你無法確保這樣做是安全的。
然把panic的理都集中在一包下,有助于简化对复杂和不可以预料问题的处理,但作为被广泛遵守的范,你不应该试图去恢其他包引起的panic。公有的API应该将函数的运行失败作为error返而不是panic。同的,你也不应该恢复一个由他人开发的函引起的panic比如说调用者入的回调函数,因为你无法确保这样做是安全的。
時我們很難完全遵循范,舉個例子net/http包中提供了一web服器,收到的求分發給用戶提供的理函。很然,我不能因爲某個處理函數引發的panic常,掉整個進web服器遇到理函數導致的panic時會調用recover出堆信息,繼續運行。這樣的做法在實踐中很便捷,但也引起資源洩漏,或是因recover操作致其他問題
时我们很难完全遵循范,举个例子net/http包中提供了一web服器,收到的求分发给用户提供的理函。很然,我不能因为某个处理函数引发的panic常,掉整个进web服器遇到理函数导致的panic时会调用recover出堆信息,继续运行。这样的做法在实践中很便捷,但也引起资源泄漏,或是因recover操作致其他问题
以上原因,安全的做法是有選擇性的recover。換句話説,隻恢複應該被恢的panic常,此外,這些異常所占的比例應該盡可能的低。爲了標識某個panic是否應該被恢,我可以panic value置成特殊型。在recover時對panic value進行檢査,如果發現panic value是特殊型,就將這個panic作errror理,如果不是,按照正常的panic進行處理(在下面的例子中,我們會看到這種方式)。
以上原因,安全的做法是有选择性的recover。换句话说,只恢复应该被恢的panic常,此外,这些异常所占的比例应该尽可能的低。为了标识某个panic是否应该被恢,我可以panic value置成特殊型。在recover时对panic value进行检查,如果发现panic value是特殊型,就将这个panic作errror理,如果不是,按照正常的panic进行处理(在下面的例子中,我们会看到这种方式)。
下面的例子是title函數的變如果HTML面包含多`<title>`該函數會給調用者返迴一個錯誤error。在soleTitle內部處理時,如果檢測到有多`<title>`會調用panic阻止函數繼續遞歸,併將特殊型bailout作panic的參數
下面的例子是title函数的变如果HTML面包含多`<title>`该函数会给调用者返回一个错误error。在soleTitle内部处理时,如果检测到有多`<title>`会调用panic阻止函数继续递归,并将特殊型bailout作panic的参数
```Go
// soleTitle returns the text of the first non-empty title element
@@ -60,8 +60,8 @@ func soleTitle(doc *html.Node) (title string, err error) {
}
```
在上例中deferred函數調用recover併檢査panic value。panic value是bailout{}類型時deferred函生成一error返迴給調用者。panic value是其他non-nil值,表示生了未知的panicdeferred函數將調用panic函數併將當前的panic value作爲參數傳入;此,等同recover有做任何操作。(請註意:在例子中,對可預期的錯誤采用了panic這違反了之前的建,我在此是想向者演示這種機製。)
在上例中deferred函数调用recover并检查panic value。panic value是bailout{}类型时deferred函生成一error返回给调用者。panic value是其他non-nil值,表示生了未知的panicdeferred函数将调用panic函数并将当前的panic value作为参数传入;此,等同recover有做任何操作。(请注意:在例子中,对可预期的错误采用了panic这违反了之前的建,我在此是想向者演示这种机制。)
有些情下,我們無法恢。某些致命錯誤會導致Go在運行時終止程序,如存不足。
有些情下,我们无法恢。某些致命错误会导致Go在运行时终止程序,如存不足。
**練習5.19** 使用panic和recover編寫一個不包含return句但能返迴一個非零值的函
**练习5.19** 使用panic和recover编写一个不包含return句但能返回一个非零值的函

View File

@@ -1,6 +1,6 @@
# 第五章 函
# 第五章 函
可以讓我們將一個語句序列打包爲一個單元,然可以程序中其它地方多次調用。函數的機製可以讓我們將一個大的工作分解小的任務,這樣的小任可以不同程序在不同時間、不同地方立完成。一個函數同時對用戶隱藏了其實現細節。由於這些因素,對於任何編程語言來説,函都是一個至關重要的部分。
可以让我们将一个语句序列打包为一个单元,然可以程序中其它地方多次用。函数的机制可以让我们将一个大的工作分解小的任务,这样的小任可以不同程序在不同时间、不同地方立完成。一个函数同时对用户隐藏了其实现细节。由于这些因素,对于任何编程语言来说,函都是一个至关重要的部分。
們已經見過許多函了。在,讓我們多花一點時間來徹底地討論函數特性。本章的行示例是一個網絡蜘蛛也就是web索引擎中負責抓取網頁部分的件,它們根據抓取網頁中的鏈接繼續抓取接指向的面。一個網絡蜘蛛的例子給我們足夠的機會去探索遞歸函數、匿名函數、錯誤處理和函其它的很多特性。
们已经见过许多函了。在,让我们多花一点时间来彻底地讨论函数特性。本章的行示例是一个网络蜘蛛也就是web索引擎中负责抓取网页部分的件,它们根据抓取网页中的链接继续抓取接指向的面。一个网络蜘蛛的例子给我们足够的机会去探索递归函数、匿名函数、错误处理和函其它的很多特性。