golang類和結構體

傑克曼發表於2019-02-16

golang結構體和類

golang中並沒有明確的物件導向的說法,實在要扯上的話,可以將struct比作其它語言中的class。

類宣告

type Poem struct {
    Title  string
    Author string
    intro  string
}

這樣就宣告瞭一個類,其中沒有public、protected、private的的宣告。golang用另外一種做法來實現屬性的訪問許可權:屬性的開頭字母是大寫的則在其它包中可以被訪問,否則只能在本包中訪問。類的宣告和方法亦是如此。

類方法宣告

func (poem *Poem) publish() {
    fmt.Println("poem publish")
}

或者

func (poem Poem) publish() {
    fmt.Println("poem publish")
}

和其它語言不一樣,golang宣告方法和普通方法一致,只是在func後增加了poem Poem這樣的宣告。加和沒有加*的區別在於一個是傳遞指標物件,一個是傳遞值物件。

傳遞指標和物件的區別

type T struct {
    Name string
}

func (t T) M1() {
    t.Name = "name1"
}

func (t *T) M2() {
    t.Name = "name2"
}

M1() 的接收者是值型別 T, M2() 的接收者是值型別 *T , 兩個方法內都是改變Name值。

下面宣告一個 T 型別的變數,並呼叫 M1() 和 M2() 。

    t1 := T{"t1"}

    fmt.Println("M1呼叫前:", t1.Name)
    t1.M1()
    fmt.Println("M1呼叫後:", t1.Name)

    fmt.Println("M2呼叫前:", t1.Name)
    t1.M2()
    fmt.Println("M2呼叫後:", t1.Name)

輸出結果為:

M1呼叫前: t1
M1呼叫後: t1
M2呼叫前: t1
M2呼叫後: name2

下面猜測一下go會怎麼處理。

先來約定一下:接收者可以看作是函式的第一個引數,即這樣的: func M1(t T), func M2(t *T)。 go不是物件導向的語言,所以用那種看起來像物件導向的語法來理解可能有偏差。

當呼叫 t1.M1() 時相當於 M1(t1) ,實參和行參都是型別 T,可以接受。此時在M1()中的t只是t1的值拷貝,所以M1()的修改影響不到t1。

當呼叫 t1.M2() => M2(t1),這是將 T 型別傳給了 *T 型別,go可能會取 t1 的地址傳進去: M2(&t1)。所以 M2() 的修改可以影響 t1 。

匿名結構體

p := struct {
    Name string
    Gender string
    Age uint8
}{"Robert", "Male", 33}

匿名結構體最大的用處是在內部臨時建立一個結構以封裝資料,而不必正式為其宣告相關規則。

例項化物件

例項化物件有好幾種方式

poem := &Poem{}

poem.Author = "Heine"

poem2 := &Poem{Author: "Heine"}

poem3 := new(Poem)

poem3.Author = "Heine"

poem4 := Poem{}

poem4.Author = "Heine"

poem5 := Poem{Author: "Heine"}

例項化的時候可以初始化屬性值,如果沒有指明則預設為系統預設值
注意:

p1 := &Poem{
        "zhangsan",
        25,
        []string{"lisi", "wangwu"},
    }

使用中如果包含陣列,結構體的例項化需要加上型別如上如果intro的型別是[]string。

加&符號和new的是指標物件,沒有的則是值物件,這點和php、java不一致,在傳遞物件的時候要根據實際情況來決定是要傳遞指標還是值。

tips:當物件比較小的時候傳遞指標並不划算。

建構函式(自己創造)

func NewPoem(param string, p ...interface{}) *Poem

示例:

func NewPoem(author string) (poem *Poem) {
    poem = &Poem{}
    poem.Author = author
    return
}

poem6 := NewPoem("Heine")

繼承

確切的說golang中叫做組合(composition)

func (e *Poem) ShowTitle() {
    fmt.Printf(e.Title)
}
type Poem struct {
    Title  string
    Author string
    intro  string
}

type ProsePoem struct {
    Poem
    Author string
}

ProsePoem屬性中宣告瞭Poem,表示組合了Poem的屬性和方法(屬性和方法都會被繼承)

初始化方式

1、先初始化為空再賦值

prosePoem := &ProsePoem{}
prosePoem.author = "Heine"

2、直接賦值

prosePoem := &ProsePoem{
        Poem: Poem{
            Title:  "Jack",
            Author: "slow",
            intro:  "simple",
        },
        Author: "test",
    }

如果其中屬性有衝突,則以外圍的為主,也就是說會被覆蓋。

type ProsePoem struct {
    Poem
    Author string
}

當訪問Author的時候預設為ProsePoem的Author,如果需要訪問Poem的Author屬性可以使用
prosePoem.Poem.Author來訪問方法同理

prosePoem := &ProsePoem{}

prosePoem.Author = "Shelley"

prosePoem.Poem.Author = "Heine"

fmt.Println(prosePoem)

從輸出中可以很直觀看到這一點。

&{{ Heine } Shelley}

方法的繼承和屬性一致,這裡不再羅列。通過組合的話可以很好的實現多繼承。

多繼承

比如有一個父親,是中國人:

type Father struct {
    MingZi string
}

func (this *Father) Say() string {
    return "大家好,我叫 " + this.MingZi
}

可以理解為父親類有一個屬性,有一個Say方法

有父親當然有母親,母親是個外國人:

type Mother struct {
    Name string
}

func (this *Mother) Say() string {
    return "Hello, my name is " + this.Name
}

父親和母親結合有了孩子類,孩子類繼承了父親和母親:

type Child struct {
    Father
    Mother
}

然後孩子類有一個例項c:

    c := new(Child)
    c.MingZi = "張小明"
    c.Name = "Tom Zhang"

因為MingZi和Name這個屬性在Mother和Father中並沒有衝突,所以可以直接使用 c. 就可以獲取而沒有問題

但是,如果這樣直接呼叫Child類的Say方式:

c.Say()

會出現衝突:

ambiguous selector c.Say

怎麼辦?其實這樣就可以輕鬆解決:

    c.Father.Say()
    c.Mother.Say()

上面兩條表示式的值分別為:

大家好,我叫 張小明
Hello, my name is Tom Zhang

方法過載

方法過載就是一個類中可以有相同的函式名稱,但是它們的引數是不一致的,在java、C++中這種做法普遍存在。golang中如果嘗試這麼做會報重新宣告(redeclared)錯誤,但是golang的函式可以宣告不定引數,這個非常強大。

func (poem *Poem) recite(v …interface{}) {

fmt.Println(v)

}

其中v …interface{}表示引數不定的意思,其中v是slice型別,fmt.Println方法也是這樣定義的。如果要根據不同的引數實現不同的功能,要在方法內檢測傳遞的引數。

相關文章