C語言中文網 目錄

Go語言type關鍵字(類型別名)

< 上一頁Go語言模擬枚舉 Go語言容器下一頁 >

注意:本節內容涉及 Go 語言新版本的功能。內容上會涉及后續章節講解的類型定義及結構體嵌入等特性。另外,本節內容適用于對 Go 語言很熟悉且正在關注工程升級、代碼重構等問題的讀者閱讀。

類型別名是 Go 1.9 版本添加的新功能。主要用于代碼升級、遷移中類型的兼容性問題。在 C/C++ 語言中,代碼重構升級可以使用宏快速定義新的一段代碼。Go 語言中沒有選擇加入宏,而是將解決重構中最麻煩的類型名變更問題。

在 Go 1.9 版本之前的內建類型定義的代碼是這樣寫的:
type byte uint8
type rune int32
而在 Go 1.9 版本之后變為:
type byte = uint8
type rune = int32
這個修改就是配合類型別名而進行的修改。

區分類型別名與類型定義

類型別名的寫法為:

type TypeAlias = Type

類型別名規定:TypeAlias 只是 Type 的別名,本質上 TypeAlias 與 Type 是同一個類型。就像一個孩子小時候有小名、乳名,上學后用學名,英語老師又會給他起英文名,但這些名字都指的是他本人。

類型別名與類型定義表面上看只有一個等號的差異,那么它們之間實際的區別有哪些呢?下面通過一段代碼來理解。
package main

import (
    "fmt"
)

// 將NewInt定義為int類型
type NewInt int

// 將int取一個別名叫IntAlias
type IntAlias = int

func main() {

    // 將a聲明為NewInt類型
    var a NewInt
    // 查看a的類型名
    fmt.Printf("a type: %T\n", a)

    // 將a2聲明為IntAlias類型
    var a2 IntAlias
    // 查看a2的類型名
    fmt.Printf("a2 type: %T\n", a2)
}
代碼運行結果:
a type: main.NewInt
a2 type: int

代碼說明如下:
  • 第 8 行,將 NewInt 定義為 int 類型,這是常見定義類型的方法,通過 type 關鍵字的定義,NewInt 會形成一種新的類型。NewInt 本身依然具備int的特性。
  • 第 11 行,將 IntAlias 設置為 int 的一個別名,使用 IntAlias 與 int 等效。
  • 第 16 行,將 a 聲明為 NewInt 類型,此時若打印,則 a 的值為 0。
  • 第 18 行,使用%T格式化參數,顯示 a 變量本身的類型。
  • 第 21 行,將 a2 聲明為 IntAlias 類型,此時打印 a2 的值為 0。
  • 第 23 行,顯示 a2 變量的類型。

結果顯示a的類型是 main.NewInt,表示 main 包下定義的 NewInt 類型。a2 類型是 int。IntAlias 類型只會在代碼中存在,編譯完成時,不會有 IntAlias 類型。

非本地類型不能定義方法

能夠隨意地為各種類型起名字,是否意味著可以在自己包里為這些類型任意添加方法?參見下面的代碼演示:
package main

import (
    "time"
)

// 定義time.Duration的別名為MyDuration
type MyDuration = time.Duration

// 為MyDuration添加一個函數
func (m MyDuration) EasySet(a string) {

}

func main() {

}
代碼說明如下:
  • 第 8 行,使用類型別名為 time.Duration 設定一個別名叫 MyDuration。
  • 第 11 行,為這個別名添加一個方法。

編譯上面代碼報錯,信息如下:

cannot define new methods on non-local type time.Duration


編譯器提示:不能在一個非本地的類型 time.Duration 上定義新方法。非本地方法指的就是使用 time.Duration 的代碼所在的包,也就是 main 包。因為 time.Duration 是在 time 包中定義的,在 main 包中使用。time.Duration 包與 main 包不在同一個包中,因此不能為不在一個包中的類型定義方法。

解決這個問題有下面兩種方法:
  • 將第 8 行修改為 type MyDuration time.Duration,也就是將 MyDuration 從別名改為類型。
  • 將 MyDuration 的別名定義放在 time 包中。

在結構體成員嵌入時使用別名

當類型別名作為結構體嵌入的成員時會發生什么情況?請參考下面的代碼。
package main

import (
    "fmt"
    "reflect"
)

// 定義商標結構
type Brand struct {
}

// 為商標結構添加Show()方法
func (t Brand) Show() {
}

// 為Brand定義一個別名FakeBrand
type FakeBrand = Brand

// 定義車輛結構
type Vehicle struct {

    // 嵌入兩個結構
    FakeBrand
    Brand
}

func main() {

// 聲明變量a為車輛類型
    var a Vehicle
   
    // 指定調用FakeBrand的Show
    a.FakeBrand.Show()

    // 取a的類型反射對象
    ta := reflect.TypeOf(a)

    // 遍歷a的所有成員
    for i := 0; i < ta.NumField(); i++ {

        // a的成員信息
        f := ta.Field(i)

        // 打印成員的字段名和類型
        fmt.Printf("FieldName: %v, FieldType: %v\n", f.Name, f.Type.
            Name())
    }
}
代碼輸出如下:
FieldName: FakeBrand, FieldType: Brand
FieldName: Brand, FieldType: Brand

代碼說明如下:
  • 第 9 行,定義商標結構。
  • 第 13 行,為商標結構添加 Show() 方法。
  • 第 17 行,為 Brand 定義一個別名 FakeBrand。
  • 第 20~25 行,定義車輛結構 Vehicle,嵌入 FakeBrand 和 Brand 結構。
  • 第 30 行,將 Vechicle 實例化為 a。
  • 第 33 行,顯式調用 Vehicle 中 FakeBrand 的 Show() 方法。
  • 第 36 行,使用反射取變量 a 的反射類型對象,以查看其成員類型。
  • 第 39~42 行,遍歷 a 的結構體成員。
  • 第 45 行,打印 Vehicle 類型所有成員的信息。

這個例子中,FakeBrand 是 Brand 的一個別名。在 Vehicle 中嵌入 FakeBrand 和 Brand 并不意味著嵌入兩個 Brand。FakeBrand 的類型會以名字的方式保留在 Vehicle 的成員中。

如果嘗試將第 33 行改為:

a.Show()

編譯器將發生報錯:

ambiguous selector a.Show

在調用 Show() 方法時,因為兩個類型都有 Show() 方法,會發生歧義,證明 FakeBrand 的本質確實是 Brand 類型。
< 上一頁Go語言模擬枚舉 Go語言容器下一頁 >

精美而實用的網站,提供C語言C++STLLinuxShellJavaGo語言等教程,以及socketGCCviSwing設計模式JSP等專題。

Copyright ?2011-2018 biancheng.net, 陜ICP備15000209號

底部Logo