這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
類型方法
1. 給類型定義方法
在Go語言中,我們可以給任何類型(包括內建類型,但不包括指標和介面)定義方法。例如,在實際編程中,我們經常使用[ ]byte的切片,我們可以定義一個新的類型:
type ByteSlice []byte
然後我們就可以定義方法了。例如,假如我們不想使用內建的append函數,我們可以實現一個自己的append方法:
func Append(slice, data[]byte) []byte {
l := len(slice)
if l + len(data) > cap(slice) { // reallocate
// Allocate double what's needed, for future growth.
newSlice := make([]byte, (l+len(data))*2)
// The copy function is predeclared and works for any slice type.
copy(newSlice, slice)
slice = newSlice
}
slice = slice[0:l+len(data)]
for i, c := range data {
slice[l+i] = c
}
return slice
}
我們可以在Append實現自己的記憶體擴充策略。這個新的類型與[ ]byte沒有其它的區別,只是它多了一個Append方法:
var a ByteSlice = []byte{1,2,3}
b := []byte{4}
a.Append(b) //won't change a
fmt.Println(a)
a = a.Append(b)
fmt.Println(a);
輸出:
[1 2 3]
[1 2 3 4]
注意,上面的Append方法只能通過ByteSlice調用,而不能通過[ ]byte的方式調用。另外,為了得到更新後的值,必須將更新後的值做為傳回值返回,這種做法比較笨拙,我們可以換一種更優美的方式實現Append方法:
func (p *ByteSlice) Append(data[]byte) {
slice := *p
l := len(slice)
if l + len(data) > cap(slice) { // reallocate
// Allocate double what's needed, for future growth.
newSlice := make([]byte, (l+len(data))*2)
// The copy function is predeclared and works for any slice type.
copy(newSlice, slice)
slice = newSlice
}
slice = slice[0:l+len(data)]
for i, c := range data {
slice[l+i] = c
}
*p = slice
}
通過使用指標的方式,可以達到修改對象本身的目的:
var a ByteSlice = []byte{1,2,3}
var c ByteSlice = []byte{1,2,3}
b := []byte{4}
(&a).Append(b)
c.Append(b)
fmt.Println(a)
fmt.Println(c)
輸出:
[1 2 3 4]
[1 2 3 4]
實際上,我們可以更進一步,我們可以將函數修改成標準Write方法的樣子:
func (p *ByteSlice) Write(data []byte) (n int, err error) {
slice := *p
l := len(slice)
if l + len(data) > cap(slice) { // reallocate
// Allocate double what's needed, for future growth.
newSlice := make([]byte, (l+len(data))*2)
// The copy function is predeclared and works for any slice type.
copy(newSlice, slice)
slice = newSlice
}
slice = slice[0:l+len(data)]
for i, c := range data {
slice[l+i] = c
}
*p = slice
return len(data), nil
}
這樣類型*ByteSlice就會滿足標準介面io.Writer:
package io
type Writer interface {
Write(p []byte) (n int, err error)
}
這樣我們就可以列印到該類型的變數中:
var b ByteSlice
fmt.Fprintf(&b, "aa%dbb", 7)
fmt.Println(b)
輸出:
[97 97 55 98 98]
注意,這裡必須傳遞&b給fmt.Fprintf,如果傳遞b,則編譯時間會報下面的錯誤:
cannot use b (type ByteSlice) as type io.Writer in argument to fmt.Fprintf:
ByteSlice does not implement io.Writer (Write method has pointer receiver)
Go語言規範有這樣的規定:
The method set of any other named type T consists of all methods with receiver type T. The method set of the corresponding pointer type *T is the set of all methods with receiver *T or T (that is, it also contains the method set of T).
參見這裡。通俗點來說,就是指標類型(*T)的對象包含的接收者為T的方法,反之,則不包含。<effective go>中有這樣的描述:
We pass the address of a ByteSlice because only *ByteSlice satisfies io.Writer. The rule about pointers vs. values for receivers is that value methods can be invoked on pointers and values, but pointer methods can only be invoked on pointers.
我們這裡只定義了(p *ByteSlice) Write方法,而ByteSlice並沒有實現介面io.Write,所以就會報上面的錯誤。注意,這裡的描述有一個上下文,就是給介面賦值。
那為什麼在Append的樣本中,(&a).Append(b)和c.Append(b)都是OK的呢?因為這裡與介面無關。我們不能再以C++的思維來理解Go,因為Go中的對象沒有this指標。更直白的說,對象本身是作為參數顯式傳遞的。所以,即使c.Append(b),Go也會傳遞&c給Append方法。
不管怎麼樣,我覺得這裡還是很讓人迷糊的。
2. 值方法與指標方法
上一節中,我們看到了值方法(value method,receiver為value)與指標方法(pointer method,receiver與pointer)的區別,
func (s *MyStruct) pointerMethod() { } // method on pointer
func (s MyStruct) valueMethod() { } // method on value
那麼什麼時候用值方法,什麼時候用指標方法呢?主要考慮以下一些因素:
(1)如果方法需要修改receiver,那麼必須使用指標方法;
(2)如果receiver是一個很大的結構體,考慮到效率,應該使用指標方法;
(3)一致性。如果一些方法必須是指標receiver,那麼其它方法也應該使用指標receiver;
(4)對於一些基本類型、切片、或者小的結構體,使用value receiver效率會更高一些。
詳細參考這裡。
3. 樣本
這種給原生資料類型增加方法的做法,在Go語言編程中很常見,來看一下http.Header:
// A Header represents the key-value pairs in an HTTP header.
type Header map[string][]string
// Add adds the key, value pair to the header.
// It appends to any existing values associated with key.
func (h Header) Add(key, value string) {
textproto.MIMEHeader(h).Add(key, value)
}
…
作者:YY哥
出處:http://www.cnblogs.com/hustcat/
本文著作權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文串連,否則保留追究法律責任的權利。