golang 結構體和方法

1. 定義

結構體是將另個或者多個任意類型的命名變量組合在一起的聚合數據類型。

2. 成員變量

訪問控制機制
如果一個結構體的成員變量名稱是首字母大些的,那麼這個變量是可導出的(即在其他包可以訪問到)。
一個結構體可以同時包含可導出和不可導出的成員變量

type A struct {
    Hour int    //可導出
    minute int  //不可導出
}

限制
命名結構體類型s不可以定義一個擁有相同結構體類型s的成員變量,也就是一個聚合類型不可以包含它自己。但是s中可以定義一個s的指針類型,即*s。如下:

type B struct {
    value int
    //Next B   //錯誤
    Next *B    //正確
}

3. 結構體比較

1.如果結構體的所有成員變量都可以比較,那麼這個結構體是可以比較的。兩個結構體的比較可以使用==或者!=。

type C struct {
    A int
    B string
}
c1 := C{A:1, B:"abc"}
c2 := C{A:1, B:"abc"}
c3 := C{A:2, B:"abc"}
fmt.Println(c1.A == c2.A && c1.B == c2.B) //true
fmt.Println(c1 == c2)                        //true 與上等價
fmt.Println(c1.A == c3.A && c1.B == c3.B) //false
fmt.Println(c1 == c3)                        //false 與上等價

2.和其他可比較的類型一樣,可比較的結構體類型都可以作爲map的鍵類型。

type C struct {
    A int
    B string
}
mp := make(map[C]int)
key := C{A:1, B:"abc"}
mp[key] = 9
fmt.Println(mp[C{A:1, B:"abc"}])  //9

4. 結構體嵌套和匿名成員

1.go允許我們定義不帶名稱的結構體成員,只需要指定類型即可;這種結構體成員稱作匿名成員。這個結構體成員的類型必須是一個命名類型或者指向命名類型的指針。正是因爲有了這種結構體嵌套的功能,我們才能直接訪問到我們需要的變量而不是指定一大串中間變量。

type Point struct {
    X int
    Y int
}
type Circle struct {
    Point
}
var c Circle
c.X = 10  //等價於 c.Point.X = 10
c.Y = 10  //等價於 c.Point.Y = 10

type Wheel struct {
    *Point
}

2.結構體字面量初始化沒有快捷方式,必須遵循形狀類型的定義。

type Point struct {
    X int
    Y int
}
type Circle struct {
    Point
}
var c Circle
c = Circle{1,1}  //錯誤
c = Circle{Point{1,1}}  //正確
c = Circle{Point: Point{1,1}}  //正確

3.因爲“匿名成員”擁有隱式的名字,所以你不能在一個結構體裏面定義兩個相同類型的匿名成員,否則會引起衝突。由於匿名成員的名字是由它們的類型決定的,因此它們的可導出性也是由他們的的類型決定。在下面的例子中,point和circle這兩個匿名成員是可導出的,即使這兩個結構體是不可導出的(point和circle)。

type point struct {
    X int
    Y int
}
type circle struct {
    point
}

type Wheel struct {
    circle
}
var w Wheel
w.X = 8 // 等價於 w.circle.point.X = 8, w.X是可導出的,w.circle.point.X是不可導出的

5. 結構體方法

1. 定義

方法的聲明和普通函數的聲明類似,知識在函數名字前面多加了一個參數。這個參數把這個方法綁定到這個參數對應的類型上。
附加的參數p成爲方法的接收者, 它源於早先的面嚮對象語言,用來描述主調方法向對象發送消息。

type Point struct {
    X int
    Y int
}
func (p Point) print() {
    fmt.Println(p.X, p.Y)
}

2. 方法定義限制

go和許多其他面向對象的語言不同,它可以將方法綁定到除了指針類型和接口類型的任何類型上。可以很方便的爲簡單的類型(入整形、字符串、slice、map、甚至函數等)定義附加的行爲。

//整形
type III int
func (i III) print() {
    fmt.Println(i)
}

//函數
func Test() {
    fmt.Println("test")
}
type FunT func()
func (f FunT) Print() {
    fmt.Println(f)
}
func main() {
    var f FunT
    f = Test
    f.Print()
}

3.指針接收者的方法

由於主調函數會複製每一個實參變量,或者如果一個實參太大而我們希望避免複製整個實參,因此我們必須使用指針來傳遞變量的地址。這也同樣適用於更新接收者我們將它綁定到指針類型。在調用方法的時候,編譯器會對變量進行隱式轉換。
總結一下結構體方法可以成功調用的條件:
實參接收者和形參接收者是同一類型,比如都是T或者都是T。(1,4,5,7)
實參接收者是T類型的變量而形參接收者是
T類型,編譯器會隱式的獲取變量的地址(3)。
實參接收者是T類型而形參接收者是T類型,編譯器會隱式的獲取實際的取值。(2,6)
其中8編譯過程報錯的原因是:編譯器對T類型轉化爲T類型的隱式轉化,只有實參接收者是變量纔可以成功,因爲無法獲取臨時變量的地址。

type Point struct {
    X int
    Y int
}
func (p Point) Print() {
    fmt.Println(p.X, p.Y)
}
func (p *Point) ScaleBy(factor int) {
    p.X *= factor
    p.Y *= factor
}
func main() {
    p := Point{1,1}
    ptr := &p
    p.Print()   //1. 正確
    ptr.Print() //2. 正確
    p.ScaleBy(2)      //3. 正確
    ptr.ScaleBy(2)    //4. 正確
    Point{1,1}.Print()    //5. 正確
    (&Point{1,1}).Print() //6. 正確
    (&Point{1,1}).ScaleBy( 2) //7. 正確
    Point{1,1}.ScaleBy( 2)    //8. 錯誤
}

nil是一個合法的接收者:就像一些函數允許nil指針作爲實參,方法的接收者允許是nil指針

type A struct {
    Data int
}
func (a *A) FunPtrA() {
    fmt.Println("FunPtrA")
}
func main() {
    var ptrA *A
    ptrA = nil
    ptrA.FunPtrA()  //FunPtrA
}

4.通過結構體內嵌組成類型

這裏主要討論內嵌結構體和結構體指針後,對於源結構體定義函數的訪問權限。

type A struct {
    Data int
}
func (a A) FunA() {
    fmt.Println(a.Data)
}
func (a *A) FunPtrA1() {
    fmt.Println("FunPtrA1")
}
func (a *A) FunPtrA2() {
    fmt.Println("FunPtrA2")
    fmt.Println(a.Data)
}

type B struct {
    A
}
type C struct {
    *A
}
func main() {
    var b B
    b.FunA()    //可以訪問
    b.FunPtrA1() //可以訪問
    b.FunPtrA2() //可以訪問

    var c C
    //c.FunA()     //不可訪問
    c.FunPtrA1() //可以訪問
    //c.FunPtrA2() //不可訪問

    c.A = &A{}
    c.FunA()     //可以訪問
    c.FunPtrA1() //可以訪問
    c.FunPtrA2() //可以訪問
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章