Go Struct超詳細講解

原創做者,公衆號【程序員讀書】,歡迎關注公衆號,轉載文章請註明出處哦。程序員

Go語言中提供了對struct的支持,struct,中文翻譯稱爲結構體,與數組同樣,屬於複合類型,並不是引用類型。編程

Go語言的struct,與C語言中的struct或其餘面向對象編程語言中的類(class)相似,能夠定義字段(屬性)和方法,但也有很不一樣的地方,須要深刻學習,才能區分他們之間的區別。json

注意複合類型與引用類型之間的區別,這應該也是值傳遞和引用傳遞的區別吧。數組

定義

使用struct關鍵字能夠定義一個結構體,結構體中的成員,稱爲結構體的字段或屬性。bash

type Member struct {
    id          int
    name, email string
    gender, age int
}
複製代碼

上面的代碼中,咱們定義了一個包含5個字段的結構體,能夠看到,相同類型nameemailgenderage在同一行中定義,但比較好的編程習慣是每一行只定義一個字段,如:併發

type Member struct {
    id     int
    name   string
    email  string
    gender int
    age    int
}
複製代碼

固然,結構體也能夠不包含任何字段,稱爲空結構體,struct{}表示一個空的結構體,注意,直接定義一個空的結構體並無意義,但在併發編程中,channel之間的通信,可使用一個struct{}做爲信號量。編程語言

ch := make(chan struct{})
ch <- struct{}{}
複製代碼

使用

上面的例子中,咱們定義了Member結構體類型,接下就能夠這個自定義的類型建立變量了。函數

直接定義變量,這個使用方式並無爲字段賦初始值,所以全部字段都會被自動賦予自已類型的零值,好比name的值爲空字符串"",age的值爲0。性能

var m1 Member//全部字段均爲空值
複製代碼

使用字面量建立變量,這種使用方式,能夠在大括號中爲結構體的成員賦初始值,有兩種賦初始值的方式,一種是按字段在結構體中的順序賦值,下面代碼中m2就是使用這種方式,這種方式要求全部的字段都必須賦值,所以若是字段太多,每一個字段都要賦值,會很繁瑣,另外一種則使用字段名爲指定字段賦值,以下面代碼中變量m3的建立,使用這種方式,對於其餘沒有指定的字段,則使用該字段類型的零值做爲初始化值。學習

var m2 = Member{1,"小明","xiaoming@163.com",1,18} // 簡短變量聲明方式:m2 := Member{1,"小明","xiaoming@163.com",1,18}
var m3 = Member{id:2,"name":"小紅"}// 簡短變量聲明方式:m3 := Member{id:2,"name":"小紅"}
複製代碼

訪問字段

經過變量名,使用逗號(.),能夠訪問結構體類型中的字段,或爲字段賦值,也能夠對字段進行取址(&)操做。

fmt.Println(m2.name)//輸出:小明
m3.name = "小花"
fmt.Println(m3.name)//輸出:小花

age := &m3.age
*age = 20
fmt.Println(m3.age)//20
複製代碼

指針結構體

結構體與數組同樣,都是值傳遞,好比當把數組或結構體做爲實參傳給函數的形參時,會複製一個副本,因此爲了提升性能,通常不會把數組直接傳遞給函數,而是使用切片(引用類型)代替,而把結構體傳給函數時,可使用指針結構體

指針結構體,即一個指向結構體的指針,聲明結構體變量時,在結構體類型前加*號,便聲明一個指向結構體的指針,如:

注意,指針類型爲引用類型,聲明結構體指針時,若是未初始化,則初始值爲nil,只有初始化後,才能訪問字段或爲字段賦值。

var m1 *Member
m1.name = "小明"//錯誤用法,未初始化,m1爲nil

m1 = &Member{}
m1.name = "小明"//初始化後,結構體指針指向某個結構體地址,才能訪問字段,爲字段賦值。 

複製代碼

另外,使用Go內置new()函數,能夠分配內存來初始化結構休,並返回分配的內存指針,由於已經初始化了,因此能夠直接訪問字段。

var m2 = new(Member)
m2.name = "小紅"
複製代碼

咱們知道,若是將結構體轉給函數,只是複製結構體的副本,若是在函數內修改結構體字段值,外面的結構體並不會受影響,而若是將結構體指針傳給函數,則在函數中使用指針對結構體所作的修改,都會影響到指針指向的結構體。

func main() {
    m1 := Member{}
    m2 := new(Member)
    Change(m1,m2)
    fmt.Println(m1,m2)
}

func Change(m1 Member,m2 *Member){
    m1.Name = "小明"
    m2.Name = "小紅"
}
複製代碼

可見性

上面的例子中,咱們定義結構體字段名首字母是小寫的,這意味着這些字段在包外不可見,於是沒法在其餘包中被訪問,只容許包內訪問。

下面的例子中,咱們將Member聲明在member包中,然後在main包中建立一個變量,但因爲結構體的字段包外不可見,所以沒法爲字段賦初始值,沒法按字段仍是按索引賦值,都會引起panic錯誤。

package member
type Member struct {
    id     int
    name   string
    email  string
    gender int
    age    int
}

package main

fun main(){
    var m = member.Member{1,"小明","xiaoming@163.com",1,18}//會引起panic錯誤
}
複製代碼

所以,若是想在一個包中訪問另外一個包中結構體的字段,則必須是大寫字母開頭的變量,便可導出的變量,如:

type Member struct {
    Id     int
    Name   string
    Email  string
    Gender int
    Age    int
}
複製代碼

Tags

在定義結構體字段時,除字段名稱和數據類型外,還可使用反引號爲結構體字段聲明元信息,這種元信息稱爲Tag,用於編譯階段關聯到字段當中,如咱們將上面例子中的結構體修改成:

type Member struct {
    Id     int    `json:"id,-"`
    Name   string `json:"name"`
    Email  string `json:"email"`
    Gender int    `json:"gender,"`
    Age    int    `json:"age"`
}
複製代碼

上面例子演示的是使用encoding/json包編碼或解碼結構體時使用的Tag信息。

Tag由反引號括起來的一系列用空格分隔的key:"value"鍵值對組成,如:

Id int `json:"id" gorm:"AUTO_INCREMENT"`
複製代碼

特性

下面總結幾點結構體的相關特性:

值傳遞

結構體與數組同樣,是複合類型,不管是做爲實參傳遞給函數時,仍是賦值給其餘變量,都是值傳遞,即復一個副本。

沒有繼承

Go語言是支持面向對象編程的,但卻沒有繼承的概念,在結構體中,能夠經過組合其餘結構體來構建更復雜的結構體。

結構體不能包含本身

一個結構體,並無包含自身,好比Member中的字段不能是Member類型,但卻多是*Member。

方法

在Go語言中,將函數綁定到具體的類型中,則稱該函數是該類型的方法,其定義的方式是在func與函數名稱之間加上具體類型變量,這個類型變量稱爲方法接收器,如:

注意,並非只有結構體才能綁定方法,任何類型均可以綁定方法,只是咱們這裏介紹將方法綁定到結構體中。

func setName(m Member,name string){//普通函數
    m.Name = name
}

func (m Member)setName(name string){//綁定到Member結構體的方法
    m.Name = name
}
複製代碼

從上面的例子中,咱們能夠看出,經過方法接收器能夠訪問結構體的字段,這相似其餘編程語言中的this關鍵詞,但在Go語言中,只是一個變量名而已,咱們能夠任意命名方法接收器

調用結構體的方法,與調用字段同樣:

m := Member{}
m.setName("小明")
fmt.Println(m.Name)//輸出爲空
複製代碼

上面的代碼中,咱們會很奇怪,不是調用setName()方法設置了字段Name的值了嗎?爲何仍是輸出爲空呢?

這是由於,結構體是值傳遞,當咱們調用setName時,方法接收器接收到是隻是結構體變量的一個副本,經過副本對值進行修復,並不會影響調用者,所以,咱們能夠將方法接收器定義爲指針變量,就可達到修改結構體的目的了。

func (m *Member)setName(name string){/將Member改成*Member
    m.Name = name
}

m := Member{}
m.setName("小明")
fmt.Println(m.Name)//小明
複製代碼

方法和字段同樣,若是首字母爲小寫,則只容許在包內可見,在其餘包中是沒法訪問的,所以,若是要在其餘包中訪問setName,則應該將方法名改成SetName

組合

咱們知道,結構體中並無繼承的概念,其實,在Go語言中也沒有繼承的概念,Go語言的編程哲學裏,推薦使用組合的方式來達到代碼複用效果。

什麼是組合

組合,能夠理解爲定義一個結構體中,其字段能夠是其餘的結構體,這樣,不一樣的結構體就能夠共用相同的字段。

注意,在記得咱們前面提過的,結構體不能包含自身,但可能包含指向自身的結構體指針。

例如,咱們定義了一個名爲Animal表示動物,若是咱們想定義一個結構體表示貓,如:

type Animal struct {
    Name   string  //名稱
    Color  string  //顏色
    Height float32 //身高
    Weight float32 //體重
    Age    int     //年齡
}
//奔跑
func (a Animal)Run() {
    fmt.Println(a.Name + "is running")
}
//吃東西
func (a Animal)Eat() {
    fmt.Println(a.Name + "is eating")
}

type Cat struct {
    a Animal
}

func main() {
    var c = Cat{
	    a: Animal{
            Name:   "貓貓",
            Color:  "橙色",
            Weight: 10,
            Height: 30,
            Age:    5,
        },
    }
    fmt.Println(c.a.Name)
    c.a.Run()
}

複製代碼

能夠看到,咱們定義Cat結構體時,能夠把Animal結構體做爲Cat的字段。

匿名字段

上面的例子,咱們看到,把Animal結構體做爲Cat的字段時,其變量名爲a,因此咱們訪問Animal的方法時,語法爲c.a.Run(),這種經過葉子屬性訪問某個字段類型所帶的方法和字段用法很是繁瑣。

Go語言支持直接將類型做爲結構體的字段,而不須要取變量名,這種字段叫匿名字段,如:

type Lion struct {
	Animal //匿名字段
}

func main(){
    var lion = Lion{
        Animal{
            Name:  "小獅子",
            Color: "灰色",
        },
    }
    lion.Run()
    fmt.Println(lion.Name)
}
複製代碼

經過上面例子,能夠看到,經過匿名字段組合其餘類型,然後訪問匿名字段類型所帶的方法和字段時,不須要使用葉子屬性,很是方便。

小結

在Go語言編程中,結構體大概算是使用得最多的數據類型了,經過定義不一樣字段和方法的結構體,抽象組合不一樣的結構體,這大概即是Go語言中對面向對象編程了。


你的關注,是我寫做路上最大的鼓勵!

相關文章
相關標籤/搜索