瞭解 Go 1.9 的類型別名

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

目錄 [−]

  1. 類型別名
  2. 類型命名和型別宣告的區別
  3. 類型迴圈
  4. 可匯出性
  5. 方法集
  6. byte 和 rune 類型
  7. 參考資料

如你所知, 類型別名(type aliases) 最終還是加入到Go 1.9中, Go 1.9 beta2今天已經發布了, 正式版預計8月初發布, 是時候深入瞭解一下它的新特性了,本文介紹的就是它的重要的新特性之一: 類型別名。

當然,如果你想嘗試這些新特性,需要安裝Go 1.9的版本,目前是beta2版,可以在官方網站下載。

類型別名主要解決什麼問題,為什麼需要這個特性? Russ Cox 的論文Codebase Refactoring (with help from Go)介紹了它的背景。類型別名主要用在:

  1. 在大規模的重構項目代碼的時候,尤其是將一個類型從一個包移動到另一個包中的時候,有些代碼使用新包中的類型,有些代碼使用舊包中的類型, 比如context
  2. 允許一個龐大的包分解成內部的幾個小包,但是小包中的類型需要集中暴漏在上層的大包中

類型別名

類型別名的文法如下:

1
type identifier = Type

它和類型定義(type definition)類似,僅僅是在identifierType之間加了一個等號=,但是和類型定義區別很大,這一點會在後面專門比較。

下面這個例子就是為字串string類型定義了一個別名S,你可以聲明變數、常量為S類型,將字串賦值給它,它和字串類型幾乎一模一樣。

12345678910
package mainimport "fmt"type S = stringfunc main() {var s S = "hello world"fmt.Println(s)}

當然, 你可以為任意的類型定義類型別名,語言規範中沒有限制,可以為數組、結構體、指標、函數、介面、Slice、Map、Channel定義別名,甚至你還可以為通過類型定義(type definition)的類型定義別名,更甚者是你可以為別名定義別名。

比如下面這個例子, 為函數類型func()定義了一個別名F:

12345678910111213
package mainimport "fmt"type F = func()func main() {var foo F = func() {fmt.Println("hello type aliases")}foo()}

又如下面的代碼,為interface{}定義了別名G:

12345678910
package mainimport "fmt"type G = interface{}func main() {var g G = "hello world"fmt.Println(g)}

類型別名還可以為其它包中的類型定義別名,只要這個類型在其它包中是exported的:

12345678910111213
package mainimport ("fmt""time")type MyTime = time.Timefunc main() {var t MyTime = time.Now()fmt.Println(t)}

類型命名和型別宣告的區別

記住下面一句話:

類型別名和原類型完全一樣,只不過是另一種叫法而已

這句話隱藏著很多的智慧,你可以慢慢體會。

完全一樣(identical types)意味著這兩種類型的資料可以互相賦值,而類型定義要和原始類型賦值的時候需要類型轉換(Conversion T(x))。

下面這個例子中,v是整數類型,可以直接賦值給d,因為d的類型是D,是是整數的別名。而var i I = v這一句會出錯,因為I和整數是兩個類型。

所以類型別名和類型定義最大的區別在於:類型別名和原類型是相同的,而類型定義和原類型是不同的兩個類型。

12345678910
package maintype D = inttype I intfunc main() {v := 100var d D = vvar i I = v}

比如類型定義type Tnamed Tunderlying,系列類型和組合類別型是不同的:

  • TnamedTunderlying
  • *Tnamed*Tunderlying
  • chan Tnamedchan Tunderlying
  • func(Tnamed)func(Tunderlying)
  • interface{ M() Tnamed }interface{ M() Tunderlying }

但是對於別名type T1 = T2,下列類型和組合類別型是相同的:

  • T1T2
  • *T1*T2
  • chan T1chan T2
  • func(T1)func(T2)
  • interface{ M() T1 }interface{ M() T2 }

還有一個重要的區別在於類型定義的類型的方法集和原始類型的方法集沒有任何關係,而類型別名和原始類型的方法集是一樣的,下面再介紹。

既然類型別名和原類型是相同的,那麼在`switch - type中,你不能將原類型和類型別名作為兩個分支,因為這是重複的case:

123456789101112131415161718
package mainimport "fmt"type D = intfunc main() {var v interface{} var d D = 100v = dswitch i := v.(type) {case int:fmt.Println("it is an int:", i)// case D:// fmt.Println("it is D type:", i)}}

類型迴圈

類型別名在定義的時候不允許出現迴圈定義別名的情況,如下面所示:

12
type T1 = T2type T2 = T1

上面的例子太明顯,下面這個例子比較隱蔽,也是迴圈定義類型別名的情況,當然這些在編譯代碼的時候編譯器會幫你檢查,如果出現迴圈定義的情況會出錯。

12345
type T1 = struct {next *T2}type T2 = T1

可匯出性

如果定義的類型別名是exported (首字母大寫)的,那麼別的包中就可以使用,它和原始類型是否可exported沒關係。也就是說,你可以為unexported類型定義一個exported的類型別名,如下面的例子:

12345
type t1 struct {S string}type T2 = t1

方法集

既然類型別名和原始類型是相同的,那麼它們的方法集也是相同的。

下面的例子中T1T3都有saygreeting方法。

1234567891011121314151617
type T1 struct{}type T3 = T1func (t1 T1) say(){}func (t3 *T3) greeting(){}func main() {var t1 T1// var t2 T2var t3 T3t1.say()t1.greeting()t3.say()t3.greeting()}

如果類型別名和原始類型定義了相同的方法,代碼編譯的時候會報錯,因為有重複的方法定義。

另一個有趣的現象是 embedded type, 比如下面的例子, T3T1的別名。在定義結構體S的時候,我們使用了匿名嵌入類型,那麼這個時候調用s.say會怎麼樣呢? 實際是你會編譯出錯,因為s.say`不知道該調用s.T1.say還是s.T3.say`,所以這個時候你需要明確的調用。

12345678910111213
type T1 struct{}type T3 = T1func (t T1) say(){}type S struct {T1T3}func main() {var s Ss.say()}

進一步想,這樣是不是我們可以為其它庫中的類型增加新的方法了, 比如為標準庫的time.Time增加一個滴答方法:

123456789
type NTime = time.Timefunc (t NTime) Dida() {fmt.Println("嘀嗒嘀嗒嘀嗒嘀嗒搜尋")}func main() {t := time.Now()t.Dida()}

答案是: NO, 編譯的時候會報錯: cannot define new methods on non-local type time.Time

byte 和 rune 類型

在Go 1.9中, 內部其實使用了類型別名的特性。 比如內建的byte類型,其實是uint8的類型別名,而rune其實是int32的類型別名。

12345678
// byte is an alias for uint8 and is equivalent to uint8 in all ways. It is// used, by convention, to distinguish byte values from 8-bit unsigned// integer values.type byte = uint8// rune is an alias for int32 and is equivalent to int32 in all ways. It is// used, by convention, to distinguish character values from integer values.type rune = int32

參考資料

  1. https://github.com/golang/proposal/blob/master/design/18130-type-alias.md
  2. https://github.com/golang/go/issues/18130
  3. https://talks.golang.org/2016/refactor.article
  4. https://github.com/golang/go/issues/16339
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.