2012-11-09 80 views
4

我只是通過Effective Go並在Pointers vs. Values部分閱讀,臨近年底說:轉到接收器的方法調用語法混亂

關於指針與對接收者值的規則是值的方法可以在指針和值上調用,但指針方法只能在指針上調用。這是因爲指針方法可以修改接收器;在值的副本上調用它們會導致這些修改被丟棄。

爲了測試它,我寫了這個:

package main 

import (
    "fmt" 
    "reflect" 
) 

type age int 

func (a age) String() string { 
    return fmt.Sprintf("%d yeasr(s) old", int(a)) 
} 

func (a *age) Set(newAge int) { 
    if newAge >= 0 { 
    *a = age(newAge) 
    } 
} 

func main() { 
    var vAge age = 5 
    pAge := new(age) 

    fmt.Printf("TypeOf =>\n\tvAge: %v\n\tpAge: %v\n", reflect.TypeOf(vAge), 
    reflect.TypeOf(pAge)) 

    fmt.Printf("vAge.String(): %v\n", vAge.String()) 
    fmt.Printf("vAge.Set(10)\n") 
    vAge.Set(10) 
    fmt.Printf("vAge.String(): %v\n", vAge.String()) 

    fmt.Printf("pAge.String(): %v\n", pAge.String()) 
    fmt.Printf("pAge.Set(10)\n") 
    pAge.Set(10) 
    fmt.Printf("pAge.String(): %v\n", pAge.String()) 
} 

並將其編譯,即使該文件說,它不應該,因爲指針方法Set()不應該通過價值VAR vAge可調用。我在這裏做錯了什麼?

回答

9

這是有效的,因爲vAge是可尋址的。請參閱語言規範下Calls最後一段:

一個方法調用XM()是有效的,如果(類型)的方法集合X 包含m和參數列表可以分配到參數列表米的 。如果x是可尋址的並且x的方法集合包含m,則x.m()是 (& x).m()的簡寫。

+1

所以基本上「Effective Go」在這方面是錯的? – Jibran

+0

我不會說這是錯的。在這個有限的情況下,Go隱式地取了x的地址,所以該方法仍然在一個指針上運行。區別在於,您將vAge vs&vAge傳遞給期望fmt.Stringer的函數。第一個會失敗,第二個不會。 –

3

vAge不被認爲是隻有一個「值變量」,因爲它是在存儲器中存儲age類型的值的已知位置。只看作vAge作爲它的值,vAge.Set(10)不是作爲一個表達式有效,但由於vAge是可尋址的,規範聲明可以將表達式視爲「獲取vAge的地址」的縮寫,並調用Set在編譯時的「」上,當我們將能夠驗證Set是爲age*age設置的方法的一部分。基本上,如果編譯器確定它是必要和可能的,那麼基本上允許編譯器對原始表達式進行文本擴展。

同時,編譯器將允許您調用age(23).String()而不是age(23).Set(10)。在這種情況下,我們正在使用類型爲age的不可尋址值。由於&age(23)無效,所以說(&age(23)).Set(10)不能有效;編譯器將不會執行該擴展。

看看Effective Go的例子,您並不是直接在的完整類型範圍內調用b.Write()。您正在創建b的臨時副本,並試圖將其作爲interface io.Writer()類型的值傳遞。問題是執行Printf不知道任何關於被傳入的對象,除非它已承諾它知道如何接收Write(),所以它不知道在調用之前將byteSlice轉換爲*ByteSlice功能。在編譯時決定是否要處理b,而編譯PrintF的前提是它的第一個參數將知道如何在不被引用的情況下接收Write()

您可能會認爲如果系統知道如何獲取age指針並將其轉換爲age值,那它應該能夠做到相反;儘管如此,t確實沒有意義。在Effective Go示例中,如果您要通過b而不是&b,那麼您將修改PrintF返回後不再存在的切片,這幾乎沒有用。在我上面的age示例中,取值23並用值10覆蓋它是毫無意義的。在第一種情況下,編譯器停止並向程序員詢問當將b關閉時她的真正意圖。在後一種情況下,編譯器拒絕修改常量值當然是有意義的。

此外,我不認爲該系統動態地延伸age的方法設置爲*age;我瘋狂的猜測是,指針類型靜態地給了每個基類型方法的方法,它只是取消引用指針並調用基類的方法。自動執行此操作是安全的,因爲無論哪種方法都不能改變指針。另一方面,擴展一組要求修改數據的方法並不總是合情合理的,通過將它們包裝在一起,使得修改後的數據不久之後就會消失。確實有這樣做是有道理的,但是這需要程序員明確地決定,編譯器停止並要求這樣做是有道理的。

tl; dr我認爲Effective Go中的段落可能會使用一些重新措辭(雖然我可能太冗長以致於無法完成這項工作),但它是正確的。類型*X的指針實際上可以訪問所有X的方法,但'X'無法訪問*X。因此,在確定一個對象是否可以滿足給定的接口時,*X可以滿足任何接口X可以,但反過來是不正確的。此外,即使在編譯時已知範圍內的X類型的變量 - 因此編譯器可以將其轉換爲*X - 它將拒絕爲接口實現的目的這樣做,因爲這樣做可能不會合理。