在Go語言中,咱們能夠給任何類型(包括內置類型,但不包括指針和接口)定義方法。例如,在實際編程中,咱們常常使用[ ]byte的切片,咱們能夠定義一個新的類型:編程
type ByteSlice []byteapp
而後咱們就能夠定義方法了。例如,假如咱們不想使用內建的append函數,咱們能夠實現一個本身的append方法:函數
func Append(slice, data[]byte) []byte {this
l := len(slice)url
if l + len(data) > cap(slice) { // reallocatespa
// 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.blog
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方法。
無論怎麼樣,我以爲這裏仍是很讓人迷糊的。
上一節中,咱們看到了值方法(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效率會更高一些。
詳細參考這裏。
這種給原生數據類型增長方法的作法,在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/ 本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,不然保留追究法律責任的權利。