该文内容来看读《Go并发编程实战》有感,仅供娱乐分享 :)



在%GOROOT%\src\sort包下有一个sort.go文件,里面第12行有这么一个接口定义:

type Interface interface {

// Len is the number of elements in the collection.

Len() int

// Less reports whether the element with

// index i should sort before the element with index j.

Less(i, j int) bool

// Swap swaps the elements with indexes i and j.

Swap(i, j int)

}

该接口定义了3个方法:Len()、Less()、Swap(),分别用以求长度、比大小和交换元素使用。


下面自定义一个数据类型,来实现这个接口:

import (

"fmt"

"sort"

)


type SortableStrings [3]string    // 自定义一个数据类型SortableString


func (s SortableStrings) Len() int {   // 非***式地实现三个接口的Len()方法

return len(s)

}


func (s SortableStrings) Less(i, j int) bool {   // 非***式地实现三个接口的Less()方法

return s[i] < s[j]

}


func (s SortableStrings) Swap(i, j int) {   // 非***式地实现三个接口的Swap()方法

s[i], s[j] = s[j], s[i]

}


func main() {

        // 断言SortableStrings类型是否已经是sort.Interface接口的一个实现?

_, ok := interface{}(SortableStrings{}).(sort.Interface)  

fmt.Println(ok)  // 这里打印是的,因为的确非***式地实现了sort.Interface接口中的所有方法

}



再定义一个接口呗,反正也不花钱

type Sortable interface {

        sort.Interface

        Sort()

}

书中举这个例子,就是想说明可以把一个接口类型(sort.Interface)嵌入到另一个接口类型(Sortable)中。



一个自定义数据类型,是否可以实现多个接口?

是的,可以,所以书中的SortableStrings又实现了Sort()方法:

func (s SortableStrings) Sort() {

        sort.Sort(s)  // 使用go标准库中的Sort()方法进行排序

}


func main() {

       // 断言SortableStrings类型是否已经是Sortable接口的一个实现?

       _, ok := interface{}(SortableStrings{}).(sort.Interface)

      fmt.Println(ok)


      _, ok2 := interface{}(SortableStrings{}).(Sortable)

      fmt.Println(ok2) // 这里打印是的true

}

这个示例就是想进一步说明一个自定义类型可以同时实现多个接口。



书中又说,既然你都实现了,那么运行一下呗,反正你也不是真懂 :)

func main() {

_, ok := interface{}(SortableStrings{}).(sort.Interface)

fmt.Println(ok)


_, ok2 := interface{}(SortableStrings{}).(Sortable)

fmt.Println(ok2)


ss := SortableStrings{"2", "3", "1"}

ss.Sort()

fmt.Printf("Sortable strings: %v\n", ss)

}

执行一打印,发现并没有排序成功:

Sortable strings: [2 3 1]

这是怎么回事呢?


书中原话,现摘录在下面:

我们在上一小节说过,在值方法中,对接收者的值的改变在该方法之外是不可见的。在上面的示例中,SortableStrings类型的Sort()方法实际上是通过函数sort.Sort()来对接收者的值进行排序的。sort.Sort()函数接受一个类型为sort.Interface的参数值,并利用这个值的方法Len、Less和Swap来修改其参数中的各个元素的位置以完成排序工作。再来看SortableStrings类型,虽然它实现了接口类型sort.Interface中声明的全部方法,但是这些方法都是值方法,这使得在这些方法中对接收者值的改变并不会影响到它的源值。因为,它们只是改变了源值的某个复制品。这就是Sort方法失效的真正原因。当我们把SortableStrings类型的方法Len、Less和Swap的接收者类型都改为*SortableStrings之后,这个问题就会得到解决。但是,这时的SortableStrings类型就已经不再是接口类型sort.Interface的实现了。


叽里咕噜地说了这么多,就是说自定义类型在实现接口sort.Interface时的方法接收者都是值方法,非指针方法。

那么什么是值方法?什么是指针方法?



好像我又挖了一个坑,再解释一下吧 :)

type SortableStrings [3]string 


func (s SortableStrings) Sort() {   // 接收者s的类型为值类型,所以这样的方法称之为值方法

       //......略

}


func (s *SortableStrings) Sort() {   // 接收者s的类型为指针类型,所以这样的方法称之为指针方法

       //......略

}


把上面的自定义类型SortableStrings的接口实现方法都修改为指针方法:

package main


import (

"fmt"

"sort"

)


type Sortable interface {

sort.Interface

Sort()

}


type SortableStrings [3]string


func (s *SortableStrings) Len() int {

return len(s)

}


func (s *SortableStrings) Less(i, j int) bool {

return s[i] < s[j]

}


func (s *SortableStrings) Swap(i, j int) {

s[i], s[j] = s[j], s[i]

}


func (s *SortableStrings) Sort() {

sort.Sort(s)

}


func main() {

_, ok := interface{}(SortableStrings{}).(sort.Interface)

fmt.Println(ok)


_, ok2 := interface{}(SortableStrings{}).(Sortable)

fmt.Println(ok2)


ss := SortableStrings{"2", "3", "1"}

ss.Sort()

fmt.Printf("Sortable strings: %v\n", ss)

}

再执行一下,看看结果变了没有?

Sortable strings: [1 2 3]

真的变了,说明排序已成功,但上面的两个断言都变成了false



那什么地方用值方法?什么地方用指针方法?

再摘抄书上的原话:

实际上,这也是一个在值方法和指针方法之间做选择的问题。这里有两条很重要的规则。

  • 在某个自定义数据类型的值上,只能够用与这个数据类型相关联的值方法,而在指向这个值的指针值上,却能够调用与其数据类型关联的值方法和指针方法。从另一个角度讲,自定义数据类型的方法集合中仅包含了与它关联的所有值方法,而与它相对应的指针类型的方法集合中却包含了与它关联的所有值方法和所有指针方法。

  • 在指针方法中一定能够改变接收者的值,而在值方法中,对接收者的值的改变对于该方法之外一般是无效的。这是因为,以接收者标识符代表的接收者的值实际上也是当前方法所属的数据类型的当前值的一个复制品。对于值方法来说,由于这个接收者的值就是一个当前值的复制品,所以对它的改变并不会影响到当前值。而对于这个指针方法来说,这个接收者的值则是一个当前值的指针的×××。因此,依据这个指针来对当前值做变更,就等于直接对该值进行了改变。


叽里咕噜地说了这么多,这是什么意思呢?

我曾经在另外的博客中说过,高手就是能够把别人绕晕,把自己绕晕,然后再绕出来的人 :)


先说第2条,用例子说吧:

package main


import (

"fmt"

)


type MyString struct {  // 自定义数据类型

name string

}


func (m MyString) modify() {  //  通过值方法修改接受者的值

m.name = "omgs"

}


func (m *MyString) update() { // 通过指针方法修改接受者的值

m.name = "pwm"

}

func main() {

var s = MyString{"ccq"}  

fmt.Println(s.name)  // 初始化name的值为ccq 


s.modify()  // 调用值方法修改name为omgs

fmt.Println(s.name)   // 打印发现值依旧是ccq,说明modify()并没有修改成功


s.update()  // 调用指针方法修改name为pwm

fmt.Println(s.name)  // 打印发现值为pwm,说明update()成功修改了name值


再说第1条,坦率地讲我是属于被绕进去一直出不来的那种,呵呵呵,就上面例子来说,对照着作者原话去扣:

在某个自定义数据类型的值上:即s

只能够调用与这个数据类型相关联的值方法:与自定义数据类型相关联的值方法是modify(),按这个意思,s只能够调用modify()方法。可惜的很,s也能调用update()方法,同时执行成功。

在指向这个值的指针值上,却能够调用与其数据类型关联的值方法和指针方法:即(&s)可以调用modify(),也能调用update(),经测试验证(&s).modify()和(&s).update()都能调用。这个没毛病!


按照内事不知问度娘的逻辑,百度了一下,结果发现很多都是这样写的:

在某个自定义数据类型的值上,只能够调用与这个数据类型相关联的值方法,而在指向这个值的指针值上,却能够调用与其数据类型关联的值方法和指针方法。虽然自定义数据类型的方法集合中不包含与它关联的指针类型,但是我们仍能够通过这个类型的值调用它的指针方法,这里需要使用取地址符&。

前半句一样,后面的解释不同,但解释不通的地方在于最后是否必须用&符的地方。



其实这个地方不用纠结,这里只需要理解自定义数据类型的方法集合中是否包含指针方法即可,这点在前面已经验证过了:

package main


import "fmt"


type Sortable interface {

Sort()

}


type SortableStrings [3]string // 自定义一个数据类型SortableString


func (s SortableStrings) Sort() {


}


func main() {

_, ok2 := interface{}(SortableStrings{}).(Sortable)

fmt.Println(ok2) // 这里打印是的true

}

这里打印true说明值方法是SortableString关联方法集合中的方法,但把值方法Sort()换成指针方法,就会打印false,所以书中作者认为指针方法不是SortableString关联方法集合中的方法。