Go的物件導向程式設計

2021-10-02 11:13:04 字數 3839 閱讀 4787

func

(p point)

distance

(q point)

float64

p := point

q := point

fmt.

println

(distance

(p, q)

)// "5", function call

fmt.

println

(p.distance

(q))

// "5", method call

**裡那個附加的引數p,叫做方法的接收器(receiver),早期的物件導向語言留下的遺產將呼叫乙個方法稱為「向乙個物件傳送訊息」。功能類似於c/c++的this指標。同乙個結構體的字段和方法都在同乙個字段如果欄位名和方法名一樣會導致衝突,在能夠給任意型別定義方法這一點上,go和很多其它的物件導向的語言不太一樣。因此在go語言裡,我們為一些簡單的數值、字串、slice、map來定義一些附加行為很方便。我們可以給同乙個包內的任意命名型別定義方法,因為每種型別都有其方法的命名空間,我們在用distance這個名字的時候,不同的distance呼叫指向了不同型別裡的distance方法。只要這個命名型別的底層型別(譯註:這個例子裡,底層型別是指point這個slice,path就是命名型別)不是指標或者inte***ce。

當呼叫乙個函式時,會對其每乙個引數值進行拷貝,如果乙個函式需要更新乙個變數,或者函式的其中乙個引數實在太大我們希望能夠避免進行這種預設的拷貝,這種情況下我們就需要用到指標了。對應到我們這裡用來更新接收器的物件的方法,當這個接受者變數本身比較大時,我們就可以用其指標而不是物件來宣告方法,在現實的程式裡,一般會約定如果point這個類有乙個指標作為接收器的方法,那麼所有point的方法都必須有乙個指標接收器,即使是那些並不需要這個指標接收器的函式。nil也是乙個合法的接收值。

補充

1.不管你的method的receiver是指標型別還是非指標型別,都是可以通過指標/非指標型別進行呼叫的,編譯器會幫你做型別轉換。

2.在宣告乙個method的receiver該是指標還是非指標型別時,你需要考慮兩方面的內部,第一方面是這個物件本身是不是特別大,如果宣告為非指標變數時,呼叫會產生一次拷貝;第二方面是如果你用指標型別作為receiver,那麼你一定要注意,這種指標型別指向的始終是一塊記憶體位址,就算你對其進行了拷貝。

如果乙個結構體被內嵌到另乙個結構體那麼他的字段和方法都被外層的結構體獲取,是一種has a的形式實現了繼承,實現的角度來考慮問題,內嵌欄位會指導編譯器去生成額外的包裝方法來委託已經宣告好的方法,和下面的形式是等價的。用這種方式,內嵌可以使我們定義字段特別多的複雜型別,我們可以將字段先按小型別分組,然後定義小型別的方法,之後再把它們組合起來。

func

(p coloredpoint)

distance

(q point)

float64

func

(p *coloredpoint)

scaleby

(factor float64

)

在型別中內嵌的匿名欄位也可能是乙個命名型別的指標,這種情況下字段和方法會被間接地引入到當前的型別中(譯註:訪問需要通過該指標指向的物件去取)。新增這一層間接關係讓我們可以共享通用的結構並動態地改變物件之間的關係。

type coloredpoint struct

p := coloredpoint

, red}

q := coloredpoint

, blue}

fmt.

println

(p.distance

(*q.point)

)// "5"

q.point = p.point // p and q now share the same point

p.scaleby(2

)fmt.

println

(*p.point,

*q.point)

當編譯器解析乙個選擇器到方法時,比如p.scaleby,它會首先去找直接定義在這個型別裡的scaleby方法,然後找被coloredpoint的內嵌字段們引入的方法,然後去找point和rgba的內嵌字段引入的方法,然後一直遞迴向下找。如果選擇器有二義性的話編譯器會報錯,比如你在同一級裡有兩個同名的方法。

方法值:就是當方法作為乙個函式的引數時直接把方法做引數不用把他轉化成匿名函式。方法本身也可以當值使用。

p := point

q := point

distancefromp := p.distance

方法表示式:把方法轉化為函式使用

p := point

q := point

distance := point.distance // method expression

fmt.

println

(distance

(p, q)

)// "5"

fmt.

printf

("%t\n"

, distance)

// "func(point, point) float64"

scale :=

(*point)

.scaleby

scale

(&p,2)

fmt.

println

(p)// ""

fmt.

printf

("%t\n"

, scale)

// "func(*point, float64)"

// 譯註:這個distance實際上是指定了point物件為接收器的乙個方法func (p point) distance(),

// 但通過point.distance得到的函式需要比實際的distance方法多乙個引數,

// 即其需要用第乙個額外引數指定接收器,後面排列distance方法的引數。

// 看起來本書中函式和方法的區別是指有沒有接收器,而不像其他語言那樣是指有沒有返回值。

當你根據乙個變數來決定呼叫同乙個型別的哪個函式時,方法表示式就顯得很有用了。你可以根據選擇來呼叫接收器各不相同的方法。下面的例子,變數op代表point型別的addition或者subtraction方法,path.translateby方法會為其path陣列中的每乙個point來呼叫對應的方法:

type point struct

func

(p point)

add(q point) point

}func

(p point)

sub(q point) point

}type path [

]point

func

(path path)

translateby

(offset point, add bool

)else

for i :=

range path

}

這個例子相當於把方法轉化成函式再用匿名函式把他們組織起來。

go語言只有一種控制可見性的手段:大寫首字母的識別符號會從定義它們的包中被匯出,小寫字母的則不會。這種限制包內成員的方式同樣適用於struct或者乙個型別的方法。因而如果我們想要封裝乙個物件,我們必須將其定義為乙個struct。這種基於名字的手段使得在語言中最小的封裝單元是package,而不是像其它語言一樣的型別。乙個struct型別的字段對同乙個包的所有**都有可見性,無論你的**是寫在乙個函式還是乙個方法裡。

Go 物件導向程式設計

值語義與引用語義的區別在於賦值。值型別不會改變變數值,引用型別會改變變數值。go 語言中大多數型別都是基於值語義,包括 基本型別 如byte int bool float32 float64和string 復合型別 如陣列 array 結構體 struct 和指標 pointer 等。go語言中的陣...

go物件導向程式設計 封裝

將結構體 屬性的字段設定為小寫 給結構體所在的包提供乙個工廠模式,首字母大寫,類似於乙個建構函式 提供乙個首字母大寫的set方法,用於對屬性的判斷並賦值func c student setscore score float64 提供乙個首字母大寫的get方法,用於獲取屬性值func 封裝的實現 ty...

Go語言 物件導向程式設計

go語言的語言設計非常的簡潔,因為,go語言並沒有物件導向的概念,因此go語言中沒有物件導向的一些概念,例如 封裝 繼承 多型,虛函式 建構函式,this指標等 儘管go語言中沒有封裝 繼承 多型,但同樣也可以實現相應的功能。封裝 通過方法進行實現 繼承 通過匿名字段實現 多型 通過介面實現 方法也...