类型方法

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

深入学习golang(3)—类型方法的更多相关文章

  1. 学习Golang语言(6):类型--切片

    学习Golang语言(1): Hello World 学习Golang语言(2): 变量 学习Golang语言(3):类型--布尔型和数值类型 学习Golang语言(4):类型--字符串 学习Gola ...

  2. Swift编程语言学习12 ——实例方法(Instance Methods)和类型方法(Type Methods)

    方法是与某些特定类型相关联的函数.类.结构体.枚举都能够定义实例方法:实例方法为给定类型的实例封装了详细的任务与功能.类.结构体.枚举也能够定义类型方法:类型方法与类型本身相关联.类型方法与 Obje ...

  3. Golang - 复合类型

    目录 Golang - 复合类型 1. 指针 2. new()和make() 3. 数组 4. slice 5. Map 6. 结构体 7. 结构体参数 Golang - 复合类型 1. 指针 go语 ...

  4. MVC学习系列4--@helper辅助方法和用户自定义HTML方法

    在HTML Helper,帮助类的帮助下,我们可以动态的创建HTML控件.HTML帮助类是在视图中,用来呈现HTML内容的.HTML帮助类是一个方法,它返回的是string类型的值. HTML帮助类, ...

  5. 学习zepto.js(原型方法)

    学习zepto.js(原型方法)[1] 转载 新的一周,新的开始,今天来学习一下zepto里边的原型方法,就是通过$.进行调用的方法,也是可以通过$.fn进行扩展的方法: $.camelCase(): ...

  6. Python学习之数组类型一:

    Python学习之数组类型一: Numpy中的向量与矩阵: 1.创建:  向量.矩阵均由array函数创建,区别在于向量是v=array( [逗号分隔的元素] ), 矩阵是M=array( [[ ]] ...

  7. pandas学习(常用数学统计方法总结、读取或保存数据、缺省值和异常值处理)

    pandas学习(常用数学统计方法总结.读取或保存数据.缺省值和异常值处理) 目录 常用数学统计方法总结 读取或保存数据 缺省值和异常值处理 常用数学统计方法总结 count 计算非NA值的数量 de ...

  8. python3.4学习笔记(二) 类型判断,异常处理,终止程序

    python3.4学习笔记(二) 类型判断,异常处理,终止程序,实例代码: #idle中按F5可以运行代码 #引入外部模块 import xxx #random模块,randint(开始数,结束数) ...

  9. (转)由su和su -的区别谈学习linux运维方法

    由su和su -的区别谈学习linux运维方法 原文:http://blog.51cto.com/oldboy/1053606 由su和su -的区别谈学习linux运维方法一例 老男孩Linux培训 ...

随机推荐

  1. jQuery中.attr()和.prop()的区别

    之前学习jQuery的时候,学习到了两种取得标签的属性值的方法:一种是elemJobj.attr(),另一种是elemJobj.prop().而在学习JS的时候,只有一种方法elemObj.getAt ...

  2. NSLog函数重写

    跟C++的输出函数相比,NSlog函数有个很大的优势,就是它可以输出对象. 在实际使用过程中,我们可以通过实现description函数来实现对NSLog函数的重写 -(NSString*)descr ...

  3. 最短路算法 (bellman-Ford算法)

    贝尔曼-福特算法与迪科斯彻算法类似,都以松弛操作为基础,即估计的最短路径值渐渐地被更加准确的值替代,直至得到最优解.在两个算法中,计算时每个边之间的估计距离值都比真实值大,并且被新找到路径的最小长度替 ...

  4. Android 开发必备

    Android 开发必备 http://www.androiddevtools.cn/ 收集整理Android开发所需的Android SDK.开发中用到的工具.Android开发教程.Android ...

  5. 『TCP/IP详解——卷一:协议』读书笔记——02

    2013-08-16 20:07:11 1.3 TCP/IP的分层 这是一个很好的图,要多看!!图上有些细节会在今后的笔记中提到,看不懂不必深究. IP是网络层上的主要协议,同时被TCP和UDP(这两 ...

  6. 解决VS2013+IE11调试DevExpress ASP.NET MVC的性能问题

    将一个MVC项目从12.2升级到14.2,VS2012升到2013,发现使用IE11调试非常慢卡死,CPU占用100%,后来经过排除,发现只有DevExpress的MVC项目有这个问题. 最后在Dev ...

  7. Web Api 中返回JSON的正确做法

    在使用Web Api的时候,有时候只想返回JSON:实现这一功能有多种方法,本文提供两种方式,一种传统的,一种作者认为是正确的方法. JSON in Web API – the formatter b ...

  8. 利用Docker Hub上的Nginx部署Web应用

    Docker Hub上提供了很多镜像,如Nginx,我们不需要自己从ubuntu开始装Nginx再做发布,只需要先下载镜像到本地 docker pull nginx 在/opt下新建文件夹API,将需 ...

  9. sql bcp 笔记

    介绍: http://blog.csdn.net/soudog/article/details/4343415 导出格式 BCP NTS.dbo.T_User format nul -f c:/Use ...

  10. 老生常谈JavaScript闭包

    闭包就是指一个有权访问另外一个函数作用域中的变量的函数.--<JavaScript高级程序第三版> 本人对于闭包初次的认识就来自<高三>,首先仅仅通过“有权”’两个字我们便可以 ...