方法 是與物件例項繫結的特殊函式。
方法 是物件導向程式設計的基本概念,用於維護和展示物件的自身狀態。物件是內斂的,每個例項都有各自不同的獨立特徵,以 屬性 和 方法 來暴露對外通訊介面。普通函式則專注於演算法流程,通過接收引數來完成特定邏輯運算,並返回最終結果。換句話說,方法是有關聯狀態的,而函式通常沒有。
方法 和 函式 定義語法區別的在於前者有前置例項接收引數(receiver),編譯器以此確定方法所屬型別。在某些語言裡,儘管沒有顯示定義,但會在呼叫時隱式傳遞 this 例項引數。
可以為 當前包,以及除 介面 和 指標 以外的任何型別定義方法。
type n int輸出:func (n n) tostring() string
func main()
0x19方法同樣不支援過載(overload)。receiver 引數名沒有限制,按慣例會選用簡短有意義的名稱(不推薦使用 this、self)。如果 方法內部並不引用例項,可省略引數名,僅保留型別。
type n int方法 可看作特殊的函式,那麼 receiver 的型別自然可以是 基礎型別 或 指標型別。這會關係到呼叫時物件例項是否被複製。func (n) test()
type n int輸出:func (n n) value()
func (n *n) pointer()
func main()
v: 0xc42000a290, 26 // receiver 被複製p: 0xc42000a268, 26
a: 0xc42000a268, 26
可使用 例項值 或 指標 呼叫方法,編譯器會根據方法 receiver 型別自動在 基礎型別 和 指標型別 間轉換。
func main()輸出:
v: 0xc42000a290, 26不能用多級指標呼叫方法。p: 0xc42000a268, 26
v: 0xc42000a2c0, 27
p: 0xc42000a268, 27
func main()指標型別的 receiver 必須是合法指標(包括 nil),或能獲取例項位址。
type x struct {}func (x *x) test()
func main() .test() // 錯誤:cannot take the address of x literal
}
將方法看作普通函式,就很容易理解 receiver 的傳參方式。如何選擇方法的 receiver 型別?
可以像訪問匿名字段成員那樣呼叫方法,由編譯器負責查詢。
type data struct方法也會有同名遮蔽問題。但利用這種特性,可實現類似覆蓋(override)操作。func main()
d.lock() // 編譯器會處理為 sync.(*mutex).lock() 呼叫
defer d.unlock()
}
type user struct {}輸出:type manager struct
func (user) tostring() string
func (m manager) tostring() string
func main()
user; manager儘管能直接訪問匿名欄位的 成員 和 方法,但它們依然不屬於繼承關係。user
型別有乙個與之相關的方法集(method set),這決定了它是否實現某個介面。
可利用反射(reflect)測試這些規則。
type s struct {}type t struct
func (s) sval() {}
func (*s) sptr() {}
func (t) tval() {}
func (*t) tptr() {}
// 顯示方法集裡所有方法名字
func methodset(a inte***ce{})
}func main()
sval func(main.t)輸出結果符合預期,但我們也注意到某些方法的 receiver 型別發生了改變。真實情況是,這些都是由編譯器按方法集所需自動生成的額外包裝方法。tval func(main.t)
----------
sptr func(*main.t)
sval func(*main.t)
tptr func(*main.t)
tval func(*main.t)
$ nm test | grep "main\."方法集 僅影響 介面實現 和 方法表示式轉換,與通過 例項 或 例項指標 呼叫方法無關。例項並不使用方法集,而是直接呼叫(或通過隱式欄位名)。...
很顯然,匿名字段就是為方法集準備的。否則,完全沒必要為少寫個欄位名而大費周章。
物件導向的三大特徵「封裝」、「繼承」和「多型」,go 僅實現了部分特徵,它更傾向於「組合優先於繼承」這種思想。將模組分解成相互獨立的更小單元,分別處理不同方面的需求,最後以匿名嵌入方式組合到一起,共同實現對外介面。而且其簡短一致的呼叫方式,更是隱藏了內部實現細節。
組合沒有父子依賴,不會破壞封裝。且整體和布局松耦合,可任意增加來實現擴充套件。各單元持有單一職責,互無關聯,實現和維護更加簡單。方法 和 函式 一樣,除直接呼叫外,還可賦值給變數,或作為引數傳遞。依照具體引用方式的不同,可分為 expression儘管介面也是多型的一種實現形式,但我認為應該和基於繼承體系的多型分離開來。
和 value
兩種狀態。
通過型別引用的 method expression 會被還原為 普通函式樣式,receiver 是第一引數,呼叫時須顯式傳參。至於型別,可以是 t 或 *t,只要目標方法存在於該型別方法集
中即可。
type n int輸出:func (n n) test()
func main()
main.n: 0xc42008c030, 25test.n: 0xc42008c048, 25
test.n: 0xc42008c058, 25
儘管 *n 方法集包裝的 test() 方法 receiver 型別不同,但編譯器會保證按原定義型別拷貝傳值。當然,也可直接以表示式方式呼叫。
基於 例項 或 指標引用 的 method value,引數簽名不會改變,依舊按正常方式呼叫。但當 method value 被賦值給變數或作為引數傳遞時,會立即計算並複製該方法執行所需的 receiver 物件,與其繫結,以便在稍後執行時,能隱式傳入 receiver 引數。
type n int輸出:func (n n) test()
func main()
main.n: 0xc42000a268, 103test.n: 0xc42000a2a0, 101
test.n: 0xc42000a2b0, 102
編譯器會為 method value 生成乙個包裝函式,實現間接呼叫。至於 receiver 複製,和閉包的實現方法基本相同,打包成 funcval,經由 dx 暫存器傳遞。當 method value 作為引數時,會複製含 receiver 在內的整個 method value。
type n int輸出:func (n n) test()
func call(m func())
func main()
main.n: 0xc420072188, 100test.n: 0xc4200721c0, 101
test.n: 0xc4200721d0, 102
當然,如果目標方法的 receiver 是指標型別,那麼被複製的僅是指標(注:指標值,及指標指向的內容沒有變!)。
type n int輸出:func (n *n) test()
func main()
main.n: 0xc420072188, 103只要 receiver 引數型別正確,使用 nil 同樣可以執行。test.n: 0xc420072188, 103
test.n: 0xc420072188, 103
type n intfunc (n) value() {}
func (*n) pointer() {}
func main()
go 方法 摘自go語言學習筆記
如何選擇方法的receiver型別 要修改例項狀態,用 t 無須修改狀態的小物件或固定值,建議用t 大物件建議用 t,以減少複製成本 引用型別 字串 函式等指標包裝物件,直接用t 若包含mutex等同步字段,用 t,避免因複製造成鎖操作無效 其他無法確定的情況,都用 t 方法集 型別t方法集包含所有...
Go學習筆記
使用關鍵字var定義變數,自動初始化為零值。如果提供初始化值,可省略變數型別。在函式內部,可用更簡略的 方式定義變數。空白符號 package main import fmt func test 2,0 函式內部 定義變數陣列data 0 data 1 data 2 並賦值0,1,2,且i 0 i,...
go學習筆記
那些打不倒你的,終將讓你變的更強 package main import fmt func main 輸出結果 num1的型別是 int,數值是 30 num2的型別是 int,數值是 40 name的型別是 string,數值是 zhangshang sum的型別是 int,數值是 30 1 2 ...