defer函數參數求值簡要分析

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

一. 引子

書接上文,在發表了《對一段Go語言代碼輸出結果的簡要分析》一文之後,原問題提出者又有了新問題,這是一個典型Gopher學習Go的曆程,想必很多Gopher們,包括我自己都遇到過的。我們先來看看這段代碼(來自原問題提出者):

// https://play.golang.org/p/dOUFNj96EIQpackage mainimport "fmt"func main() {    var i int = 1    defer fmt.Println("result =>",func() int { return i * 2 }())    i++}

這裡顯然有坑!初學者的常規邏輯一般是:defer是在main函數退出後執行,退出前i已經做了+1操作,值變成了2,這樣一來defer後的Println應該輸出:result => 4 才對!實際輸出結果呢?

result => 2

這怎麼可能?

實際上不光是defer這樣,即使用go關鍵字替換掉defer,輸出的結果也是一樣的:result => 2

package mainimport (     "fmt"    "time")func main() {    var i int = 1    go fmt.Println("result =>",func() int { return i * 2 }())    i++    time.Sleep(3*time.Second)}

二. defer function分析

那麼究竟為什麼輸出的是2,而不是4呢?因為無論是go關鍵字還是defer關鍵字,在代碼執行到它們時,編譯器都要為它們後面的函數準備好函數調用的參數堆棧,要確定的參數值和參數類型大小。這樣一來就得去求值:對它們後面的函數的參數進行求值。

以本文第一個defer那個例子為例!我們需要為defer後面的函數進行參數求值:

defer fmt.Println("result =>",func() int { return i * 2 }())

此時defer後面的函數是Println,這裡Println有兩個輸入參數:”result =>”和func() int {return i * 2}(),前者就是一個字串常量值,而後者是一個函數調用,我們需要對該函數調用進行求值。而在此時,i依然為1,因此Println的第二個參數的求值結果為2,於是上面defer的調用就等價於:

defer fmt.Println("result =>",2)

因此,無論最終i的值變成了多少,defer最終的輸出都是:result => 2。go關鍵字後面的參數亦是如此。其實這個過程與為普通函數的調用做準備是一樣的,也要先對函數的參數進行求值,之後再進入函數體,只不過defer將進入函數執行的過程延遲到defer的調用方退出之前了。

搞清楚這個defer原理後,我們如果想在defer函數執行時輸出4,那麼使用一個閉包函數即可:

// https://play.golang.org/p/Eux7zpSr7O8package mainimport "fmt"func main() {        var i int = 1        defer func() {                fmt.Println("result =>", func() int { return i * 2 }())        }()        i++}

這裡我們看到defer 後面是一個不帶任何參數的匿名函數,所謂的對參數求值也是無值可求。在main函數退出前,defer後面的匿名函數真正執行時i的值已經是2,因此閉包函數中的Println輸出4。

三. defer method分析

defer後面除了可以跟著普通函數調用外,還可以使用方法調用(method):

defer instance.Method(x,y)

這可能又會讓初學者有些迷惑,多數又是Method的receiver類型以及go自動對instance的Method調用解引用或求地址的問題,我們“趁熱打鐵”,再來基於上一篇文章《對一段Go語言代碼輸出結果的簡要分析》中的例子做些修改,看看將go關鍵字換成defer會是一種什麼情況:

//https://play.golang.org/p/T8CdRfEn2h4package mainimport (    "fmt")type field struct {    name string}func (p *field) print() {    fmt.Println(p.name)}func main() {    data1 := []*field{{"one"}, {"two"}, {"three"}}    for _, v := range data1 {        defer v.print()    }    data2 := []field{{"four"}, {"five"}, {"six"}}    for _, v := range data2 {        defer v.print()    }}

這段代碼運行起來輸出:

sixsixsixthreetwoone

有了《對一段Go語言代碼輸出結果的簡要分析》一文中的思路作為基礎,對上面這段代碼的分析也就不難了。沒錯,還是按照我上一篇的“等價轉換”思路去思考,將method轉換為function後,再分析。上面的代碼可以等價變換為下面代碼:

https://play.golang.org/p/a-vOSz4N3jbpackage mainimport (    "fmt")type field struct {    name string}func print(p *field) {    fmt.Println(p.name)}func main() {    data1 := []*field{{"one"}, {"two"}, {"three"}}    for _, v := range data1 {        defer print(v)    }    data2 := []field{{"four"}, {"five"}, {"six"}}    for _, v := range data2 {        defer print(&v)    }}

接下來,我們就利用defer的“參數即時求值”原理,對上面的代碼作分析:

data1的三次迭代:defer的參數求值完後,defer print(v)調用分別變成了:

  • defer print(&field{“one”})
  • defer print(&field{“two”})
  • defer print(&field{“three”})

data2的三次迭代,defer的參數求值完後,defer print(v)調用分別變成了:

  • defer print(&v)
  • defer print(&v)
  • defer print(&v)

於是在main退出前,defer函數按defer被調用的反向順序執行:

  • print(&v)
  • print(&v)
  • print(&v)
  • print(&field{“three”})
  • print(&field{“two”})
  • print(&field{“one”})

而此刻:v中儲存的值為field{“six”},於是前三次print均輸出”six”。

四. 小結

defer雖然帶來一些效能損耗,但defer的適當使用可以讓程式的邏輯結構變得更為簡潔。

《對一段Go語言代碼輸出結果的簡要分析》一文發出後,出乎意料地收到一些反饋,其實很多Go初學者希望能看到一些像這樣的入門,但又“較真”的,最好再涉及點底層實現的文章。以後有精力會多多關注這一點的。歡迎大家來本站繼續交流,從各位朋友提出的問題中,我也能收穫到靈感^0^。

著名雲主機服務廠商DigitalOcean發布最新的主機計劃,入門級Droplet配置升級為:1 core CPU、1G記憶體、25G高速SSD,價格5$/月。有使用DigitalOcean需求的朋友,可以開啟這個連結地址:https://m.do.co/c/bff6eed92687 開啟你的DO主機之路。

我的連絡方式:

微博:https://weibo.com/bigwhite20xx
公眾號:iamtonybai
部落格:tonybai.com
github: https://github.com/bigwhite

讚賞:

商務合作方式:撰稿、出書、培訓、線上課程、合夥創業、諮詢、廣告合作。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.