golang結構體看這篇就夠啦

struct概述

結構體是go語言最重要的數據結構之一,go和其它編程語言不一樣,它沒有類的概念,類比過來struct就相當于其它語言中的類,因此十分重要。

結構體這部分涉及到的知識點頁比較多,此文偏長,請耐心閱讀。

1. 認識結構體

直接說語法往往非常枯燥,在正式開始前,我們先來看一段簡單的結構體代碼,建立整體感知,后續我們再一一細說其中的知識點。

package main

import "fmt"

// Person結構體 - 相當于類
type Person struct {
    Name string // 字段Name 類型為string
    Age  int8   // 字段Age  類型為int8
}

// 實例方法
func (p Person) GetName() {
    fmt.Printf("My name: %s\n", p.Name)
}

func main() {
    p := Person{
        Name: "zhangsan",
        Age:  18,
    }

    p.GetName()
}

// My name: zhangsan

看到了吧,還是很簡單的,跟著注釋你大概已經看懂了如何使用。
下面我們拆分成知識點細細分析

1.1 如何定義

它按照如下方式定義(PS: 它還可以代標簽,為簡單起見,這里暫且不討論)

// 如下格式
type 結構體名 struct {
  字段名1 字段類型1
  字段名2 字段類型2
  .....
}

1.2 實例化

主要有幾種方式:

var p = new(Person) // 這里返回的是實力化后的地址  注意地址哈
var p Person        // 它是值類型 所以這種相當于零值初始化
var p = Person{}    // 這種和上面等效

// 這是一種最標準的賦值方式 把每個字段名都寫了出來
p := Person{
    Name: "zhangsan",
    Age:  18,
}

// 可以省略字段名
p := Person{"zhangsan", 18}

實際例化后我們可以通過obj.字段名的方式調出值,如上例中p.Name

1.3 方法

結構體方法,對應到面向對象語言中就是實例方法.

在上例中,如下部分:

// 實例方法
func (p Person) GetName() {
    fmt.Printf("My name: %s\n", p.Name)
}

// 在這里 p Person稱為接收者 后續為方法名
// 定義后 我們可以同 obj.方法名調用

方法和函數有什么主要區別呢?

方法它有接收者,而函數沒有

1.4 接收者

接收者既可以是值也可以是指針類型,我們看下:

package main

import "fmt"

// Person結構體 - 相當于類
type Person struct {
    Name string // 字段Name 類型為string
    Age  int8   // 字段Age  類型為int8
}

// 值接收者
func (p Person) GetName() {
    fmt.Printf("My name: %s\n", p.Name)
}

// 指針接收者
func (p *Person) GetAge() {
    fmt.Printf("My age: %d\n", p.Age)
}

func main() {
    p1 := Person{Name: "張三", Age: 18}  // 值
    p2 := &Person{Name: "李四", Age: 16} // 指針

    // 值對象可以同時調用 值接收者 和 指針接收者方法
    p1.GetName()
    p1.GetAge()

    fmt.Println("---------分割線-------")
    // 指針對象可以同時調用 值接收者 和 指針接收者方法
    p2.GetName()
    p2.GetAge()
}

// My name: 張三
// My age: 18
// ---------分割線-------
// My name: 李四
// My age: 16

我們可以發現,無論接收者是值類型還是指針類型,它們在調用上卻不會有任何區別,這是因為go編譯器會悄悄自動幫我轉換, nice!

1.5 指針接收者or值接收者

那么什么時候使用值接收者啥時候用指針接收者呢?

  1. 在go中一般約定,同一個struct接收者類型保持一致(要么全是指針接收者,要么全是值接收者
  2. 值接收者: 結構體相對較小(拷貝成本不高),不需要改變結構體內部值場景
  3. 指針接收者: 結構體比較大(拷貝成本高),需要改變結構體內部值場景

2. 匿名字段及嵌套

匿名字段可以說是結構體最有用的功能,使用的地方比比皆是,下面我們來看下

2.1 匿名字段

所謂匿名字段指的是在結構體中字段名可以不用顯示寫出來,比如:

package main

import "fmt"

type Data struct {
    uint8 // 沒有結構體字段名 只有類型名
          // 此時字段名 == 類型名
}

func main() {
    d := Data{8}
    // 直接通過類型名調用
    fmt.Println(d.uint8)
}

// 8

關鍵點在于字段名 == 類型名

2.2 結構體嵌套

在開始之前我們來看下兩個結構體

// 人結構體
type Person struct {
    Name string // 姓名
    Age  int8   // 年齡
}

// 結構體
type Student struct {
    ID    int     // 學生id
    Name  string  // 姓名
    Age   int8    // 年齡
    Score float32 // 分數
}

我們會發現學生結構體和人結構體相比只多了兩個字段(IDScore)分別定義有點浪費?
另外人和學生有許多相似的地方,某些時候Person結構體中的方法,Student同樣也需要,如果分別寫兩份相同的方法,也很浪費?

好啦!在go中可以通過嵌套解決,直接看代碼

package main

import "fmt"

type Person struct {
    Name string // 姓名
    Age  int8   // 年齡
}

// person結構體方法
func (p Person) GetName() {
    fmt.Printf("My name: %s\n", p.Name)
}

type Student struct {
    ID     int     // 學生id
    Score  float32 // 分數
    Person         // 嵌套Person結構體 這里是匿名字段
}

func (s Student) GetScore() {
    fmt.Printf("My score: %v\n", s.Score)
}

func main() {
    p := Student{
        ID:    1,
        Score: 98,
        Person: Person{ // 這里是匿名字段 字段名 == 字段類型
            Name: "zhangsan",
            Age:  18,
        },
    }

    // 調用嵌套結構體字段
    fmt.Printf("My age: %d\n", p.Age)                     // 直接調用 嵌套結構體字段
    fmt.Printf("My age p.Person.age: %d\n", p.Person.Age) // 通過匿名字段間接調用

    p.GetScore()       // 調用自己方法
    p.GetName()        // 直接調用嵌套結構體字段
    p.Person.GetName() // 通過匿名字段間接調用
}

// My age: 18
// My age p.Person.age: 18
// My score: 98
// My name: zhangsan
// My name: zhangsan

上面的注釋已經非常詳細,這里總結下規律:

匿名結構體嵌套,會有如下效果:

  1. 匿名結構體中字段,當前結構體可以直接調用
  2. 匿名結構體方法,當前結構體可以直接調用

本質是:go在字段查找時,現在本結構體中找,如果找不到則到匿名結構體中查找;方法同理

2.3 匿名結構體嵌套經典使用

數據庫表設計中:
我們可以把常用的字段抽出來成一個結構體,其它結構體只需要引入就可以擴展其中字段以及方法,比如:

package main

import (
    "fmt"
    "time"
)

type BaseTable struct {
    ID        int
    CreatedAt time.Time
    UpdatedAt time.Time
}

type User struct {
    Name      string
    BaseTable // 擴展User
}

3. 方法值和方法表達式

方法值和方法表達式類似于函數表達式,我們可以將函數表達式當作變量傳遞,方法值和方法表達式也是一樣,文字上不太容易明白,直接看代碼

package main

import (
    "fmt"
)

type Person struct {
    Name string
    Age  int8
}

func (p Person) GetName() {
    fmt.Printf("My name: %s\n", p.Name)
}

func main() {
    p := Person{Name: "zhangsan", Age: 18}

    // 方法值
    getName := p.GetName
    getName() // 調用方法值

    fmt.Println("--------分割線-------")
    // 方法表達方式
    pGetName := Person.GetName
    pGetName(p) // 方法表達式需要傳遞接收者
}

// My name: zhangsan
// --------分割線-------
// My name: zhangsan

它可以做為變量取出,因此可以實現復雜精巧場景下的使用,舉例這里不做舉例,方法值和方法表達式的區別在于:

方法表達式需要把接收者做為參數傳入

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容