## 5.7\. 函數
### 5.7.1\. 多值返回
Go語言中函數和方法方法的一個有意思的特性是它們可以同時返回多個值。它可以比C語言 更簡潔的處理多個返回值的情況:例如在修改一個參數的同時獲取錯誤返回值 (-1或EOF)。
在傳統的C語言中,如果寫數據失敗的話,會在另外一個地方保存錯誤標志,而且錯誤標志很容易被 其他函數產生的錯誤覆蓋。在Go語言中,則可以在返回成功寫入的數據數目的同時,也可以返回有意義 的錯誤信息:“您已經寫了一些數據,但不是全部,因為設備在阻塞填充中”。對于os 包中的*File.Write函數,說明如下:
```
func (file *File) Write(b []byte) (n int, err Error)
```
在函數的文檔中有函數返回值的描述:返回成功寫入的數據長度,如果n != len(b),則同時返回一個非non-nil的錯誤信息。 這是Go語言中,處理錯誤的常見方式。在后面的“錯誤處理”一節,會有更多的描述。
多個返回值還可以用于模擬C語言中通過指針的方式遍歷。下面的函數是從一個int數組中獲取 一個數據,然后移動到下一個位置。
```
func nextInt(b []byte, i int) (int, int) {
for ; i < len(b) && !isDigit(b[i]); i++ {
}
x := 0
for ; i < len(b) && isDigit(b[i]); i++ {
x = x*10 + int(b[i])-'0'
}
return x, i
}
```
你還可以用這個方法來打印一個數組:
```
for i := 0; i < len(a); {
x, i = nextInt(a, i)
fmt.Println(x)
}
```
### 5.7.2\. 命名的結果參數
Go語言中,我們還可以給函數或方法的返回值命名,就像函數的輸入參數那樣。如果我們命名了返回值, 那么它們將在函數開始的時候被初始化為空。然后,在執行不帶參數的return語句時, 命名的返回值變量將被用于返回。
返回值命名并不強制使用,但是有時我們給名返回值命令可以產生更清晰的代碼,同時它也可以用于文檔。 例如,我們把nextInt的返回值命名:
```
func nextInt(b []byte, pos int) (value, nextPos int) {
```
命名后,返回值會自動初始化,而且不需要在return中顯式寫出返回參數。下面的io.ReadFull 函數是另一個類似的例子:
```
func ReadFull(r Reader, buf []byte) (n int, err os.Error) {
for len(buf) > 0 && err == nil {
var nr int
nr, err = r.Read(buf)
n += nr
buf = buf[nr:len(buf)]
}
return
}
```
### 5.7.3\. Defer
Go 的 defer 語句安排一個函數調用(被 defer 的函數)延遲發生在執行 defer 的函數剛要返回之前。當函數無論怎樣返回,某資源必須釋放時,可用這種與眾不同、但有效的處理方式。傳統的例子包括解鎖互斥或關閉文件。
```
// Contents returns the file's contents as a string.
func Contents(filename string) (string, os.Error) {
f, err := os.Open(filename, os.O_RDONLY, 0)
if err != nil {
return "", err
}
defer f.Close() // f.Close will run when we're finished.
var result []byte
buf := make([]byte, 100)
for {
n, err := f.Read(buf[0:])
result = append(result, buf[0:n]...) // append is discussed later.
if err != nil {
if err == os.EOF {
break
}
return "", err // f will be closed if we return here.
}
}
return string(result), nil // f will be closed if we return here.
}
```
這樣延遲一個函數有雙重優勢:一是你永遠不會忘記關閉文件,此錯誤在你事后編輯函數添加一個返回路徑時常常發生。二是關閉和打開靠在一起,比放在函數尾要清晰很多。
延遲函數的參量(包括接受者,如果函數是一個方法)的求值發生在defer 語句執行時,而不是延遲函數調用時。除了不必擔心函數執行時變量值的改變外,也意味著同一延遲調用可以延遲多個函數的執行。如下傻例:
```
for i := 0; i < 5; i++ {
defer fmt.Printf("%d ", i)
}
```
延遲函數執行順序為 LIFO,所以上面代碼在函數返回時打印 4 3 2 1 0。更可信的例子是跟蹤程序中函數執行的一個簡單方式。我們可以寫些簡單的跟蹤例程:
```
func trace(s string) { fmt.Println("entering:", s) }
func untrace(s string) { fmt.Println("leaving:", s) }
// Use them like this:
func a() {
trace("a")
defer untrace("a")
// do something....
}
```
利用被延遲函數的參量在 defer 執行時得值的特點,跟蹤函數可以安排未跟蹤函數的參量。
```
func trace(s string) string {
fmt.Println("entering:", s)
return s
}
func un(s string) {
fmt.Println("leaving:", s)
}
func a() {
defer un(trace("a"))
fmt.Println("in a")
}
func b() {
defer un(trace("b"))
fmt.Println("in b")
a()
}
func main() {
b()
}
```
打印:
```
entering: b
in b
entering: a
in a
leaving: a
leaving: b
```
對于習慣其它語言的塊層次資源管理的程序員,defer 可能比較怪,但它最有趣最強力的應用恰恰來自它不是基于塊、而是基于函數。在panic 和 recover 一節我們會看到一個例子。
- 1. 關于本文
- 2. Go語言簡介
- 3. 安裝go環境
- 3.1. 簡介
- 3.2. 安裝C語言工具
- 3.3. 安裝Mercurial
- 3.4. 獲取代碼
- 3.5. 安裝Go
- 3.6. 編寫程序
- 3.7. 進一步學習
- 3.8. 更新go到新版本
- 3.9. 社區資源
- 3.10. 環境變量
- 4. Go語言入門
- 4.1. 簡介
- 4.2. Hello,世界
- 4.3. 分號(Semicolons)
- 4.4. 編譯
- 4.5. Echo
- 4.6. 類型簡介
- 4.7. 申請內存
- 4.8. 常量
- 4.9. I/O包
- 4.10. Rotting cats
- 4.11. Sorting
- 4.12. 打印輸出
- 4.13. 生成素數
- 4.14. Multiplexing
- 5. Effective Go
- 5.1. 簡介
- 5.2. 格式化
- 5.3. 注釋
- 5.4. 命名
- 5.5. 分號
- 5.6. 控制流
- 5.7. 函數
- 5.8. 數據
- 5.9. 初始化
- 5.10. 方法
- 5.11. 接口和其他類型
- 5.12. 內置
- 5.13. 并發
- 5.14. 錯誤處理
- 5.15. Web服務器
- 6. 如何編寫Go程序
- 6.1. 簡介
- 6.2. 社區資源
- 6.3. 新建一個包
- 6.4. 測試
- 6.5. 一個帶測試的演示包
- 7. Codelab: 編寫Web程序
- 7.1. 簡介
- 7.2. 開始
- 7.3. 數據結構
- 7.4. 使用http包
- 7.5. 基于http提供wiki頁面
- 7.6. 編輯頁面
- 7.7. template包
- 7.8. 處理不存在的頁面
- 7.9. 儲存頁面
- 7.10. 錯誤處理
- 7.11. 模板緩存
- 7.12. 驗證
- 7.13. 函數文本和閉包
- 7.14. 試試!
- 7.15. 其他任務
- 8. 針對C++程序員指南
- 8.1. 概念差異
- 8.2. 語法
- 8.3. 常量
- 8.4. Slices(切片)
- 8.5. 構造值對象
- 8.6. Interfaces(接口)
- 8.7. Goroutines
- 8.8. Channels(管道)
- 9. 內存模型
- 9.1. 簡介
- 9.2. Happens Before
- 9.3. 同步(Synchronization)
- 9.4. 錯誤的同步方式
- 10. 附錄
- 10.1. 命令行工具
- 10.2. 視頻和講座
- 10.3. Release History
- 10.4. Go Roadmap
- 10.5. 相關資源