在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/ 本文版权归做者和博客园共有,欢迎转载,但未经做者赞成必须保留此段声明,且在文章页面明显位置给出原文链接,不然保留追究法律责任的权利。