天天看點

Go 聚合類型 struct 和 interface:結構體與接口都實作了哪些功能?

結構體

結構體定義

結構體是由一些列屬性組成的複合資料類型,每個屬性都具有名稱、類型和值,結構體将屬

性組合在一起進行由程式進行處理。

自定義類型

在 go 語言中使用 type 聲明一種新的類型,文法格式為:

Go 聚合類型 struct 和 interface:結構體與接口都實作了哪些功能?

Format 可以時任意内置類型、函數簽名、結構體、接口。使用自定義類型的好處是見名知道其意思。其次自定義類型可以添加方法,但是對于原始的資料類型是沒有辦法添加方法的。

type User map[string]string

 var c User
  c = make(User)
  c["one"] = "1"
  fmt.Println(c)      

也可以定義函數,對函數類型的重新定義

type Callback func()

  callbacks := map[string]Callback{}
  callbacks ["one"] = func() {
    fmt.Println("one")
  }

   v,ok := callbacks["one"]

   if ok{
    v()
   }      

結構體是一種聚合類型,裡面可以包含任意類型的值,這些值就是我們定義的結構體的成員,也稱為字段。在 Go 語言中,要自定義一個結構體,需要使用 type+struct 關鍵字組合。

在下面的例子中,我自定義了一個結構體類型,名稱為 person,表示一個人。這個 person 結構體有兩個字段:name 代表這個人的名字,age 代表這個人的年齡。

type person struct {

    name string

    age uint

}      

在定義結構體時,字段的聲明方法和平時聲明一個變量是一樣的,都是變量名在前,類型在後,隻不過在結構體中,變量名稱為成員名或字段名。

結構體的成員字段并不是必需的,也可以一個字段都沒有,這種結構體成為空結構體。

根據以上資訊,我們可以總結出結構體定義的表達式,如下面的代碼所示:

type structName struct{

    fieldName typeName

    ....

    ....

}      

 其中:

  • type 和 struct 是 Go 語言的關鍵字,二者組合就代表要定義一個新的結構體類型。
  • structName 是結構體類型的名字。
  • fieldName 是結構體的字段名,而 typeName 是對應的字段類型。
  • 字段可以是零個、一個或者多個。
小提示:結構體也是一種類型,是以以後自定義的結構體,我會稱為某結構體或某類型,兩者是一個意思。比如 person 結構體和 person 類型其實是一個意思。 

定義好結構體後就可以使用了,因為它是一個聚合類型,是以比普通的類型可以攜帶更多資料。

結構體聲明使用

結構體類型和普通的字元串、整型一樣,也可以使用同樣的方式聲明和初始化。

在下面的例子中,我聲明了一個 person 類型的變量 p,因為沒有對變量 p 初始化,是以預設會使用結構體裡字段的零值。

var p person      

當然在聲明一個結構體變量的時候,也可以通過結構體字面量的方式初始化,如下面的代碼所示:

p=person{"飛雪無情",
           30}      

采用簡短聲明法,同時采用字面量初始化的方式,把結構體變量 p 的 name 初始化為“飛雪無情”,age 初始化為 30,以逗号分隔。

聲明了一個結構體變量後就可以使用了,下面我們運作以下代碼,驗證 name 和 age 的值是否和初始化的一樣。

fmt.Println(p.name,p.age)      

在 Go 語言中,通路一個結構體的字段和調用一個類型的方法一樣,都是使用點操作符“.”。

采用字面量初始化結構體時,初始化值的順序很重要,必須和字段定義的順序一緻。

在 person 這個結構體中,第一個字段是 string 類型的 name,第二個字段是 uint 類型的 age,是以在初始化的時候,初始化值的類型順序必須一一對應,才能編譯通過。也就是說,在示例 {"飛雪無情",30} 中,表示 name 的字元串飛雪無情必須在前,表示年齡的數字 30 必須在後。

那麼是否可以不按照順序初始化呢?當然可以,隻不過需要指出字段名稱,如下所示:

p:=person{age:30,
          name:"飛雪無情",}      

其中,第一位我放了整型的 age,也可以編譯通過,因為采用了明确的 field:value 方式進行指定,這樣 Go 語言編譯器會清晰地知道你要初始化哪個字段的值。

有沒有發現,這種方式和 map 類型的初始化很像,都是采用冒号分隔。Go 語言盡可能地重用操作,不發明新的表達式,便于我們記憶和使用。

當然你也可以隻初始化字段 age,字段 name 使用預設的零值,如下面的代碼所示,仍然可以編譯通過。

p:=person{age:30}      

字段結構體

結構體的字段可以是任意類型,也包括自定義的結構體類型,比如下面的代碼: 

type person struct {

    name string

    age uint

    addr address

}

type address struct {

    province string

    city string

}      

在這個示例中,我定義了兩個結構體:person 表示人,address 表示位址。在結構體 person 中,有一個 address 類型的字段 addr,這就是自定義的結構體。

通過這種方式,用代碼描述現實中的實體會更比對,複用程度也更高。對于嵌套結構體字段的結構體,其初始化和正常的結構體大同小異,隻需要根據字段對應的類型初始化即可,如下面的代碼所示:

p:=person{

        age:30,

        name:"飛雪無情",

        addr:address{

            province: "北京",

            city:     "北京",

        },

    }      

如果需要通路結構體最裡層的 province 字段的值,同樣也可以使用點操作符,隻不過需要使用兩個點,如下面的代碼所示:

fmt.Println(p.addr.province)      

第一個點擷取 addr,第二個點擷取 addr 的 province。

結構體指針

new是初始化結構體的指針,使用 new 函數進行初始化結構體指針對象

type User struct {
 id int
 name string
 birthday time.Time
}

  b := new(User)
  fmt.Printf("%T,      %v\n",b,b)

*main.User,      &{0  {0 0 <nil>}}      

面向對象三大思想:

封裝📦:在go裡面實作封裝使用對是結構體,在其他語言裡面通過類來進行封裝,通過類實作封裝📦

結構體 > 類(類裡面一般都會有構造函數,用來建立類對應對執行個體的,建立結構體類型的變量可以使用New函數,看到new函數就知道是建構結構體的)

fun New(id int,name string,birthday Time) User{

return User{id,name,birthday}

}      

如果傳回的是指針類型的結構體

fun New(id int,name string,birthday Time) *User{

return &User{id,name,birthday}

}      
type User struct {
 id int
 name string
 birthday time.Time
}

func New(id int,name string,birthday time.Time) *User{
  return &User{
    id:       id,
    name:     name,
    birthday: birthday,
  }
}



  d := New(1,"jerry",time.Now())
  fmt.Println(d.id,d.name,d.birthday)


1 jerry 2022-03-08 08:42:34.551726 +0800 CST m=+0.001138918      

組合 > 繼承:目前有了一個結構體,想要在之前的結構體上面擴充并且使用

type Addr struct {
  province string
}

type User struct {
  id int
  name string
  add Addr
}      

組合可以了解為結構體裡面有屬性定義為了另外的結構體類型。 

多态 

匿名結構體

也有匿名結構體,一般使用在項目的配置,一個項目的配置隻有一份,一般會使用匿名結構體,

或者在做web前端開發,給模版上傳遞值。

e := struct {
   id int
   name string
  }{id: 1,name: "cherry"}
  fmt.Println(e)      

或者

var user1 struct{
      id int
      name string
   }

 
 user1 = struct {
    id   int
    name string
 }{id: 1, name: "jerry"}
 
 
fmt.Println(user1)

或者直接
 user1 := struct {
    id   int
    name string
 }{id: 1, name: "jerry"}      

匿名結構體一般不使用指針類型。 

接口

接口的定義

接口是和調用方的一種約定,它是一個高度抽象的類型,不用和具體的實作細節綁定在一起。接口要做的是定義好約定,告訴調用方自己可以做什麼,但不用知道它的内部實作,這和我們見到的具體的類型如 int、map、slice 等不一樣。

接口的定義和結構體稍微有些差别,雖然都以 type 關鍵字開始,但接口的關鍵字是 interface,表示自定義的類型是一個接口。也就是說 Stringer 是一個接口,它有一個方法 String() string,整體如下面的代碼所示:

type Stringer interface {

    String() string

}      
提示:Stringer 是 Go SDK 的一個接口,屬于 fmt 包。

針對 Stringer 接口來說,它會告訴調用者可以通過它的 String() 方法擷取一個字元串,這就是接口的約定。至于這個字元串怎麼獲得的,長什麼樣,接口不關心,調用者也不用關心,因為這些是由接口實作者來做的。

接口的實作

接口的實作者必須是一個具體的類型,繼續以 person 結構體為例,讓它來實作 Stringer 接口,如下代碼所示: 

func (p person) String()  string{

    return fmt.Sprintf("the name is %s,age is %d",p.name,p.age)

}      

給結構體類型 person 定義一個方法,這個方法和接口裡方法的簽名(名稱、參數和傳回值)一樣,這樣結構體 person 就實作了 Stringer 接口。

注意:如果一個接口有多個方法,那麼需要實作接口的每個方法才算是實作了這個接口。

實作了 Stringer 接口後就可以使用了。首先我先來定義一個可以列印 Stringer 接口的函數,如下所示: 

func printString(s fmt.Stringer){

    fmt.Println(s.String())

}      

這個被定義的函數 printString,它接收一個 Stringer 接口類型的參數,然後列印出 Stringer 接口的 String 方法傳回的字元串。

printString 這個函數的優勢就在于它是面向接口程式設計的,隻要一個類型實作了 Stringer 接口,都可以列印出對應的字元串,而不用管具體的類型實作。

因為 person 實作了 Stringer 接口,是以變量 p 可以作為函數 printString 的參數,可以用如下方式列印:

printString(p)      

 結果為:

the name is 飛雪無情,age is 30      
func (addr address) String()  string{

    return fmt.Sprintf("the addr is %s%s",addr.province,addr.city)

}      
printString(p.addr)
//輸出:the addr is 北京北京