玩轉(zhuǎn)Golang之Struct結(jié)構(gòu)體

先介紹一下go語言的類型系統(tǒng)

Golang中的類型系統(tǒng)

類型系統(tǒng)是指一個語言的類型體系結(jié)構(gòu)。一個典型的類型系統(tǒng)通常包含如下基本內(nèi)容:

? 基礎(chǔ)類型,如byte、int、bool、float等;
? 復(fù)合類型,如數(shù)組、結(jié)構(gòu)體、指針等;
? 可以指向任意對象的類型(Any類型);
? 值語義和引用語義;
? 面向?qū)ο螅此芯邆涿嫦驅(qū)ο筇卣?比如成員方法)的類型;
? 接口。

Go語言中的大多數(shù)類型都是值語義,并且都可以包含對應(yīng)的操作方法。在需要的時候,你可以給任何類型(包括內(nèi)置類型)“增加”新方法。而在實現(xiàn)某個接口時,無需從 該接口繼承(事實上,Go語言根本就不支持面向?qū)ο笏枷胫械睦^承語法),只需要實現(xiàn)該接口 要求的所有方法即可。任何類型都可以被Any類型引用。Any類型就是空接口,即interface{}。

什么是結(jié)構(gòu)體

結(jié)構(gòu)體(struct)是用戶自定義的類型,它代表若干字段的集合,可以用于描述一個實體對象,類似java中的class,是golang面向?qū)ο缶幊痰幕A(chǔ)類型。
結(jié)構(gòu)體的概念在軟件工程上舊的術(shù)語叫 ADT(抽象數(shù)據(jù)類型:Abstract Data Type)。在 C++ 它也存在,并且名字也是 struct,在面向?qū)ο蟮木幊陶Z言中,跟一個無方法的輕量級類一樣。因為 Go 語言中沒有類的概念,所以在 Go 中結(jié)構(gòu)體有著更為重要的地位。

如何定義一個結(jié)構(gòu)體

type Coordinate struct {
    X, Y float32
}

語法:type<Name>struct{}
上述代碼定義個一個名為Coordinate的結(jié)構(gòu)體,里面包括了兩個float32的變量X,Y,該結(jié)構(gòu)體可用于表示一個平面坐標(biāo)。

添加對象方法

其他的經(jīng)典面向?qū)ο笳Z言,如java,C#,定義對象方法時,會包含在class的定義內(nèi),如

public class Coordinate{
    public float X {get; set;}
    public float Y {get; set;}
    //打印坐標(biāo)
    public void GetCoordinate(){
          Console.WriteLine("("+this.X+","+this.Y+")");
    }
}

在go語言中,對象方法在結(jié)構(gòu)體定義的外部添加

type Coordinate struct {
    X, Y float32
}
//打印坐標(biāo)
func (coo *Coordinate) GetCoordinate() {
    fmt.Printf("(%.2f,%.2f)\n", coo.X, coo.Y)
    return
}

其中,func關(guān)鍵字后面的(coo *Coordinate),表示該函數(shù)傳入一個指向Coordinate的指針,可通過指針變量coo來操作結(jié)構(gòu)體的值。

幾種結(jié)構(gòu)體初始化

一、按原始字段順序通過創(chuàng)建結(jié)構(gòu)體

package main
import (
    "fmt"
)
func main(){
    p0 := Coordinate{1, 2}
    p0.GetCoordinate()
}

輸出:(1.00,2.00),其中X=1,Y=2

二、按照自定義字段順序進(jìn)行初始化

package main
import (
    "fmt"
)
func main(){
    p0 := Coordinate{Y:1, X:2}
    p0.GetCoordinate()
}

輸出:(2.00,1.00),其中X=2,Y=1

三、通過new函數(shù)創(chuàng)建

package main
import (
    "fmt"
)
func main(){
    //給該結(jié)構(gòu)體p2變量分配內(nèi)存,它返回指向已分配內(nèi)存的指針
    p0 := new(Coordinate)
    p0.X=1
    p0.Y=2
    p0.GetCoordinate()
}

輸出:(1.00,2.00),其中X=1,Y=2
其中p0 := new(Coordinate)等價于以下寫法

p3 := &Coordinate{X: 1, Y: 2}
p3 := &Coordinate{1,2}

比較三種創(chuàng)建方式

其中,第一種與第二種,p0均為一個類型為Coordinate的實例,而第三種p0為一個指向Coordinate的指針,相當(dāng)于var p0 *Coordinate = new(Coordinate)

一般在進(jìn)行例如type T struct {a, b int}的結(jié)構(gòu)體定義之后
習(xí)慣使用t := new(T)給該結(jié)構(gòu)體變量分配內(nèi)存,它返回指向已分配內(nèi)存的指針。變量t是一個指向T的指針,此時結(jié)構(gòu)體字段的值是它們所屬類型的零值。
聲明 var t T 也會給 t 分配內(nèi)存,并零值化內(nèi)存,但是這個時候 t是類型T。在這兩種方式中,t 通常被稱做類型T的一個實例(instance)或?qū)ο?code>(Object)。var t *T = new(T)等價于t := new(T)

通過代碼分析以上結(jié)論

package main
import (
    "fmt"
)
func main(){
    p0 := Coordinate{1, 2}
    //給該結(jié)構(gòu)體p2變量分配內(nèi)存,它返回指向已分配內(nèi)存的指針
    p2 := new(Coordinate)
    p2.X = 1
    p2.Y = 2
    p3 := &Coordinate{X: 1, Y: 2}
    p4 := &Coordinate{1, 2}

    fmt.Println("-------輸出p0-------")
    fmt.Printf("%v\n%T\n", p0, p0)
    fmt.Println("-------輸出p2-------")
    fmt.Printf("%v\n%T\n", p2, p2)
    fmt.Println("-------輸出p3-------")
    fmt.Printf("%v\n%T\n", p3, p3)
    fmt.Println("-------輸出p4-------")
    fmt.Printf("%v\n%T\n", p4, p4)
}

輸出:

-------輸出p0-------
{1 2}
Coordinate
-------輸出p2-------
&{1 2}
*Coordinate
-------輸出p3-------
&{1 2}
*Coordinate
-------輸出p4-------
&{1 2}
*Coordinate

可以看出來,p2,p3,p4均為一個指針變量

添加值拷貝的對象方法

剛才說到了,添加一個對象方法,可以通過func (t *T) functionname()來創(chuàng)建,其中t為一個指針變量。我們也可以通過值拷貝的方式,添加一個對象方法,語法為func(t T) functionname()

package main
import (
    "fmt"
)
type Coordinate struct {
    X, Y float32
}

func (coo *Coordinate) GetCoordinate() {
    fmt.Printf("(%.2f,%.2f)\n", coo.X, coo.Y)
    return
}
//值拷貝對象方法
func (coo Coordinate) SetPosition01(a float32,b float32) {
    coo.X = a
    coo.Y = b
}

//指針變量對象方法
func (coo *Coordinate) SetPosition02(a float32,b float32) {
    coo.X = a
    coo.Y = b
}
func main(){
    p0 := Coordinate{1, 2}
    fmt.Print("SetPosition01調(diào)用前:")
    p0.GetCoordinate()
    p0.SetPosition01(0, 0)
    fmt.Print("SetPosition01調(diào)用后:")
    p0.GetCoordinate()

    fmt.Print("SetPosition02調(diào)用前:")
    p0.GetCoordinate()
    p0.SetPosition02(0, 0)
    fmt.Print("SetPosition02調(diào)用后:")
    p0.GetCoordinate()
}

輸出:

SetPosition01調(diào)用前:(1.00,2.00)
SetPosition01調(diào)用后:(1.00,2.00)
SetPosition02調(diào)用前:(1.00,2.00)
SetPosition02調(diào)用后:(0.00,0.00)

從程序輸出中可以看出,調(diào)用SetPosition01方法,發(fā)生了值拷貝,即使在方法內(nèi)改變了coo的值,外部的p0的值沒有被改變。而SetPosition02方法中,coo為指向p0地址的指針,由于是通過指針變量修改了X,Y的值,所以調(diào)用完畢后,外部p0的值會被修改為(0,0)

匿名結(jié)構(gòu)體

package main
import (
    "fmt"
)
func main(){
    p_3d := struct {
        X, Y, Z float32
    }{1, 2, 3}
    fmt.Println("-------輸出p_3d-------")
    fmt.Printf("%v\n%T\n", p_3d, p_3d)
}

輸出:

-------輸出p_3d-------
{1 2 3}
struct { X float32; Y float32; Z float32 }

p_3d為一個包含X,Y,Z三個變量的匿名結(jié)構(gòu)體

golang構(gòu)造函數(shù)?

在Go語言中沒有構(gòu)造函數(shù)的概念,對象的創(chuàng)建通常交由一個全局的創(chuàng)建函數(shù)來完成,以 NewXXX來命名,表示“構(gòu)造函數(shù)”:
這一切非常自然,開發(fā)者也不需要分析在使用了new之后到底背后發(fā)生了多少事情。在Go語言中,一切要發(fā)生的事情都直接可以看到。
—— 《Go語言編程》

func NewRect(x, y, width, height float64) *Rect { 
    return &Rect{x, y, width, height}
}

變量、方法可見性

Go語言對關(guān)鍵字的增加非常吝嗇,其中沒有privateprotectedpublic這樣的關(guān)鍵 字。要使某個符號對其他包(package)可見(即可以訪問),需要將該符號定義為以大寫字母開頭,如:

 type Rect struct { 
  X, Y float64
  Width, Height float64 
}

這樣,Rect類型的成員變量就全部被導(dǎo)出了,可以被所有其他引用了Rect所在包的代碼訪問到。 成員方法的可訪問性遵循同樣的規(guī)則,例如:

func (r *Rect) area() float64 { 
    return r.Width * r.Height
} 

這樣,Rectarea()方法只能在該類型所在的包內(nèi)使用。
需要注意的一點是,Go語言中符號的可訪問性是包一級的而不是類型一級的。在上面的例 子中,盡管area()Rect的內(nèi)部方法,但同一個包中的其他類型也都可以訪問到它。這樣的可訪問性控制很粗曠,很特別,但是非常實用。如果Go語言符號的可訪問性是類型一級的,少不 了還要加上friend這樣的關(guān)鍵字,以表示兩個類是朋友關(guān)系,可以訪問彼此的私有成員。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容