Like Share Discussion Bookmark Smile

J.J. Huang   2025-02-09   Getting Started Golang 01.基本型別   瀏覽次數:次   DMCA.com Protection Status

Go | 「別名」的介紹與應用

💬 簡介

別名(Alias)是一種將一個型別重新命名為其他名稱的功能。這樣的操作可以讓程式的可讀性更高,也可以解決某些場景下命名衝突的問題。別名可以用來為現有的型別(例如整數、結構等)賦予更易懂、更具描述性的名稱。

這篇文章將介紹如何創建別名,以及如何在實際程式中應用它們。

圖片來源:Gophers(地鼠造型的原創者為 Renee French)


🔎 什麼是別名?

在 Go 語言中,別名是通過 type 關鍵字來創建的。別名不會創建新的型別,而只是為已經存在的型別提供一個新的名稱。別名的主要目的是讓程式碼更具可讀性,並且避免重複書寫複雜的型別名稱。

  • 範例:基本別名創建
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package main

    import "fmt"

    // 為 int 型別創建別名
    type Age int

    func main() {
    var myAge Age = 25
    fmt.Println("My age is:", myAge) // 輸出:My age is: 25
    }

    📝 在這個範例中,我們使用 type Age int 創建了一個名為 Age 的別名,該別名實際上是 int 型別的另一個名稱。這樣可以讓程式碼中的變數和常數更具語義性。


🛠️ 別名的應用

1️⃣ 增強可讀性

別名最常見的用途之一是提高程式的可讀性。當一個型別的名稱不夠具體或過於冗長時,使用別名可以使程式碼更加簡潔和易於理解。

  • 範例:增強可讀性
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package main

    import "fmt"

    // 定義一個表示價格的別名
    type Price float64

    func main() {
    var productPrice Price = 199.99
    fmt.Println("Product price is:", productPrice) // 輸出:Product price is: 199.99
    }

    📝 在這個範例中,我們使用 Price 來代替 float64 型別,這樣不僅使程式碼更具描述性,也讓程式碼更加語義化。

2️⃣ 解決命名衝突

在大型程式中,有時候不同的庫或模塊會定義相同名稱的型別。在這種情況下,我們可以使用別名來避免命名衝突。

  • 範例:解決命名衝突
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    package main

    import "fmt"

    // 定義兩個結構,並使用別名來避免衝突
    type User struct {
    Name string
    }

    type Customer struct {
    Name string
    }

    // 為 User 結構創建別名
    type Person User

    func main() {
    user := User{"Alice"}
    customer := Customer{"Bob"}
    person := Person{"Charlie"}

    fmt.Println("User:", user.Name) // 輸出:User: Alice
    fmt.Println("Customer:", customer.Name) // 輸出:Customer: Bob
    fmt.Println("Person:", person.Name) // 輸出:Person: Charlie
    }

    📝 在這個範例中,我們創建了兩個具有相同屬性的結構 UserCustomer,並使用 Person 作為 User 的別名,這樣可以解決結構名稱的衝突問題。

3️⃣ 與介面的配合使用

在 Go 語言中,別名還可以與介面(interface)結合使用,讓程式碼更加靈活。

  • 範例:別名與介面
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    package main

    import "fmt"

    // 定義一個簡單的介面
    type Speaker interface {
    Speak() string
    }

    // 定義一個結構
    type Person struct {
    Name string
    }

    // Person 實現了 Speaker 介面
    func (p Person) Speak() string {
    return "Hello, my name is " + p.Name
    }

    func main() {
    var speaker Speaker
    speaker = Person{Name: "Alice"}

    fmt.Println(speaker.Speak()) // 輸出:Hello, my name is Alice
    }

    📝 雖然這個範例沒有直接使用別名,但我們可以通過為介面或結構創建別名來提高程式碼的可讀性和靈活性。


⚠️ 注意事項

1️⃣ 別名與原型的區別

Go 的別名與其他語言(如 C++)中的型別繼承不同。Go 中的別名只是一個新的名稱,並不創建新的型別。換句話說,type Age intint 還是同一個型別,這意味著它們在運算中沒有區別。

2️⃣ 別名不能被修改

由於 Go 的型別系統不支持型別的變更,因此,即使創建了別名,也無法在別名上進行更改。這意味著,使用別名的程式碼仍然受到原始型別的約束。


✨ 額外補充

1️⃣ 與指標結合的使用

我們可以將別名與指標結合,來達到更加靈活的效果。

  • 範例:別名與指標
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package main

    import "fmt"

    // 為 int 創建別名
    type Age int

    func main() {
    var age Age = 30
    var p *Age = &age

    fmt.Println("Age:", *p) // 輸出:Age: 30
    }

    📝 在這個範例中,我們將 Age 別名與指標結合,展示了如何靈活使用別名來操作資料。

2️⃣ 非本地型別不能定義方法

在 Go 語言中,當你為一個現有型別創建別名時,這個別名會指向原本的型別。如果你為這個別名嘗試添加方法,會遇到一些限制。特別是,對於非本地型別(即不在當前包中定義的型別),你不能為它們定義新的方法。

  • 範例:不能為 time.Duration 型別的別名定義方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package main

    import (
    "time"
    )

    // 為 time.Duration 設定一個別名 MyDuration
    type MyDuration = time.Duration

    // 嘗試為 MyDuration 添加一個方法
    func (m MyDuration) EasySet(a string) {

    }

    func main() {

    }
    在這段程式碼中,MyDurationtime.Duration 的別名,並且我們嘗試為 MyDuration 添加一個名為 EasySet 的方法。結果,這段程式碼會編譯出錯,錯誤訊息是:
1
cannot define new methods on non-local type time.Duration

這個錯誤的原因是 time.Duration 是在標準庫的 time 包中定義的,並不是在我們當前的 main 包中。因此,Go 不允許我們為這種非本地型別定義新的方法。

解決方法:

  • 你可以將 MyDuration 由別名改為新的型別定義,像這樣:
1
type MyDuration time.Duration

或者

  • 你也可以將 MyDuration 定義在 time 包中,這樣就可以直接在 time 包內對其進行擴展。

3️⃣ 結構嵌入時使用別名

使用別名來嵌入結構成員時,別名並不會完全改變原型別的行為。事實上,Go 在處理嵌入的結構時,會保留該結構的原型別名稱,這有時會引起一些不預期的行為,特別是當兩個結構有相同的成員時,可能會產生衝突。

  • 範例:別名在結構中的作用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    package main

    import "fmt"

    // 定義品牌
    type ProductBrand struct {
    Name string
    }

    // 為 ProductBrand 定義一個方法
    func (p ProductBrand) Show() {
    fmt.Println("品牌名稱: ", p.Name)
    }

    // 定義 ProductBrand 的別名 FakeBrand
    type FakeBrand = ProductBrand

    // 定義一個產品結構
    type Product struct {
    FakeBrand // 嵌入 FakeBrand
    ProductBrand // 嵌入 ProductBrand
    }

    func main() {
    // 初始化一個產品
    p := Product{
    FakeBrand: ProductBrand{Name: "SuperBrand"},
    ProductBrand: ProductBrand{Name: "UltraBrand"},
    }

    // 顯式呼叫 Show 方法
    p.FakeBrand.Show()

    // 反射查看結構成員
    fmt.Println("結構成員:")
    fmt.Printf("FieldName: %v, FieldType: %v\n", "FakeBrand", "ProductBrand")
    fmt.Printf("FieldName: %v, FieldType: %v\n", "ProductBrand", "ProductBrand")
    }
    這段程式碼展示了如何將 ProductBrand 作為 FakeBrand 的別名並嵌入到 Product 結構中。在這個範例中,我們將 FakeBrand 作為 ProductBrand 的別名嵌入 Product 結構。

在這個情況下,編譯後運行結果會顯示:

1
2
3
4
品牌名稱:  SuperBrand
結構成員:
FieldName: FakeBrand, FieldType: ProductBrand
FieldName: ProductBrand, FieldType: ProductBrand

重要提醒: 當我們呼叫 p.Show() 時,會發生編譯錯誤 “ambiguous selector”(歧義選擇)。這是因為 FakeBrandProductBrand 都包含 Show 方法,造成呼叫時的歧義。因此我們需要明確地指定要呼叫哪一個方法,避免產生混淆。


🎯總結

別名功能非常強大,能夠幫助提高程式碼的可讀性和可維護性,並在需要處理命名衝突時提供便利。透過簡單的 type 關鍵字,可以為現有型別創建別名,使程式更加語義化並避免重複的型別名稱。

別名特別適合用於為長型別名稱賦予簡短名稱,或者在面對外部庫的型別衝突時使用。

最後建議回顧一下 Go | 菜鳥教學 目錄,了解其章節內容。


註:以上參考了
Go