Go 語(yǔ)言 方法聲明

2023-03-14 16:54 更新

原文鏈接:https://gopl-zh.github.io/ch6/ch6-01.html


6.1. 方法聲明

在函數(shù)聲明時(shí),在其名字之前放上一個(gè)變量,即是一個(gè)方法。這個(gè)附加的參數(shù)會(huì)將該函數(shù)附加到這種類型上,即相當(dāng)于為這種類型定義了一個(gè)獨(dú)占的方法。

下面來(lái)寫我們第一個(gè)方法的例子,這個(gè)例子在package geometry下:

gopl.io/ch6/geometry

package geometry

import "math"

type Point struct{ X, Y float64 }

// traditional function
func Distance(p, q Point) float64 {
    return math.Hypot(q.X-p.X, q.Y-p.Y)
}

// same thing, but as a method of the Point type
func (p Point) Distance(q Point) float64 {
    return math.Hypot(q.X-p.X, q.Y-p.Y)
}

上面的代碼里那個(gè)附加的參數(shù)p,叫做方法的接收器(receiver),早期的面向?qū)ο笳Z(yǔ)言留下的遺產(chǎn)將調(diào)用一個(gè)方法稱為“向一個(gè)對(duì)象發(fā)送消息”。

在Go語(yǔ)言中,我們并不會(huì)像其它語(yǔ)言那樣用this或者self作為接收器;我們可以任意的選擇接收器的名字。由于接收器的名字經(jīng)常會(huì)被使用到,所以保持其在方法間傳遞時(shí)的一致性和簡(jiǎn)短性是不錯(cuò)的主意。這里的建議是可以使用其類型的第一個(gè)字母,比如這里使用了Point的首字母p。

在方法調(diào)用過(guò)程中,接收器參數(shù)一般會(huì)在方法名之前出現(xiàn)。這和方法聲明是一樣的,都是接收器參數(shù)在方法名字之前。下面是例子:

p := Point{1, 2}
q := Point{4, 6}
fmt.Println(Distance(p, q)) // "5", function call
fmt.Println(p.Distance(q))  // "5", method call

可以看到,上面的兩個(gè)函數(shù)調(diào)用都是Distance,但是卻沒(méi)有發(fā)生沖突。第一個(gè)Distance的調(diào)用實(shí)際上用的是包級(jí)別的函數(shù)geometry.Distance,而第二個(gè)則是使用剛剛聲明的Point,調(diào)用的是Point類下聲明的Point.Distance方法。

這種p.Distance的表達(dá)式叫做選擇器,因?yàn)樗麜?huì)選擇合適的對(duì)應(yīng)p這個(gè)對(duì)象的Distance方法來(lái)執(zhí)行。選擇器也會(huì)被用來(lái)選擇一個(gè)struct類型的字段,比如p.X。由于方法和字段都是在同一命名空間,所以如果我們?cè)谶@里聲明一個(gè)X方法的話,編譯器會(huì)報(bào)錯(cuò),因?yàn)樵谡{(diào)用p.X時(shí)會(huì)有歧義(譯注:這里確實(shí)挺奇怪的)。

因?yàn)槊糠N類型都有其方法的命名空間,我們?cè)谟肈istance這個(gè)名字的時(shí)候,不同的Distance調(diào)用指向了不同類型里的Distance方法。讓我們來(lái)定義一個(gè)Path類型,這個(gè)Path代表一個(gè)線段的集合,并且也給這個(gè)Path定義一個(gè)叫Distance的方法。

// A Path is a journey connecting the points with straight lines.
type Path []Point
// Distance returns the distance traveled along the path.
func (path Path) Distance() float64 {
    sum := 0.0
    for i := range path {
        if i > 0 {
            sum += path[i-1].Distance(path[i])
        }
    }
    return sum
}

Path是一個(gè)命名的slice類型,而不是Point那樣的struct類型,然而我們依然可以為它定義方法。在能夠給任意類型定義方法這一點(diǎn)上,Go和很多其它的面向?qū)ο蟮恼Z(yǔ)言不太一樣。因此在Go語(yǔ)言里,我們?yōu)橐恍┖?jiǎn)單的數(shù)值、字符串、slice、map來(lái)定義一些附加行為很方便。我們可以給同一個(gè)包內(nèi)的任意命名類型定義方法,只要這個(gè)命名類型的底層類型(譯注:這個(gè)例子里,底層類型是指[]Point這個(gè)slice,Path就是命名類型)不是指針或者interface。

兩個(gè)Distance方法有不同的類型。他們兩個(gè)方法之間沒(méi)有任何關(guān)系,盡管Path的Distance方法會(huì)在內(nèi)部調(diào)用Point.Distance方法來(lái)計(jì)算每個(gè)連接鄰接點(diǎn)的線段的長(zhǎng)度。

讓我們來(lái)調(diào)用一個(gè)新方法,計(jì)算三角形的周長(zhǎng):

perim := Path{
    {1, 1},
    {5, 1},
    {5, 4},
    {1, 1},
}
fmt.Println(perim.Distance()) // "12"

在上面兩個(gè)對(duì)Distance名字的方法的調(diào)用中,編譯器會(huì)根據(jù)方法的名字以及接收器來(lái)決定具體調(diào)用的是哪一個(gè)函數(shù)。第一個(gè)例子中path[i-1]數(shù)組中的類型是Point,因此Point.Distance這個(gè)方法被調(diào)用;在第二個(gè)例子中perim的類型是Path,因此Distance調(diào)用的是Path.Distance。

對(duì)于一個(gè)給定的類型,其內(nèi)部的方法都必須有唯一的方法名,但是不同的類型卻可以有同樣的方法名,比如我們這里Point和Path就都有Distance這個(gè)名字的方法;所以我們沒(méi)有必要非在方法名之前加類型名來(lái)消除歧義,比如PathDistance。這里我們已經(jīng)看到了方法比之函數(shù)的一些好處:方法名可以簡(jiǎn)短。當(dāng)我們?cè)诎庹{(diào)用的時(shí)候這種好處就會(huì)被放大,因?yàn)槲覀兛梢允褂眠@個(gè)短名字,而可以省略掉包的名字,下面是例子:

import "gopl.io/ch6/geometry"

perim := geometry.Path{{1, 1}, {5, 1}, {5, 4}, {1, 1}}
fmt.Println(geometry.PathDistance(perim)) // "12", standalone function
fmt.Println(perim.Distance())             // "12", method of geometry.Path

譯注: 如果我們要用方法去計(jì)算perim的distance,還需要去寫全geometry的包名,和其函數(shù)名,但是因?yàn)镻ath這個(gè)類型定義了一個(gè)可以直接用的Distance方法,所以我們可以直接寫perim.Distance()。相當(dāng)于可以少打很多字,作者應(yīng)該是這個(gè)意思。因?yàn)樵贕o里包外調(diào)用函數(shù)需要帶上包名,還是挺麻煩的。



以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)