天天看點

從零開發區塊鍊應用(八)--結構體初識

文章目錄

  • ​​一、結構體定義​​
  • ​​二、初始化結構體​​
  • ​​三、結構體的通路​​
  • ​​四、結構體指針​​
  • ​​五、結構體可見性​​
  • ​​六、結構體标簽​​
  • ​​七、結構體嵌套​​
  • ​​八、結構體方法​​
  • ​​九、結構體特性​​

Go語言中提供了對struct的支援,struct,中文翻譯稱為結構體,與數組一樣,屬于複合類型,并非引用類型。

Go語言的struct,與C語言中的struct或其他面向對象程式設計語言中的類(class)類似,可以定義字段(屬性)和方法,但也有很不同的地方,需要深入學習,才能區分他們之間的差別。

一、結構體定義

Go語言中數組可以存儲同一類型的資料,但在結構體中我們可以為不同項定義不同的資料類型。

結構體是由一系列具有相同類型或不同類型的資料構成的資料集合

結構體成員是由一系列的成員變量構成,這些成員變量也被稱為“字段”

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

上面的代碼中,我們定義了一個包含5個字段的結構體,可以看到,相同類型name和email、gender和age在同一行中定義,但比較好的程式設計習慣是每一行隻定義一個字段,如:

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//所有字段均為空值      

另外也有使用字面量建立變量,這種使用方式,可以在大括号中為結構體的成員賦初始值,有兩種賦初始值的方式:

  • 按照順序提供初始化值:
var m2 = Member{
  1,
  "傑哥的技術雜貨鋪",
  "[email protected]",
  1,
  18,
}

或者
// 簡短變量聲明方式:
m2 := Member{
  1,
  "傑哥的技術雜貨鋪",
  "[email protected]",
  1,
  18
}      

這種方式要求所有的字段都必須指派,是以如果字段太多,每個字段都要指派,會很繁瑣,另一種則使用字段名為指定字段指派,如下面代碼中變量m3的建立,使用這種方式,對于其他沒有指定的字段,則使用該字段類型的零值作為初始化值。

  • 通過field:value的方式初始化,這樣可以任意順序
var m3 = Member{
  id:2,
  "name":"傑哥的技術雜貨鋪"
}

或者
// 簡短變量聲明方式:
m3 := Member{
  id:2,
  "name":"傑哥的技術雜貨鋪"
}      

三、結構體的通路

通過變量名,使用符号點(.),可以通路結構體類型中的字段,或為字段指派,也可以對字段進行取址(&)操作。

package main

import "fmt"

//定義結構體
type Persion struct {
  name    string
  age     int
  sex     string
  address string
}

func main()  {
  //1.方法一
  var p1 Persion
  p1.name = "傑哥的技術雜貨鋪"
  p1.age = 18
  p1.sex = "男"
  p1.address = "技術部落格"
  fmt.Printf("姓名:%s,年齡:%d,性别:%s,位址:%s\n",p1.name,p1.age,p1.sex,p1.address)

  //2.方法二
  p2 := Persion{}
  p2.name = "黃風怪"
  p2.age = 3000
  p2.sex = "男"
  p2.address = "黃風洞"
  fmt.Printf("姓名:%s,年齡:%d,性别:%s,位址:%s\n",p2.name,p2.age,p2.sex,p2.address)


  //3.方法三:建立結構體對象時,直接進行指派
  p3 := Persion{name: "蜘蛛精",age: 500,sex: "女",address: "盤絲洞"}
  fmt.Printf("姓名:%s,年齡:%d,性别:%s,位址:%s\n",p3.name,p3.age,p3.sex,p3.address)

  p4 := Persion{
    name:"黃眉大王",
    age: 1000,
    sex: "男",
    address: "小雷音寺",
  }
  fmt.Println(p4)

  //4.方法四:建立結構體對象時,不寫字段名,直接賦予數值
  //此種方式需要注意順序
  p5 := Persion{"白骨精",300,"女","白骨洞"}
  fmt.Println(p5)
}      

四、結構體指針

結構體與數組一樣,都是值傳遞,比如當把數組或結構體作為實參傳給函數的形參時,會複制一個副本,是以為了提高性能,一般不會把數組直接傳遞給函數,而是使用切片(引用類型)代替,而把結構體傳給函數時,可以使用指針結構體。

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

注意,指針類型為引用類型,聲明結構體指針時,如果未初始化,則初始值為nil,隻有初始化後,才能通路字段或為字段指派。
var m1 *Member
m1.name = "傑哥的技術雜貨鋪"//錯誤用法,未初始化,m1為nil

m1 = &Member{}
m1.name = "傑哥的技術雜貨鋪"//初始化後,結構體指針指向某個結構體位址,才能通路字段,為字段指派。      

另外,使用Go内置new()函數,可以配置設定記憶體來初始化結構休,并傳回配置設定的記憶體指針,因為已經初始化了,是以可以直接通路字段。

var m2 = new(Member)
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,
  "傑哥的技術雜貨鋪",
  "[email protected]",
  1,
  18
}//會引發panic錯誤
}      

是以,如果想在一個包中通路另一個包中結構體的字段,則必須是大寫字母開頭的變量,即可導出的變量,如:

type Member struct {
    Id     int
    Name   string
    Email  string
    Gender int
    Age    int
}      

六、結構體标簽

在定義結構體字段時,除字段名稱和資料類型外,還可以使用反引号為結構體字段聲明元資訊,這種元資訊稱為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"​

七、結構體嵌套

結構體嵌套,可以了解為定義一個結構體中,其字段可以是其他的結構體,這樣,不同的結構體就可以共用相同的字段。

注意, 結構體不能包含自身,但可能包含指向自身的結構體指針。
package main

import (
  "fmt"
)

//1.定義一個書的結構體
type Book struct {
  bookname string
  price float64
}

//2.定義學生的結構體
type Student struct {
  name string
  age int
  book Book
}
type Student2 struct {
  name string
  age int
  book *Book  //book結構體的位址
}
func main()  {
  b1 := Book{}
  b1.bookname = "西遊記"
  b1.price = 66.6

  s1 := Student{}
  s1.name = "紅孩兒"
  s1.age = 18
  s1.book = b1  //值傳遞
  fmt.Println(b1)
  fmt.Println(s1)
  fmt.Printf("學生姓名:%s,學生年齡:%d,看的書是:《%s》,書的價格是:%.2f\n",s1.name,s1.age,s1.book.bookname,s1.book.price)

  s2 := Student{name: "武松",age: 28,book: Book{bookname: "《Go語言從入門到放棄》",price: 88.8}}
  fmt.Println(s2.name,s2.age)
  fmt.Println("\t",s2.book.bookname,s2.book.price)

  s3 := Student{
    name: "jack",
    age: 17,
    book: Book{
      bookname: "十萬個為啥",
      price: 35.5,
    },
  }
  fmt.Println(s3.name,s3.age)
  fmt.Println("\t",s3.book.bookname,s3.book.price)


  b4 := Book{bookname: "射雕英雄傳",price: 76.0}
  s4 := Student2{name:"張三",age: 20,book: &b4}
  fmt.Println(b4)
  fmt.Println(s4)
  fmt.Println("\t",s4.book)

  s4.book.bookname = "挪威的森林"
  fmt.Println(b4)
  fmt.Println(s4)
  fmt.Println("\t",s4.book)
}      

可以看到,我們定義Student結構體時,可以把Book結構體作為Student的字段。

八、結構體方法

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

注意,并不是隻有結構體才能綁定方法,任何類型都可以綁定方法,隻是我們這裡介紹将方法綁定到結構體中。
func setName(m Member,name string){//普通函數
    m.Name = name
}

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

從上面的例子中,我們可以看出,通過方法接收器可以通路結構體的字段,我們可以任意命名方法接收器。

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

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語言是支援面向對象程式設計的,但卻沒有繼承的概念,在結構體中,可以通過組合其他結構體來建構更複雜的結構體。

  • 結構體不能包含自己

繼續閱讀