1. 程式人生 > 實用技巧 >結構體與繼承

結構體與繼承

0. 什麼是結構體?

在之前學過的資料型別中,陣列與切片,只能儲存同一型別的變數。若要儲存多個型別的變數,就需要用到結構體,它是將多個容易型別的命令變數組合在一起的聚合資料型別。

每個變數都成為該結構體的成員變數。

可以理解為 Go語言 的結構體struct和其他語言的class有相等的地位,但是Go語言放棄大量面向物件的特性,所有的Go語言型別除了指標型別外,都可以有自己的方法,提高了可擴充套件性。

在 Go 語言中沒有沒有 class 類的概念,只有 struct 結構體的概念,因此也沒有繼承,本篇文章,帶你學習一下結構體相關的內容。

1. 定義結構體

宣告結構體

type 結構體名 struct
{ 屬性名 屬性型別 屬性名 屬性型別 ... }

比如我要定義一個可以儲存個人資料名為 Profile 的結構體,可以這麼寫

type Profile struct {
    name   string
    age    int
    gender string
    mother *Profile // 指標
    father *Profile // 指標
}

2. 定義方法

在 Go 語言中,我們無法在結構體內定義方法,那如何給一個結構體定義方法呢,答案是可以使用組合函式的方式來定義結構體方法。它和普通函式的定義方式有些不一樣,比如下面這個方法

func (person Profile) FmtProfile() {
    fmt.Printf("名字:%s\n", person.name)
    fmt.Printf("年齡:%d\n", person.age)
    fmt.Printf("性別:%s\n", person.gender)
}

其中fmt_profile是方法名,而(person Profile):表示將 fmt_profile 方法與 Profile 的例項繫結。我們把 Profile 稱為方法的接收者,而 person 表示例項本身,它相當於 Python 中的 self,在方法內可以使用person.屬性名

的方法來訪問例項屬性。

完整程式碼如下:

package main

import "fmt"

// 定義一個名為Profile 的結構體
type Profile struct {
    name   string
    age    int
    gender string
    mother *Profile // 指標
    father *Profile // 指標
}

// 定義一個與 Profile 的繫結的方法
func (person Profile) FmtProfile() {
    fmt.Printf("名字:%s\n", person.name)
    fmt.Printf("年齡:%d\n", person.age)
    fmt.Printf("性別:%s\n", person.gender)
}

func main() {
    // 例項化
    myself := Profile{name: "小明", age: 24, gender: "male"}
    // 呼叫函式
    myself.FmtProfile()
}
輸出如下

名字:小明
年齡:24
性別:male

3. 方法的引數傳遞方式

上面定義方法的方式叫當你想要在方法內改變例項的屬性的時候,必須使用指標做為方法的接收者。

package main

import "fmt"

// 宣告一個 Profile 的結構體
type Profile struct {
    name   string
    age    int
    gender string
    mother *Profile // 指標
    father *Profile // 指標
}

// 重點在於這個星號: *
func (person *Profile) increase_age() {
    person.age += 1
}

func main() {
    myself := Profile{name: "小明", age: 24, gender: "male"}
    fmt.Printf("當前年齡:%d\n", myself.age)
    myself.increase_age()
    fmt.Printf("當前年齡:%d", myself.age)
}

輸出結果 如下,可以看到在方法內部對 age 的修改已經生效。你可以嘗試去掉*,使用值做為方法接收者,看看age是否會發生改變。

當前年齡:24
當前年齡:25

至此,我們知道了兩種定義方法的方式:

  • 以值做為方法接收者

  • 以指標做為方法接收者

那我們如何進行選擇呢?以下幾種情況,應當直接使用指標做為方法的接收者。

  1. 你需要在方法內部改變結構體內容的時候

  2. 出於效能的問題,當結構體過大的時候

有些情況下,以值或指標做為接收者都可以,但是考慮到程式碼一致性,建議都使用指標做為接收者。

不管你使用哪種方法定義方法,指標例項物件、值例項物件都可以直接呼叫,而沒有什麼約束。這一點Go語言做得非常好。

4. 結構體實現 “繼承”

為什麼標題的繼承,加了雙引號,因為Go 語言本身並不支援繼承。

但我們可以使用組合的方法,實現類似繼承的效果。

在生活中,組合的例子非常多,比如一臺電腦,是由機身外殼,主機板,CPU,記憶體等零部件組合在一起,最後才有了我們用的電腦。

同樣的,在 Go 語言中,把一個結構體嵌入到另一個結構體的方法,稱之為組合。

現在這裡有一個表示公司(company)的結構體,還有一個表示公司職員(staff)的結構體。

type company struct {
    companyName string
    companyAddr string
}

type staff struct {
    name string
    age int
    gender string
    position string
}

若要將公司資訊與公司職員關聯起來,一般都會想到將 company 結構體的內容照抄到 staff 裡。

type staff struct {
    name string
    age int
    gender string
    companyName string
    companyAddr string
    position string
}

雖然在實現上並沒有什麼問題,但在你對同一公司的多個staff初始化的時候,都得重複初始化相同的公司資訊,這做得並不好,借鑑繼承的思想,我們可以將公司的屬性都“繼承”過來。

但是在 Go 中沒有類的概念,只有組合,你可以將 company 這個 結構體嵌入到 staff 中,做為 staff 的一個匿名欄位,staff 就直接擁有了 company 的所有屬性了。

type staff struct {
    name string
    age int
    gender string
    position string
    company   // 匿名欄位 
}

來寫個完整的程式驗證一下。

package main

import "fmt"

type company struct {
    companyName string
    companyAddr string
}

type staff struct {
    name string
    age int
    gender string
    position string
    company
}

func main()  {
    myCom := company{
        companyName: "Tencent",
        companyAddr: "深圳市南山區",
    }
    staffInfo := staff{
        name:     "小明",
        age:      28,
        gender:   "",
        position: "雲端計算開發工程師",
        company: myCom,
    }

    fmt.Printf("%s 在 %s 工作\n", staffInfo.name, staffInfo.companyName)
    fmt.Printf("%s 在 %s 工作\n", staffInfo.name, staffInfo.company.companyName)
}

輸出結果如下,可見staffInfo.companyNamestaffInfo.company.companyName的效果是一樣的。

小明 在 Tencent 工作
小明 在 Tencent 工作

5. 內部方法與外部方法

在 Go 語言中,函式名的首字母大小寫非常重要,它被來實現控制對方法的訪問許可權。

  • 當方法的首字母為大寫時,這個方法對於所有包都是Public,其他包可以隨意呼叫

  • 當方法的首字母為小寫時,這個方法是Private,其他包是無法訪問的。