【go語言】wait,I don't understand

該文內容來看讀《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關聯方法集合中的方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章