天天看點

Go語言中的結構體

作者:一代鹽商

定義結構體

Go語言中通過關鍵字type定義自定義類型,結構體定義需要使用type和struct關鍵字。

文法:

type 結構體名 struct {
    成員變量1 類型1
    成員變量2 類型2
    成員變量3 類型3
    ...
}           

解釋:

  • 結構體名在同一個包内必須唯一,不能重複。首字母可以大寫也可以小寫,大寫表示這個結構體是公有的,在其它的包裡面也可以使用,小寫表示結構體屬于私有的,在其它地方不能使用
  • 結構體的字段必須唯一,不能重複
  • 同類型的結構體字段可以放在一行定義

示例:

type Person struct {
    name, sex string
    age       int
}           

執行個體化結構體

結構體執行個體化時,會真正地配置設定記憶體。

Go語言執行個體化結構體主要有以下三種方式:

  • 标準執行個體化
  • new函數執行個體化
  • 取位址執行個體化

示例:

type Person struct {
    name, sex string
    age       int
}

func main() {
    // 标準執行個體化
    var p1 Person
    // new函數執行個體化,執行個體化完成後會傳回結構體地指針類型
    p2 := new(Person)
    // 取位址執行個體化傳回的也是結構體指針類型
    p3 := &Person{}
    fmt.Println(p1, p2, p3)
}           

初始化結構體

鍵值對格式初始化
結構體執行個體 := 結構體類型{
    成員變量1:值1,
    成員變量2:值2,
    成員變量3:值3,
}           
清單格式初始化
結構體執行個體 := 結構體類型{
    值1,
    值2,
    值3,
}           

示例:

type Person struct {
    name, sex string
    age       int
}

func main() {
    // 鍵值對格式初始化
    p1 := Person{name: "孫權", sex: "男", age: 18}
    // 清單格式初始化
    p2 := Person{"曹操", "男", 58}
    fmt.Println(p1, p2)
}           

結構體内嵌

Go語言的結構體内嵌是一種組合特性,使用結構體内嵌可建構一種面向對象程式設計思想中的繼承關系。

結構體執行個體化後,可直接通路内嵌結構體的所有成員變量和方法。

示例:

type Person struct {
    name, sex string
    age       int
}

type Man struct {
    Person
    drink int
}

func main() {
    p1 := Man{Person: Person{name: "buddha", age: 18, sex: "男"}, drink: 2}
    fmt.Println(p1)
}           

結構體方法

方法和函數比較像,差別是函數屬于包,通過包調用函數,而方法屬于結構體,通過結構體變量調用。所謂方法就是定義了接收者的函數。接收者的概念就類似于其他語言中的this 或者self。

文法:

func (變量名 結構體類型) 方法名(參數清單) (傳回值清單) {
    // 方法體
}           

解釋:

  • 對于結構體方法,接收者可以是結構體類型的值或指針。
  • 接收者變量:接收者中的參數變量名在命名時,官方建議使用接收者類型名的第一個小寫字母,而不是self、this之類的命名。例如,Person類型的接收者變量應該命名為p,Connector類型的接收者變量應該命名為c等。
    • 非指針類型:表示不修改結構體的内容
    • 指針類型:表示修改結構體中的内容
  • 方法名、參數清單、傳回參數:具體格式與函數定義相同

示例:

// Person /*
type Person struct {
    name string
    age  int
    sex  string
}

// PrintInfo /*
func (p Person) PrintInfo() {
    fmt.Print(" 姓名: ", p.name)
    fmt.Print(" 年齡: ", p.age)
    fmt.Print(" 性别: ", p.sex)
    fmt.Println()
}
func (p *Person) SetInfo(name string, age int, sex string) {
    p.name = name
    p.age = age
    p.sex = sex
}

func main() {
    var person = Person{
        "曹操",
        58,
        "男",
    }
    person.PrintInfo()
    person.SetInfo("孫權", 18, "男")
    person.PrintInfo()
}           

運作結果為:

姓名: 曹操 年齡: 58 性别: 男
 姓名: 孫權 年齡: 18 性别: 男           

給任意類型添加方法

在Go語言中,接收者的類型可以是任何類型,不僅僅是結構體,任何類型都可以擁有方法。

type myInt int

func (i myInt) PrintInfo() {
    fmt.Println("hello,world", i)
}

func main() {
    var a myInt = 10
    a.PrintInfo()
}           
type關鍵字定義int新的自定義類型,然後添加方法

結構體的匿名字段

結構體允許其成員字段在聲明時沒有字段名而隻有類型,這種沒有名字的字段就被稱為匿名字段

type Person struct {
    string
    int
}

func main() {
    p := Person{"buddha", 18}
    fmt.Println(p)
}           
匿名字段預設采用類型名作為字段名,結構體要求字段名稱必須唯一,是以一個結構體中同種類型的匿名字段隻能一個

結構體的字段類型可以是:基本資料類型,也可以是切片、Map 以及結構體

如果結構體的字段類似是:指針、slice、和 map 的零值都是nil,即還沒有配置設定空間

如果需要使用這樣的字段,需要先make,才能使用

/**
    定義結構體
 */
type Person struct {
    name string
    age int
    hobby []string
    mapValue map[string]string
}

func main() {
    var person = Person{}
    person.name = "張三"
    person.age = 10

    // 給切片申請記憶體空間
    person.hobby = make([]string, 4, 4)
    person.hobby[0] = "睡覺"
    person.hobby[1] = "吃飯"
    person.hobby[2] = "打豆豆"

    // 給map申請存儲空間
    person.mapValue = make(map[string]string)
    person.mapValue["address"] = "北京"
    person.mapValue["phone"] = "123456789"

    // 列印完整資訊
    fmt.Printf("%#v", person)
}           

同時我們還支援結構體的嵌套,如下所示

// 使用者結構體
type User struct {
    userName string
    password string
    sex string
    age int
    address Address // User結構體嵌套Address結構體
}

// 收貨位址結構體
type Address struct {
    name string
    phone string
    city string
}

func main() {
    var u User
    u.userName = "moguBlog"
    u.password = "123456"
    u.sex = "男"
    u.age = 18
    
    var address Address
    address.name = "張三"
    address.phone = "110"
    address.city = "北京"
    u.address = address
    fmt.Printf("%#v", u)
}           

嵌套結構體的字段名沖突

嵌套結構體内部可能存在相同的字段名,這個時候為了避免歧義,需要指定具體的内嵌結構體的字段。(例如,父結構體中的字段 和 子結構體中的字段相似)

預設會從父結構體中尋找,如果找不到的話,再去子結構體中在找

如果子類的結構體中,同時存在着兩個相同的字段,那麼這個時候就會報錯了,因為程式不知道修改那個字段的為準。

結構體的繼承

結構體的繼承,其實就類似于結構體的嵌套,如下所示,我們定義了兩個結構體,分别是Animal 和 Dog,其中每個結構體都有各自的方法,然後通過Dog結構體 繼承于 Animal結構體

// 父結構體
type Animal struct {
    name string
}

func (a Animal) run() {
    fmt.Printf("%v 在運動 \n", a.name)
}

// 子結構體
type Dog struct {
    age int
    // 通過結構體嵌套,完成繼承
    Animal
}

func (dog Dog) cry() {
    fmt.Printf("%v 在汪汪叫 \n", dog.name)
}

func main() {
    var dog = Dog{
        age: 10,
        Animal: Animal{
            name: "泰迪",
        },
    }
    dog.run()
    dog.cry()
}           

運作後,發現Dog擁有了父類的方法

泰迪 在運動 
泰迪 在汪汪叫           

結構體和Json互相轉換

Go語言JSON序列化是指把結構體資料轉化成JSON格式的字元串,Go語言JSON的反序列化是指把JSON資料轉化成結構體對象。序列化和反序列化通過encoding/json包中的json.Marshal() 和 son.Unmarshal()

結構體轉json字元串

// 定義一個學生結構體,注意結構體的首字母必須大寫,代表公有,否則将無法轉換
type Student struct {
    ID string
    Gender string
    Name string
    Sno string
}
func main() {
    var s1 = Student{
        ID: "12",
        Gender: "男",
        Name: "李四",
        Sno: "s001",
    }
    // 結構體轉換成Json(傳回的是byte類型的切片)
    jsonByte, _ := json.Marshal(s1)
    jsonStr := string(jsonByte)
    fmt.Printf(jsonStr)
}           

json字元串轉結構體

// 定義一個學生結構體,注意結構體的首字母必須大寫,代表公有,否則将無法轉換
type Student struct {
    ID     string
    Gender string
    Name   string
    Sno    string
}

func main() {
    // Json字元串轉換成結構體
    var str = `{"ID":"12","Gender":"男","Name":"李四","Sno":"s001"}`
    var s2 = Student{}
    // 第一個是需要傳入byte類型的資料,第二參數需要傳入轉換的位址
    err := json.Unmarshal([]byte(str), &s2)
    if err != nil {
        fmt.Printf("轉換失敗 \n")
    } else {
        fmt.Printf("%#v \n", s2)
    }
}           
要實作結構體轉換成字元串,必須保證結構體中的字段是公有的,也就是首字母必須是大寫的。

結構體标簽Tag

Tag是結構體的元資訊,可以在運作的時候通過反射的機制讀取出來。Tag在結構體字段的後方定義,由一對反引号包裹起來,具體的格式如下:

key1:"value1" key2:"value2"           

結構體tag由一個或多個鍵值對組成。鍵與值使用冒号分隔,值用雙引号括起來。同一個結構體字段可以設定多個鍵值對tag,不同的鍵值對之間使用空格分隔。

注意事項:為結構體編寫Tag時,必須嚴格遵守鍵值對的規則。結構體标簽的解析代碼的容錯能力很差,一旦格式寫錯,編譯和運作時都不會提示任何錯誤,通過反射也無法正确取值。例如不要在key和value之間添加空格。

如下所示,我們通過tag标簽,來轉換字元串的key

// 定義一個Student體,使用結構體标簽
type Student2 struct {
    Id string `json:"id"` // 通過指定tag實作json序列化該字段的key
    Gender string `json:"gender"`
    Name string `json:"name"`
    Sno string `json:"sno"`
}
func main() {
    var s1 = Student2{
        Id: "12",
        Gender: "男",
        Name: "李四",
        Sno: "s001",
    }
    // 結構體轉換成Json
    jsonByte, _ := json.Marshal(s1)
    jsonStr := string(jsonByte)
    fmt.Println(jsonStr)

    // Json字元串轉換成結構體
    var str = `{"Id":"12","Gender":"男","Name":"李四","Sno":"s001"}`
    var s2 = Student2{}
    // 第一個是需要傳入byte類型的資料,第二參數需要傳入轉換的位址
    err := json.Unmarshal([]byte(str), &s2)
    if err != nil {
        fmt.Printf("轉換失敗 \n")
    } else {
        fmt.Printf("%#v \n", s2)
    }
}           

嵌套結構體和Json序列化反序列化

// 定義一個Student結構體
type Student3 struct {
    Id int
    Gender string
    Name string
}

// 定義一個班級結構體
type Class struct {
    Title string
    Students []Student3
}

func main() {
    var class = Class{
        Title: "1班",
        Students: make([]Student3, 0),
    }
    for i := 0; i < 10; i++ {
        s := Student3{
            Id: i + 1,
            Gender: "男",
            Name: fmt.Sprintf("stu_%v", i + 1),
        }
        class.Students = append(class.Students, s)
    }
    fmt.Printf("%#v \n", class)

    // 轉換成Json字元串
    strByte, err := json.Marshal(class)
    if err != nil {
        fmt.Println("列印失敗")
    } else {
        fmt.Println(string(strByte))
    }
}