日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
gopl方法和接口

方法聲明

寫一個簡單的方法:

成都創(chuàng)新互聯(lián)公司-專業(yè)網(wǎng)站定制、快速模板網(wǎng)站建設(shè)、高性價比華寧網(wǎng)站開發(fā)、企業(yè)建站全套包干低至880元,成熟完善的模板庫,直接使用。一站式華寧網(wǎng)站制作公司更省心,省錢,快速模板網(wǎng)站建設(shè)找我們,業(yè)務(wù)覆蓋華寧地區(qū)。費用合理售后完善,10年實體公司更值得信賴。

type Point struct{X, Y float64}

// 普通的函數(shù)
func Distance(p, q Point) float64 {
    return math.Hypot(q.X-p.X, q.Y-p.Y)
}

// 同樣的作用,用方法實現(xiàn)
func (p Point) Distance(q Point) float64 {
    return math.Hypot(q.X-p.X, q.Y-p.Y)
}

接收者:附加的參數(shù) p 稱為方法的接收者。
調(diào)用方法的時候,接收者在方法名的前面。這樣就和聲明保持一致:

p := Point{1, 2}
q := Point{4, 6}
fmt.Println(Distance(p, q)) // 函數(shù)調(diào)用
fmt.Println(p.Distance(q))  // 方法調(diào)用

選擇子:表達(dá)是 p.Distance 稱作選擇子(selector),因為它為接收者 p 選擇合適的 Distance 方法。

指針接收者的方法

對于函數(shù),它會復(fù)制每一只實參變量。如果函數(shù)需要更新一個變量,或者是因為實參太大而需要避免復(fù)制整個實參,就需要使用指針來傳遞變量的地址。
對于方法的接受者,也可以將方法綁定到指針類型。習(xí)慣上遵循如果一個類型的任何一個方法使用指針接收者,那么所有該類型的方法都應(yīng)該使用指針接收者,即使有些方法不一定需要。
另外,為了防止混淆,不允許本身是指針的類型進行方法聲明,會有編譯錯誤:

type p *int
func (p) f() { /*...*/ } // 編譯錯誤:非法的接收者類型

方法變量與表達(dá)式

方法變量(method value)

通常是在相同的表達(dá)式里使用和調(diào)用方法,但是把兩個操作分開也是可以的。選擇子 p.Distance 可以賦予一個方法變量,它是一個函數(shù),把方法(Point.Distance)綁定到一個接收者 p 上。函數(shù)只需要提供實參而不需要提供接收者就能夠調(diào)用:

p := Point{1, 2}
q := Point{4, 6}
distanceFromP := p.Distance // 方法變量
fmt.Println(distanceFromP(q))

這里 p.Distance 是選擇子,把它賦值給變量 distanceFromP,這個變量就是方法變量,并且這個變量是一個函數(shù)。
如果包內(nèi)的 API 調(diào)用一個函數(shù)值,并且使用者期望這個函數(shù)的行為是調(diào)用一個特定接收者的方法,方法變量就非常有用。使用方法變量還可以是代碼更加簡潔:

type Rocket struct { /* ... */ }
func (r *Rocket) Launch() { /* ... */ }

r := new(Rocket)
time.AfterFunc(10 * time.Second, func() { r.Launch() }) // 如果沒有方法變量,那么要把執(zhí)行一個方法包在一個函數(shù)里,等到函數(shù)被調(diào)用后執(zhí)行
time.AfterFunc(10 * time.Second, r.Launch)  // 使用方法變量,這里 r.Launch 就是一個函數(shù),只是沒有賦值給某個變量,沒有函數(shù)名

函數(shù) time.AfterFunc 的作用是在指定的延遲后調(diào)用一個函數(shù)。上面說了,方法變量也是函數(shù)。

方法表達(dá)式(method expression)

調(diào)用方法的時候必須提供接收者,并且按照選擇子的語法進行調(diào)用。
方法表達(dá)式,寫成 T.f 或者 (*T.f)。
其中 T 是類型,是一種函數(shù)變量,把原來方法的接收者替換成函數(shù)的第一個形參,因此它可以像平常的函數(shù)一樣調(diào)用:

p := Point{1, 2}
q := Point{4, 6}
distance :=  Point.Distance  // 方法表達(dá)式
fmt.Println(distance(p, q))
fmt.Printf("%T\n", distance) // "func(Point, Point) float64"

如果需要一個值來代表多個方法中的一個,而方法都屬于同一個類型,方法表達(dá)式可以實現(xiàn)讓這個值所對應(yīng)的方法來處理不同的接收者。就是可以把一個方法變成一個函數(shù),函數(shù)的變量會增加一個,第一個變量就是原來方法中的接收者。其實各個參數(shù)的順序還是一樣的,原本第一個參數(shù)在 func 前,現(xiàn)在移動到了 func 后面。 p.Distance(q) 變成了 distance(p, q)。

接口類型

io包定義了很多有用的接口:

  • io.Writer : 抽象了所有寫入字節(jié)的類型,下面會列舉
  • io.Reader : 抽象了所有可以讀取字節(jié)的類型
  • io.Closer : 抽象了所有可以關(guān)閉的類型,比如文件或者網(wǎng)絡(luò)連接

io.Writer 是一個廣泛使用的接口,它負(fù)責(zé)所有可以寫入字節(jié)的抽象,包括但不限于下面列舉的這些:

  • 文件
  • 內(nèi)存緩沖區(qū)
  • 網(wǎng)絡(luò)連接
  • HTTP客戶端
  • 打包器(archiver)
  • 散列器(hasher)

接口值

接口值,就是一個接口類型的值。分兩個部分:

  • 動態(tài)類型: 該接口的具體類型
  • 動態(tài)值: 該具體類型的一個值
var w io.Writer  // 聲明接口,動態(tài)類型和動態(tài)值都是nil
w = os.Stdout  // 有動態(tài)類型,也有動態(tài)值
w = io.Writer(os.Stdout)  // 和上面這句等價,把一個具體類型顯式轉(zhuǎn)換為接口類型
w = new(bytes.Buffer)  // 有動態(tài)類型,也有動態(tài)值
w = nil  // 把動態(tài)類型和動態(tài)值都設(shè)置為nil,恢復(fù)到聲明時的狀態(tài)

比較接口值

接口值可以用 == 和 != 來比較。動態(tài)類型一致,然后動態(tài)值相等(使用動態(tài)類型的 == 來比較),那么接口值相等。接口值都是nil也是相等的。
可以作為map的key,也可以作為switch語句的操作數(shù),因為可以比較。
動態(tài)值可能是不可比較的類型,比如切片。對這樣的接口進行比較,就會Panic。把這樣的接口用作map的key或者switch語句的操作數(shù)時也同樣會Panic。所以,僅在能確認(rèn)接口值包含的動態(tài)值可以比較時,才比較接口值。
fmt 包的 %T 打印出來的就是動態(tài)類型。在內(nèi)部實現(xiàn)中,fmt 用反射來拿到接口動態(tài)類型的名字。

注意:含有空指針的非空接口

空的接口值(動態(tài)類型和動態(tài)值都為空)和僅僅動態(tài)值為nil的接口值是不一樣的。

const debug = true

func main() {
    var buf *bytes.Buffer
    if debug {
        buf = new(bytes.Buffer)
    }
    f(buf)
    if debug {
        // ...使用 buf...
    }
}

// 如果 out 不是 nil,那么會向其寫入輸出的數(shù)據(jù)
func f(out io.Writer) {
    // ...其他代碼...
    if out != nil {
        out.Write([]byte("done\n"))
    }
}

這里,把一個類型為 *bytes.Buffer 的空指針賦給了 out 參數(shù),此時 out 的動態(tài)值為空。但它的動態(tài)類型是 *bytes.Buffer。就是說 out 是一個包含空指針的非空接口,所以這里的檢查 out != nil 是 true,防御不了這種情況。
對于某些類型,比如 *os.File,空接收值是合法的。但是對于這里的 *buyes.Buffer,要求接收者不能為空,于是運行時會Panic。
這里的解決方案是,把 main 函數(shù)中的 buf 類型修改為 io.Writer,從而避免在最開始就把一個功能不完整的值賦給一個接口:

var buf io.Writer
if debug {
    buf = new(bytes.Buffer)
}
f(buf)

類型斷言

類型斷言是一個作用在接口值上的操作,代碼類似于x(T),x是一個接口類型的表達(dá)式,而T是一個類型(稱為斷言類型)。類型斷言會檢查操作數(shù)的動態(tài)類型是否滿足指定的斷言類型。
這里有兩種可能:

  • 斷言類型T是一個具體類型
  • 斷言類型T是一個接口類型

具體類型
如果斷言類型T是一個具體類型,斷言類型會檢查x的動態(tài)類型是否就是T。如果檢查成功,返回x的動態(tài)值,返回的類型就是T。如果檢查失敗,那么操作崩潰。

接口類型
如果斷言類型T是一個接口類型,斷言類型會檢查x的動態(tài)類型是否滿足T。如果檢查成功,動態(tài)值并沒有提取出來,仍然是一個接口值,接口值的類型和值部分也不會變,只是結(jié)果類型為接口類型T。就是說,這里類型斷言就是一個接口值表達(dá)式,從一個接口類型變?yōu)閾碛辛硗庖惶追椒ǖ慕涌陬愋?,但保留了接口值中動態(tài)類型和動態(tài)值部分。如果檢查失敗還是會崩潰。

類型斷言可以返回兩個結(jié)果,此時操作不會因為檢查失敗而崩潰。多出來的返回值是一個布爾型,用來指示斷言是否成功。按照慣例,一般變量名用ok。如果操作失敗,ok為false,而第一個返回值會是斷言類型的零值。

類型分支

接口有兩種不同的風(fēng)格。
第一種風(fēng)格下,典型的比如:io.Reader、io.Writer、fmt.Stringer、sort.Interface、http.Handler 和 error。接口上的各種方法突出了滿足這個接口的具體類型之間的相似性,但隱藏了各個具體類型的布局和各自特有的功能。這種風(fēng)格強調(diào)了方法,而不是具體類型。
第二種風(fēng)格則充分利用了接口值能夠容納各種具體類型的能力,它把接口作為這些類型的聯(lián)合(union)來使用。類型斷言用來在運行時區(qū)分這些類型并分別處理。這這種風(fēng)格中,強調(diào)的是滿足這個接口的具體類型,而不是這個接口的方法(經(jīng)常是沒變方法的空接口),也不注重信息隱藏。這種風(fēng)格的接口使用方式稱為可識別聯(lián)合(discriminated union)。
如果對面向?qū)ο笫煜?,這兩種風(fēng)格分別對應(yīng):

  • 子類型多態(tài)(subtype polymorphism)
  • 特設(shè)多態(tài)(ad hoc polymorphism)

使用接口的一些建議

不要一開始就定義接口,每個接口卻只是一個單獨的實現(xiàn)。這種接口是不必要的抽象,還會有運行時的成本。僅在有兩個或多個具體類型需要按統(tǒng)一的方式處理時才需要接口。
上面的建議也有特例,如果接口和類型實現(xiàn)出于依賴的原因不能放在同一個包里邊,那么一個接口只有一個具體類型實現(xiàn)也是可以的。在這種情況下,接口是一種解耦兩個包的好方式。


名稱欄目:gopl方法和接口
網(wǎng)站地址:http://www.dlmjj.cn/article/jghcsc.html