chapter5、6 golang的函數(shù)與方法

函數(shù)

//常規(guī)的函數(shù)定義
func 方法名(參數(shù)列表) 返回值 {
    定義
}

函數(shù)的值(閉包)

在Go中各墨,函數(shù)被看作第一類值(first-class values):函數(shù)像其他值一樣,擁有類型启涯,可以被賦值給其他變量贬堵,傳遞給函數(shù)恃轩,從函數(shù)返回。函數(shù)類型的零值是nil黎做。調(diào)用值為nil的函數(shù)值會引起panic錯誤:

var f func(int) int
f(3) // 此處f的值為nil, 會引起panic錯誤

函數(shù)值不僅僅是一串代碼叉跛,還記錄了狀態(tài)。Go使用閉包(closures)技術(shù)實現(xiàn)函數(shù)值蒸殿,Go程序員也把函數(shù)值叫做閉包筷厘。我們看個閉包的例子:

func f1(limit int) (func(v int) bool) {
    //編譯器發(fā)現(xiàn)limit逃逸了,自動在堆上分配
    return func (v int) bool { return v>limit} 
}

func main() {
    closure := f1(5)
    fmt.Printf("%v\n", closure(1)) //false
    fmt.Printf("%v\n", closure(5)) //false
    fmt.Printf("%v\n", closure(10)) //true
}

在這個例子中宏所,f1函數(shù)傳入limit參數(shù)酥艳,返回一個閉包,閉包接受一個參數(shù)v爬骤,判斷v是否大于之前設(shè)置進去的limit充石。

可變參數(shù)列表

可變參數(shù),即參數(shù)不是固定的霞玄,例如fmt.Printf函數(shù)那樣骤铃,注意只有最后一個參數(shù)才可以是聲明為可變參數(shù),聲明:

func 函數(shù)名(變量名...類型) 返回值

我們看個例子:

package main

import (
    "fmt"
)

func f1(name string, vals... int) (sum int) {
    for _, v := range vals {
        sum += v
    }
    sum += len(name)
    return
}

func main() {
    fmt.Printf("%d\n", f1("abc", 1,2,3,4 )) //13
}

函數(shù)的延遲執(zhí)行 defer

包含defer語句的函數(shù)執(zhí)行完畢后(例如return坷剧、panic)惰爬,釋放堆棧前會調(diào)用被聲明defer的語句,常用于釋放資源惫企、記錄函數(shù)執(zhí)行耗時等撕瞧,有一下幾個特點:

  1. 當(dāng)defer被聲明時,其參數(shù)就會被實時解析
  2. 執(zhí)行順序和聲明順序相反
  3. defer可以讀取有名返回值

看個例子:

package main

import (
    "fmt"
)

//演示defer的函數(shù)可以訪問返回值
func f2() (v int) {
    defer func (){ v++}()
    return 1 //執(zhí)行這個時,把v置為1
}

//演示defer聲明即解釋
func f3(i int) (v int) {
    defer func(j int) { v += j} (i) //此時函數(shù)i已被解析為10,后面修改i的值無影響
    v = i
    i = i*2
    return
}

//演示defer的執(zhí)行順序,與聲明順序相反
func f4() {
    defer func() {fmt.Printf("first\n")} ()
    defer func() {fmt.Printf("second\n")} ()
}

func main() {
    fmt.Printf("%d\n", f2()) // 13
    fmt.Printf("%d\n", f3(10)) // 20
    f4() //second\nfirst\n
}

典型的使用場景雅任,函數(shù)執(zhí)行完畢關(guān)閉資源:

func do() error {
    f, err := os.Open("book.txt")
    if err != nil {
        return err
    }

    defer func(f io.Closer) {
        if err := f.Close(); err != nil {
            // log etc
        }
    }(f)

    // ..code...

    f, err = os.Open("another-book.txt")
    if err != nil {
        return err
    }

    defer func(f io.Closer) {
        if err := f.Close(); err != nil {
            // log etc
        }
    }(f)

    return nil
}

在這里例子中可以看到风范,我們判斷了Close()是否成功,因為在一些文件系統(tǒng)中沪么,尤其是NFS硼婿,寫文件出錯往往被延遲到Close的時候才反饋,所以必須檢查Close的狀態(tài)禽车。

異常panic

Go有別于那些將函數(shù)運行失敗看作是異常的語言寇漫。雖然Go有各種異常機制,但這些機制僅僅用于嚴重的錯誤殉摔,而不是那些在健壯程序中應(yīng)該被避免的程序錯誤州胳。runtime在一些情況下會拋出異常,例如除0栓撞,我們也能使用panic關(guān)鍵字自己拋出異常

panic(異常的值) //值是啥都行

出現(xiàn)異常之后,默認情況就是程序退出并打印堆棧:

package main


func f6() {
    func () {
        func () int {
            x := 0
            y := 5/x
            return y
        }()
    }()
}

func main() {

    f6()

}

輸出

panic: runtime error: integer divide by zero

goroutine 1 [running]:
main.f6.func1.1(...)
    /Users/kitmanzheng/study/go/src/test_func.go:8
main.f6.func1()
    /Users/kitmanzheng/study/go/src/test_func.go:10 +0x11
main.f6()
    /Users/kitmanzheng/study/go/src/test_func.go:11 +0x20
main.main()
    /Users/kitmanzheng/study/go/src/test_func.go:16 +0x20
exit status 2

如果不想程序退出的話瓤湘,也有辦法,就是使用recover捕捉異常弛说,然后返回error挽懦。在沒發(fā)生panic的情況下,調(diào)用recover會返回nil木人,發(fā)生了panic,那么就是panic的值醒第。看個例子:

package main

import (
    "fmt"
)

type shouldRecover struct{}
type emptyStruct struct{}

func f6() (err error) {
    defer func () {
        switch p := recover(); p {
            case nil: //donoting
        case shouldRecover{}:
            err = fmt.Errorf("occur panic but had recovered")
        default:
            panic(p)
        }
    } ()

    func () {
        func () int {
            panic(shouldRecover{})
            //panic(emptyStruct{})
            x := 0
            y := 5/x
            return y
        }()
    }()

    return
}


func main() {
    err := f6()
    if err != nil {
        fmt.Printf("fail %v\n", err)
    } else {
        fmt.Printf("success\n")
    }
}

輸出
fail occur panic but had recovered
在這個例子中淘讥,我們手動拋出一個panic圃伶,值是shouldRecover,然后外層使用defer + 匿名函數(shù) + recover去捕捉異常蒲列,發(fā)現(xiàn)panic的值是shouldRecover那么就不退出,而是返回error蝗岖。

方法

//這種只能給type定義的類型用 
func (type類型參數(shù)) 方法名(參數(shù)列表) 返回值 {
    定義
}
//eg:
func (t TestType) testFunc() int {
    //...
}

例子中t稱為接收器,可以是該類型本身抵赢,或該類型的指針,由于是值傳遞划提,所以是接收器是該類型時,會復(fù)制值鹏往,類型比較大時開銷大,可以選擇使用指針降低開銷伊履。而且在使用defer的時候款违,由于值復(fù)制,如果不用指針插爹,變量發(fā)生了變化,但是defer運行時還是基于老變量運行的,容易會造成一些坑溢陪,除非你明確知道自己要這么做。建議func (*type)而不是func(type)。但是如果一個類型低層實際是一個指針杉编,那么不允許在使用該類型的指針作為接收器。

當(dāng)我們使用指針作為接收器時嘶朱,記得檢查是否是nil光酣。

非常重要的一點是疏遏,T使用接收者是T和*T的方法救军,而T只使用接收者是T的方法,T能直接調(diào)接受者是*T方法,僅僅是一個語法糖唱遭,編譯器幫我們?nèi)〉刂妨?/strong>。

看下面這個例子:

type myInt struct {
    owner string
    value int
}

func (a myInt) Owner(suffix string) string { //golang不支持默認參數(shù)
    return a.owner + suffix
}

func (a *myInt) SetOwner(owner string) {
    if a == nil {
        fmt.Println("set owner to nil point is invalid")
        return
    }
    a.owner = owner
}

func (a myInt) SetOwner2(owner string) { //golang函數(shù)參數(shù)按值傳遞,所以這個方法實際只是修改臨時變量的owner
    a.owner = owner
}

func SetOwner3(a *myInt, owner string) {
    if a == nil {
        fmt.Println("set owner to nil point is invalid")
        return
    }
    a.owner = owner
}

func main() {
    var k = myInt{"kitman", 3}

    fmt.Print(k.value, " ", k.Owner("aa"), "\n") //輸出3 kitmanaa

    k.SetOwner("ak") //相當(dāng)于SetOwner(&k, "ak")
    fmt.Print(k.value, " ", k.Owner("bb"), "\n") //輸出3 akbb

    k.SetOwner2("sss")  //相當(dāng)于SetOwner(k, "sss")
    fmt.Print(k.value, " ", k.Owner("bb"), "\n") //輸出3 akbb

    SetOwner3(&k, "sss")
    fmt.Print(k.value, " ", k.Owner("bb"), "\n") //輸出3 sssbb

    var k2 *myInt = nil
    k2.SetOwner("aa")     //輸出set owner to nil point is invalid
}

輸出
3 kitmanaa
3 akbb
3 akbb
3 sssbb
set owner to nil point is invalid
通過上面的例子疫鹊,我們可以發(fā)現(xiàn)一些知識點:

  1. 使用第二種函數(shù)定義的方法司致,那么就和c++的類差不多。本質(zhì)上和普通函數(shù)一樣脂矫,就是語法上的差別而已。
  2. 就算給type類型定義方法奕枢,函數(shù)參數(shù)也是按值傳遞的,所以type參數(shù)使用指針才能修改變量缝彬。
  3. nil指針也能調(diào)用方法哺眯,但是如果方法里面沒判斷指針是否是nil谷浅,那么就會core

面向?qū)ο罄^承語義

可以通過使用匿名成員 + 定義方法,實現(xiàn)部分繼承的語義:

package main

import (
    "fmt"
)

type Base struct {
    y int
    Y int
}

func (b *Base) FuncByPoint() int {
    if (b == nil) {
        return 0;
    }

    return b.y*b.Y
}

func (b Base) FuncByValue() int {
    return b.y*b.Y
}

type Child struct {
    Base
    x int
    X int
}

func (c *Child) FuncByPoint() int {
    if (c == nil) {
        return 0
    }

    return c.x*c.X
}

func main() {
    var c Child
    c.y = 2
    c.Y = 3
    fmt.Printf("%v\n", c.FuncByPoint())          //0
    fmt.Printf("%v\n", c.Base.FuncByPoint())//6
    fmt.Printf("%v\n", c.FuncByValue()).  //6

    var f1 func() int
    f1 = c.FuncByPoint
    fmt.Printf("%v\n", f1())   //0

    var f2 func(*Child) int
    f2 = (*Child).FuncByPoint
    fmt.Printf("%v\n", f2(&c)) //0
}

這個例子可以看到撼玄,Base中定義的方法墩邀,被外層的同名方法覆蓋,需要顯式指明才能調(diào)用到Base中的方法眉睹。注意golang中不存在真正的繼承,這是嵌入匿名成員竹海,用匿名成員的方法去理解這樣的語法。另外孔飒,方法的值也是第一類變量,能賦值給別的變量罩润,比c/c++靈活作岖,golang無論是對象方法昔馋,還是類型的方法猛频,都能賦值給別的變量,可以參照例子中的寫法鹿寻。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市坦敌,隨后出現(xiàn)的幾起案子痢法,更是在濱河造成了極大的恐慌狱窘,老刑警劉巖财搁,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異搭儒,居然都是意外死亡,警方通過查閱死者的電腦和手機淹禾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來汪疮,“玉大人,你說我怎么就攤上這事铲咨。” “怎么了?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵隆檀,是天一觀的道長。 經(jīng)常有香客問我恐仑,道長为鳄,這世上最難降的妖魔是什么裳仆? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任歧斟,我火速辦了婚禮,結(jié)果婚禮上静袖,老公的妹妹穿的比我還像新娘俊扭。我一直安慰自己队橙,他們只是感情好萨惑,可當(dāng)我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著解总,像睡著了一般。 火紅的嫁衣襯著肌膚如雪倾鲫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天乌昔,我揣著相機與錄音,去河邊找鬼磕道。 笑死,一個胖子當(dāng)著我的面吹牛伶丐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播哗魂,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼漓雅,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了邻吞?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤崔列,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后赵讯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體趣效,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年跷敬,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片斤寇。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡拥褂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出饺鹃,到底是詐尸還是另有隱情间雀,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布惹挟,位于F島的核電站缝驳,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏用狱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一夏伊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧溺忧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽报账。三九已至,卻和暖如春透罢,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背乾胶。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工朽寞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留识窿,地道東北人脑融。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像甥温,于是被迫代替她去往敵國和親锻煌。 傳聞我的和親對象是個殘疾皇子姻蚓,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容