結構體與繼承
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
至此,我們知道了兩種定義方法的方式:
-
以值做為方法接收者
-
以指標做為方法接收者
那我們如何進行選擇呢?以下幾種情況,應當直接使用指標做為方法的接收者。
-
你需要在方法內部改變結構體內容的時候
-
出於效能的問題,當結構體過大的時候
有些情況下,以值或指標做為接收者都可以,但是考慮到程式碼一致性,建議都使用指標做為接收者。
不管你使用哪種方法定義方法,指標例項物件、值例項物件都可以直接呼叫,而沒有什麼約束。這一點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.companyName
和staffInfo.company.companyName
的效果是一樣的。
小明 在 Tencent 工作
小明 在 Tencent 工作
5. 內部方法與外部方法
在 Go 語言中,函式名的首字母大小寫非常重要,它被來實現控制對方法的訪問許可權。
-
當方法的首字母為大寫時,這個方法對於所有包都是Public,其他包可以隨意呼叫
-
當方法的首字母為小寫時,這個方法是Private,其他包是無法訪問的。