當調用一個函數時,會對其每一個參數值進行拷貝,如果一個函數需要更新一個變量,或者函數的其中一個參數實在太大我們希望能夠避免進行這種默認的拷貝,這種情況下我們就需要用到指針了。對應到我們這里用來更新接收器的對象的方法,當這個接受者變量本身比較大時,我們就可以用其指針而不是對象來聲明方法。
如下:
func (p *Point) ScaleBy(factor float64) {
p.X *= factor
p.Y *= factor
}
總結為兩點
- 不管你的method的receiver是指針類型還是非指針類型,都是可以通過指針/非指針類型進行調用的,編譯器會幫你做類型轉換。
- 在聲明一個method的receiver該是指針還是非指針類型時,你需要考慮兩方面的因素,第一方面是這個對象本身是不是特別大,如果聲明為非指針變量時,調用會產生一次拷貝;第二方面是如果你用指針類型作為receiver,那么你一定要注意,這種指針類型指向的始終是一塊內存地址,就算你對其進行了拷貝。熟悉C或者C++的人這里應該很快能明白。
Pointer receivers
You can declare methods with pointer receivers.
This means the receiver type has the literal syntax *T for some type T. (Also, T cannot itself be a pointer such as *int.)
For example, the Scale method here is defined on *Vertex.
Methods with pointer receivers can modify the value to which the receiver points (as Scale does here). Since methods often need to modify their receiver, pointer receivers are more common than value receivers.
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
v.Scale(10)
fmt.Println(v.Abs())//結果為50。當func (v *Vertex) Scale(f float64)變成func (v Vertex) Scale(f float64),結果為5,因為Scale沒有改變Vertex結構體中的字段值。
}
Try removing the * from the declaration of the Scale function on line 16 and observe how the program's behavior changes.
With a value receiver, the Scale method operates on a copy of the original Vertex value. (This is the same behavior as for any other function argument.) The Scale method must have a pointer receiver to change the Vertex value declared in the main function.
Nil也是一個合法的接收器類型
就像一些函數允許nil指針作為參數一樣,方法理論上也可以用nil指針作為其接收器,尤其當nil對于對象來說是合法的零值時,比如map或者slice。在下面的簡單int鏈表的例子里,nil代表的是空鏈表:
// An IntList is a linked list of integers.
// A nil *IntList represents the empty list.
type IntList struct {
Value int
Tail *IntList
}
// Sum returns the sum of the list elements.
func (list *IntList) Sum() int {
if list == nil {
return 0
}
return list.Value + list.Tail.Sum()
}
當你定義一個允許nil作為接收器值的方法的類型時,在類型前面的注釋中指出nil變量代表的意義是很有必要的,就像我們上面例子里做的這樣。
下面是net/url包里Values類型定義的一部分。
package url
// Values maps a string key to a list of values.
type Values map[string][]string
// Get returns the first value associated with the given key,
// or "" if there are none.
func (v Values) Get(key string) string {
if vs := v[key]; len(vs) > 0 {
return vs[0]
}
return ""
}
// Add adds the value to key.
// It appends to any existing values associated with key.
func (v Values) Add(key, value string) {
v[key] = append(v[key], value)
}
這個定義向外部暴露了一個map的命名類型,并且提供了一些能夠簡單操作這個map的方法。這個map的value字段是一個string的slice,所以這個Values是一個多維map。客戶端使用這個變量的時候可以使用map固有的一些操作(make,切片,m[key]等等),也可以使用這里提供的操作方法,或者兩者并用,都是可以的:
m := url.Values{"lang": {"en"}} // direct construction
m.Add("item", "1")
m.Add("item", "2")
fmt.Println(m.Get("lang")) // "en"
fmt.Println(m.Get("q")) // ""
fmt.Println(m.Get("item")) // "1" (first value)
fmt.Println(m["item"]) // "[1 2]" (direct map access)
m = nil
fmt.Println(m.Get("item")) // ""
m.Add("item", "3") // panic: assignment to entry in nil map
對Get的最后一次調用中,nil接收器的行為即是一個空map的行為。我們可以等價地將這個操作寫成Value(nil).Get(“item”),但是如果你直接寫nil.Get(“item”)的話是無法通過編譯的,因為nil的字面量編譯器無法判斷其準確類型。所以相比之下,最后的那行m.Add的調用就會產生一個panic,因為他嘗試更新一個空map。
由于url.Values是一個map類型,并且間接引用了其key/value對,因此url.Values.Add對這個map里的元素做任何的更新、刪除操作對調用方都是可見的。實際上,就像在普通函數中一樣,雖然可以通過引用來操作內部值,但在方法想要修改引用本身時是不會影響原始值的,比如把他置換為nil,或者讓這個引用指向了其它的對象,調用方都不會受影響。(譯注:因為傳入的是存儲了內存地址的變量,你改變這個變量本身是影響不了原始的變量的,想想C語言,是差不多的)
參考: