第五章:函數
·當函數存在返回值的時候必須顯式地以return語句結束,除非函數明確不會走完整個流程(比如一個for死循環)
·函數的類型
稱爲函數簽名,當一個函數擁有相同的形參列表和返回列表時,認爲這兩個函數的類型或簽名是相同的。形參和返回參數的名稱不會影響函數的類型
·實參是按值傳遞的,所以函數接收到的是沒有實參的副本,修改形參的變量並不會影響到實參的值。
但是,如果提供的實參是引用類型,比如 指針,slice,map,函數或者通道,那麼形參變量就有可能間接的修改實參變量
·有些函數沒有函數體,說明不是用go語言實現的,eg
package math
func Sin(x float64) float64 //使用彙編語言實現
·錯誤
·習慣上將錯誤值作爲最後一個結果返回,如果錯誤只有一個情況,結果通常設置爲布爾類型
·與其他語言不同,go語言通過使用普通變量的值而非異常來報告錯誤。儘管go有異常機制,但是go語言的異常是針對bug導致的預料外的錯誤
·go程序使用通常的控制流機制(比如if和return語句)也對錯誤
·錯誤處理策略:
當函數調用返回一個錯誤時,調用者應該負責檢查錯誤並採取合適的處理應對
·首先最常見的是將錯誤傳遞下去,在子例程中發生的錯誤通過返回值返回給主例程,這樣在主獲得錯誤並進行判斷處理
·log.Fatalf("Size is down: %v\n",err) //以這種方式處理錯誤,它默認會將時間和日期作爲前綴添加到錯誤消息前
·當讀取文件到結束的位置,io包會返回 EOF 錯誤
package io
import "errors"
var EOF = errors.New("EOF")
eg: 一段讀取文件的錯誤處理 (僞代碼)
for {
r,_,err := in.read()
if (err == io.EOF) {
break
}
if err != nil {
return fmt.Errorf("read failed: %v",err)
}
}
·函數變量:
var f func(int) int
func square(n int) int {return n * n}
func negative(n int) int {return -n}
func product(m,n int) int {return m * n}
func main() {
f := square //f的類型已經確定了
fmt.Println(f(3))
/**
negative 和 square 的函數類型是一致的,所以f可以完成賦值
函數的類型 是由形參列表和返回值列表確定的
*/
f = negative
fmt.Println(f(3))
//f = product //編譯錯誤: 不能把類型 func(int,int) int 賦值 給 func (int) int
//fmt.Println(f(3))
g := product
fmt.Println(g(2,3))
}
·不能調用一個空的函數變量
var f func(int) int //
func main() {
fmt.Println(f(3))
}
·defer : 正確使用defer是在成功獲得資源之後
resp,err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close() //確定了成功獲取資源之後釋放資源
·可以使用改方法來測試一個函數的調用時間
func main() {
defer f1()() //注意這裏是兩個括號
time.Sleep(time.Second * 5)
}
func f1() func() {
start := time.Now() //這個不是延遲執行
return func() { //延遲執行的是返回的這個函數
fmt.Println(time.Since(start)) //可以計算函數的調用時間
}
}
·延遲函數可以獲取和改變返回值
func main() {
fmt.Println(double(5))
}
func double(x int) (result int) {
defer func() {
fmt.Println(x,result)
result += 100 //延遲函數不到函數的最後一刻是不會執行的
}()
return x + x
}
·注意延時函數在循環裏面的使用
for _,filename := range filenames {
f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close //這樣f.Close根本沒有在每次循環的時候關閉,會用盡資源
}
解決方案是:將for循環放到一個函數裏面,這樣函數結束就會執行defer
for _, filename := range filenames {
if err := doFile(filename); err != nil {
return err
}
}
func doFile(filename string) error {
f,err := os.Open(filename)
if err != nil {return err}
defer f.Close() //這樣每次執行都會調用到f.Close
}
·後defer的先執行,相當於一個棧