go基礎5-方法
方法
go中同時有函式和方法.方法是包含了接收者( receiver)的函式,接收者可以是命名型別或結構體型別的值或指標.所有給定型別的方法屬於該型別的方法集.go中的方法是一種有繫結行為的特殊函式,繫結行為用於標識函式所屬.
方法宣告
方法:在函式名稱前增加變數的宣告方式.這個附加的引數會將該函式附加到這種型別上,即為變數型別定義一個獨佔的方法.方法上的變數必須屬於一種型別,不能使用原始型別.
type Point struct{ X, Y float64 } // 函式 func Distance(p, q Point) float64 { return math.Hypot(q.X-p.X, q.Y-p.Y) } // 方法 表示Distance是Point的一個方法 func (p Point) Distance(q Point) float64 { return math.Hypot(q.X-p.X, q.Y-p.Y) } // 宣告型別 type I int func (i I) Sum(q I) int { return int(i) + int(q) }
上面變數p稱為方法的接收器;接收器命名建議使用變數的首字母,簡短一致;
方法呼叫方式:使用所屬方式
p := Point{1, 2}
q := Point{4, 6}
fmt.Println(Distance(p, q)) // "5", 函式呼叫
fmt.Println(p.Distance(q)) // "5", 方法呼叫
這種所屬方式使用形式叫做選擇器.它會根據變數後的名稱選擇相應的方法或欄位來使用.
對於給定的型別,其內部方法的命名必須唯一,不同型別可以使用相同方法名;
基於指標物件的方法
函式呼叫使用指標可以避免參數拷貝,資料修改更加直接;&取地址;*取表示式值
func (p *Point) ScaleBy(factor float64) {
p.X *= factor
p.Y *= factor
}
一般會約定如果Point類有一個指標作為接收器的方法,那麼所有Point的方法都必須有一個指標接收器,及時哪些不需要指標接收器的函式.????
方法宣告中,不允許型別名本身就是指標的接收器.
type P *int
func (P) f() { /* ... */ } // compile error: invalid receiver type
呼叫指標型別方法(*Point).ScaleBy,只需要提供一個Point型別指標即可:
// 方法一 r := &Point{1, 2} r.ScaleBy(2) fmt.Println(*r) // 方法二 p := Point{1, 2} pptr := &p pptr.ScaleBy(2) fmt.Println(p) // 方法三 q := Point{1, 2} (&q).ScaleBy(2) fmt.Println(q) //方法四 p.ScaleBy(2)
方法四中go會隱式用&p去呼叫方法,但它只適用於變數.
不管接收器是指標型別還是非指標型別,都是可以通過指標/非指標型別呼叫,編譯器會幫你做型別轉換;
指標非指標型別的選擇要看物件本身是否比較大,是否需要原值修改;
宣告為非指標變數時,呼叫會產生一次拷貝;
指標型別接收器會指向同一記憶體地址,操作同一個資料;
嵌入結構體來擴充套件型別
type ColoredPoint struct {
Point
Color color.RGBA
}
通過組合方式來擴充套件資料型別,呼叫時也跟定義在自身上一致.如:cp.X=cp.Point.X.但是嵌入結構體只是型別組合不是繼承關係.
可以定義匿名欄位,用於直接使用型別的方法.
方法值和方法表示式
方法呼叫可以拆分為兩個步驟:方法繫結和呼叫
p := Point{1, 2}
q := Point{4, 6}
p.Distance(q)
<===>
distanceFromP := p.Distance // method value
distanceFromP(q) // "5"
p.Distance會返回將方法和特定接收器變數繫結的函式,後續使用傳入函式引數即可,不用在指定接收器.
方法表示式中型別T呼叫可以省略接收器變數:
distance := Point.Distance
fmt.Println(distance(p, q))
類似於靜態變數.
bit陣列
bit陣列適用於集合間的並集,交集操作.通常使用無符號數或稱為"字"的slice來表示.
每個元素的每一位都表示集合裡的一個值.每個元素:指位數的分組數.如1byte=8bit,13/8代表有兩個byte,就是有兩個元素;元素裡每一位都會進行值儲存;當集合的第i位被設定時,我們才說這個集合包含元素i.
// 值新增
func (s *IntSet) Add(x int) {
word, bit := x/64, uint(x%64)
for word >= len(s.words) {
s.words = append(s.words, 0)
}
s.words[word] |= 1 << bit
}
// 取值
func (s *IntSet) String() string {
var buf bytes.Buffer
buf.WriteByte('{')
for i, word := range s.words {
if word == 0 {
continue
}
for j := 0; j < 64; j++ {
if word&(1<<uint(j)) != 0 {
if buf.Len() > len("{") {
buf.WriteByte(' ')
}
fmt.Fprintf(&buf, "%d", 64*i+j)
}
}
}
buf.WriteByte('}')
return buf.String()
}
封裝
封裝:呼叫方不可見物件中的變數或方法.go中只有一種可見性控制手段:大小寫識別符號;這種限制同樣適用於struct或型別的方法;
type IntSet struct {
words []uint64
}
type IntSet []uint64
上面兩種寫法的底層實現一致,但是欄位的外部可見性不同.但是struct的內部欄位是包內可見,無論你定義在函式還是方法裡.
封裝的優點:
- 降低外部呼叫方使用難度
- 隱藏實現細節
- 阻止外部呼叫方對物件內部的值任意地修改