惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

A
About on SuperTechFans
C
Cybersecurity and Infrastructure Security Agency CISA
N
News and Events Feed by Topic
C
Cisco Blogs
Cisco Talos Blog
Cisco Talos Blog
A
Arctic Wolf
Scott Helme
Scott Helme
P
Palo Alto Networks Blog
S
Schneier on Security
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
Tor Project blog
量子位
G
Google Developers Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
B
Blog RSS Feed
NISL@THU
NISL@THU
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
AWS News Blog
AWS News Blog
爱范儿
爱范儿
Last Week in AI
Last Week in AI
Y
Y Combinator Blog
L
LINUX DO - 最新话题
Security Archives - TechRepublic
Security Archives - TechRepublic
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
S
Secure Thoughts
Cloudbric
Cloudbric
aimingoo的专栏
aimingoo的专栏
L
Lohrmann on Cybersecurity
TaoSecurity Blog
TaoSecurity Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Hacker News: Ask HN
Hacker News: Ask HN
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The GitHub Blog
The GitHub Blog
有赞技术团队
有赞技术团队
S
Security @ Cisco Blogs
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Cyber Attacks, Cyber Crime and Cyber Security
G
GRAHAM CLULEY
P
Proofpoint News Feed
V
V2EX
Martin Fowler
Martin Fowler
C
CERT Recently Published Vulnerability Notes
Attack and Defense Labs
Attack and Defense Labs
C
CXSECURITY Database RSS Feed - CXSecurity.com
The Cloudflare Blog
SecWiki News
SecWiki News
罗磊的独立博客
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
小众软件
小众软件
The Last Watchdog
The Last Watchdog

博客园 - YY哥

在github上写博客 从veth看虚拟网络设备的qdisc 深入学习golang(5)—接口 深入学习golang(4)—new与make 深入学习golang(2)—channel 深入学习golang(1)—数组与切片 Docker实践(6)—CentOS7上部署Kubernetes CoreOS实践(2)—在coreos上安装Kubernetes Docker实践(5)—资源隔离 CoreOS实践(1)—CoreOS初体验 Docker实践(4)—network namespace与veth pair Ceph monitor故障恢复探讨 环回接口(loopback interface)的新认识 Docker实践(3)—浅析device mapper的thin provision Docker实践(2)—虚拟网络 Docker实践(1)—入门 gpg的一些常用操作 Open vSwitch实践——VLAN CentOS6.5下安装Open vSwitch
深入学习golang(3)—类型方法
YY哥 · 2014-10-03 · via 博客园 - YY哥

类型方法

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);

输出:

注意,上面的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)

输出:

实际上,我们可以更进一步,我们可以将函数修改成标准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)

输出:

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