鍍金池/ 教程/ GO/ 函數(shù)
版權(quán)
簡介
Bibliography
接口
通訊
函數(shù)
進階
索引
并發(fā)

函數(shù)

函數(shù)是構(gòu)建 Go 程序的基礎(chǔ)部件;所遇有趣的事情都是在它其中發(fā)生的。函數(shù)的定義看起來像這樣:

http://wiki.jikexueyuan.com/project/learn-go-language/images/59.png" alt="pic" />

0 .關(guān)鍵字 func 用于定義一個函數(shù);

  1. 函數(shù)可以綁定到特定的類型上。這叫做接收者。有接收者的函數(shù)被稱作 method。第 5 章將對其進行說明;
  2. funcname 是你函數(shù)的名字;
  3. int 類型的變量 q 作為輸入?yún)?shù)。參數(shù)用 pass-by-value 方式傳遞,意味著它們會被復制;
  4. 變量 r 和 s 是這個函數(shù)的命名返回值。在 Go 的函數(shù)中可以返回多個值。參閱第 28 頁的“多值返回”。如果不想對返回的參數(shù)命名,只需要提供類型:(int,int)。 如果只有一個返回值,可以省略圓括號。如果函數(shù)是一個子過程,并且沒有任何 返回值,也可以省略這些內(nèi)容;
  5. 這是函數(shù)體。注意 return 是一個語句,所以包裹參數(shù)的括號是可選的。

這里有兩個例子,左邊的函數(shù)沒有返回值,右邊的只是簡單的將輸入返回。

func subroutine(in i n t ) { return }

func identity(in i n t ) i n t { return in }

可以隨意安排函數(shù)定義的順序,編譯器會在執(zhí)行前掃描每個文件。所以函數(shù)原型在 Go 中都是過期的舊物。Go 不允許函數(shù)嵌套,然而你可以利用匿名函數(shù)實現(xiàn)它,參閱本章第31 頁的“函數(shù)作為值”。

遞歸函數(shù)跟其他語言是一樣的:

http://wiki.jikexueyuan.com/project/learn-go-language/images/60.png" alt="pic" />

作用域

在 Go 中,定義在函數(shù)外的變量是全局的,那些定義在函數(shù)內(nèi)部的變量,對于函數(shù)來說 是局部的。如果命名覆蓋——一個局部變量與一個全局變量有相同的名字——在函數(shù) 執(zhí)行的時候,局部變量將覆蓋全局量。

http://wiki.jikexueyuan.com/project/learn-go-language/images/61.png" alt="pic" />

在 2.3 中定義了函數(shù) q() 的局部變量 a。局部變量 a 僅在 q() 中可見。這也就是為什么代碼會打?。?56。在 2.4 中沒有定義局部變量,只有全局變量 a。這將使得對 a 的賦值全局可見。這段代碼將會打?。?55。

在下面的例子中,我們在 f() 中調(diào)用 g():

Listing 2.5. 當函數(shù)調(diào)用函數(shù)時的作用域

http://wiki.jikexueyuan.com/project/learn-go-language/images/62.png" alt="pic" />

輸出內(nèi)容將是:565。局部變量僅僅在執(zhí)行定義它的函數(shù)時有效。

多值返回

Go 一個非常特別的特性(對于編譯語言而言)是函數(shù)和方法可以返回多個值(Python和 Perl 同樣也可以)。這可以用于改進一大堆在 C 程序中糟糕的慣例用法:修改參數(shù)的方式,返回一個錯誤(例如遇到 EOF 則返回 -1)。在 Go 中,Write 返回一個計數(shù)值和一個錯誤:“是的,你寫入了一些字節(jié),但是由于設(shè)備異常,并不是全部都寫入了。” os 包中的 *File.Write 是這樣聲明的:

func (file *File) Write(b []byte) (n int , err e r r o r )

如同文檔所述,它返回寫入的字節(jié)數(shù),并且當 n != len(b) 時,返回非 nil 的 error。這是 Go 中常見的方式。

元組沒有作為原生類型出現(xiàn),所以多返回值可能是最佳的選擇。你可以精確的返回希望的值,而無須重載域空間到特定的錯誤信號上。

命名返回值

Go 函數(shù)的返回值或者結(jié)果參數(shù)可以指定一個名字,并且像原始的變量那樣使用,就像輸入?yún)?shù)那樣。如果對其命名,在函數(shù)開始時,它們會用其類型的零值初始化。如果函數(shù)在不加參數(shù)的情況下執(zhí)行了 return 語句,結(jié)果參數(shù)會返回。用這個特性,允許(再一次的)用較少的代碼做更多的事。

名字不是強制的,但是它們可以使得代碼更加健壯和清晰:這是文檔。例如命名 int 類型的 nextPos 返回值,就能說明哪個代表哪個。

func nextInt(b []byte, pos i n t ) (value, nextPos i n t ) { /* ... */ }

由于命名結(jié)果會被初始化并關(guān)聯(lián)于無修飾的 return,它們可以非常簡單并且清晰。這里有一段 io.ReadFull 的代碼,很好的運用了它:

http://wiki.jikexueyuan.com/project/learn-go-language/images/63.png" alt="pic" />

延遲代碼

假設(shè)有一個函數(shù),打開文件并且對其進行若干讀寫。在這樣的函數(shù)中,經(jīng)常有提前返回的地方。如果你這樣做,就需要關(guān)閉正在工作的文件描述符。這經(jīng)常導致產(chǎn)生下面的代碼:

http://wiki.jikexueyuan.com/project/learn-go-language/images/64.png" alt="pic" />

在這里有許多重復的代碼。為了解決這些,Go 有了 defer 語句。在 defer 后指定的函數(shù)會在函數(shù)退出前調(diào)用。

上面的代碼可以被改寫為下面這樣。將 Close 對應(yīng)的放置于 Open 后,能夠使函數(shù)更加可讀、健壯。

http://wiki.jikexueyuan.com/project/learn-go-language/images/65.png" alt="pic" />

可以將多個函數(shù)放入“延遲列表”中,這個例子來自 [8]:

http://wiki.jikexueyuan.com/project/learn-go-language/images/66.png" alt="pic" />

延遲的函數(shù)是按照后進先出(LIFO)的順序執(zhí)行,所以上面的代碼打?。? 3 2 1 0。利用 defer 甚至可以修改返回值,假設(shè)正在使用命名結(jié)果參數(shù)和函數(shù)符號,例如:

http://wiki.jikexueyuan.com/project/learn-go-language/images/67.png" alt="pic" />

或者這個例子,更加容易了解為什么,以及在哪里需要括號:

http://wiki.jikexueyuan.com/project/learn-go-language/images/68.png" alt="pic" />

在這個(匿名)函數(shù)中,可以訪問任何命名返回參數(shù):

http://wiki.jikexueyuan.com/project/learn-go-language/images/69.png" alt="pic" />

變參

接受不定數(shù)量的參數(shù)的函數(shù)叫做變參函數(shù)。定義函數(shù)使其接受變參:

func myfunc(arg ... i n t ) { }

arg ...int 告訴 Go 這個函數(shù)接受不定數(shù)量的參數(shù)。注意,這些參數(shù)的類型全部是 int。在函數(shù)體中,變量 arg 是一個 int 類型的 slice:

http://wiki.jikexueyuan.com/project/learn-go-language/images/70.png" alt="pic" />

如果不指定變參的類型,默認是空的接口 interface{}(參閱第 5 章)。假設(shè)有另一個變參函數(shù)叫做 myfunc2,下面的例子演示了如何向其傳遞變參:

http://wiki.jikexueyuan.com/project/learn-go-language/images/71.png" alt="pic" />

函數(shù)作為值

就像其他在 Go 中的其他東西一樣,函數(shù)也是值而已。它們可以像下面這樣賦值給變量:

http://wiki.jikexueyuan.com/project/learn-go-language/images/72.png" alt="pic" />

如果使用 fmt.Printf("\%T\n", a) 打印 a 的類型,輸出結(jié)果是 func()。

函數(shù)作為值,也會被用在其他地方,例如 map。這里將整數(shù)轉(zhuǎn)換為函數(shù):

http://wiki.jikexueyuan.com/project/learn-go-language/images/73.png" alt="pic" />

也可以編寫一個接受函數(shù)作為參數(shù)的函數(shù),例如用于操作 int 類型的 slice 的 Map 函數(shù)。這是一個留給讀者的練習,參考在第 34 頁的練習 Q11。

回調(diào)

由于函數(shù)也是值,所以可以很容易的傳遞到其他函數(shù)里,然后可以作為回調(diào)。首先定義一個函數(shù),對整數(shù)做一些“事情”:

http://wiki.jikexueyuan.com/project/learn-go-language/images/74.png" alt="pic" />

這個函數(shù)的標識是 func printit(int),或者沒有函數(shù)名的:func(int)。創(chuàng)建新的函數(shù)使用這個作為回調(diào),需要用到這個標識:

http://wiki.jikexueyuan.com/project/learn-go-language/images/75.png" alt="pic" />

恐慌(Panic)和恢復(Recover)

Go 沒有像 Java 那樣的異常機制,例如你無法像在 Java 中那樣拋出一個異常。作為替代,它使用了恐慌和恢復(panic-and-recover)機制。一定要記得,這應(yīng)當作為最后的手段被使用,你的代碼中應(yīng)當沒有,或者很少的令人恐慌的東西。這是個強大的工具,明智的使用它。那么,應(yīng)該如何使用它呢。下面的描述來自于[7]:

Panic

是一個內(nèi)建函數(shù),可以中斷原有的控制流程,進入一個令人恐慌的流程中。當函數(shù)F 調(diào)用 panic,函數(shù) F 的執(zhí)行被中斷,并且 F 中的延遲函數(shù)會正常執(zhí)行,然后F 返回到調(diào)用它的地方。在調(diào)用的地方,F(xiàn) 的行為就像調(diào)用 panic。這一過程繼續(xù)向上,直到程序崩潰時的所有 goroutine 返回。

恐慌可以直接調(diào)用 panic 產(chǎn)生。也可以由運行時錯誤產(chǎn)生,例如訪問越界的數(shù)組。

Recover

是一個內(nèi)建的函數(shù),可以讓進入令人恐慌的流程中的 goroutine 恢復過來。recover 僅在延遲函數(shù)中有效。

在正常的執(zhí)行過程中,調(diào)用 recover 會返回 nil 并且沒有其他任何效果。如果當前的 goroutine 陷入恐慌,調(diào)用 recover 可以捕獲到 panic 的輸入值,并且恢復正常的執(zhí)行。

這個函數(shù)檢查作為其參數(shù)的函數(shù)在執(zhí)行時是否會產(chǎn)生 panic:

http://wiki.jikexueyuan.com/project/learn-go-language/images/76.png" alt="pic" />

0 .定義一個新函數(shù) throwsPanic 接受一個函數(shù)作為參數(shù)(參看“函數(shù)作為值”)。函數(shù) f 產(chǎn)生 panic,就返回 true,否則返回 false;

  1. 定義了一個利用 recover 的 defer 函數(shù)。如果當前的 goroutine 產(chǎn)生了 panic,這個 defer 函數(shù)能夠發(fā)現(xiàn)。當 recover() 返回非 nil 值,設(shè)置 b 為 true;
    2 .調(diào)用作為參數(shù)接收的函數(shù);
  2. 返回 b 的值。由于 b 是命名返回值。

(第 28 頁),無須指定 b。

練習

Q5. (0) 平均值

  1. 編寫一個函數(shù)用于計算一個 float64 類型的 slice 的平均值。

Q6. (0) 整數(shù)順序

  1. 編寫函數(shù),返回其(兩個)參數(shù)正確的(自然)數(shù)字順序:

f(7,2) ! 2,7
f(2,7) ! 2,7

Q7. (1) 作用域

  1. 下面的程序有什么錯誤?

http://wiki.jikexueyuan.com/project/learn-go-language/images/77.png" alt="pic" />

Q8. (1) 棧

  1. 創(chuàng)建一個固定大小保存整數(shù)的棧。它無須超出限制的增長。定義 push 函數(shù)——將數(shù)據(jù)放入棧,和 pop 函數(shù)——從棧中取得內(nèi)容。棧應(yīng)當是后進先出(LIFO)的。

http://wiki.jikexueyuan.com/project/learn-go-language/images/78.png" alt="pic" />

2 .更進一步。編寫一個 String 方法將棧轉(zhuǎn)化為字符串形式的表達。可以這樣的方式打印整個棧:fmt.Printf("My stack %v\n", stack)

??梢员惠敵龀蛇@樣的形式:[0:m] [1:l] [2:k]

Q9. (1) 變參

  1. 編寫函數(shù)接受整數(shù)類型變參,并且每行打印一個數(shù)字。

Q10. (1) 斐波那契

  1. 斐波那契數(shù)列以:1; 1; 2; 3; 5; 8; 13; : : : 開始?;蛘哂脭?shù)學形式表達:x1 = 1; x2 = 1; xn = xn
上一篇:Bibliography下一篇:索引