go語言20小時從入門到精通(8、 面向對象編程)

##8.1 概述 對於面向對象編程的支持Go 語言設計得很是簡潔而優雅。由於, Go語言並無沿襲傳統面向對象編程中的諸多概念,好比繼承(不支持繼承,儘管匿名字段的內存佈局和行爲相似繼承,但它並非繼承)、虛函數、構造函數和析構函數、隱藏的this指針等。編程

儘管Go語言中沒有封裝、繼承、多態這些概念,但一樣經過別的方式實現這些特性: 封裝:經過方法實現 繼承:經過匿名字段實現 多態:經過接口實現bash

##8.2 匿名組合 ###8.2.1 匿名字段 通常狀況下,定義結構體的時候是字段名與其類型一一對應,實際上Go支持只提供類型,而不寫字段名的方式,也就是匿名字段,也稱爲嵌入字段。函數

當匿名字段也是一個結構體的時候,那麼這個結構體所擁有的所有字段都被隱式地引入了當前定義的這個結構體。佈局

//人
type Person struct {
    name string
    sex  byte
    age  int
}
//學生
type Student struct {
    Person // 匿名字段,那麼默認Student就包含了Person的全部字段
    id     int
    addr   string
}
複製代碼

###8.2.2 初始化測試

//人
type Person struct {
    name string
    sex  byte
    age  int
}

//學生
type Student struct {
    Person // 匿名字段,那麼默認Student就包含了Person的全部字段
    id     int
    addr   string
}

func main() {
    //順序初始化
    s1 := Student{Person{"mike", 'm', 18}, 1, "sz"}
    //s1 = {Person:{name:mike sex:109 age:18} id:1 addr:sz}
    fmt.Printf("s1 = %+v\n", s1)

    //s2 := Student{"mike", 'm', 18, 1, "sz"} //err

    //部分紅員初始化1
    s3 := Student{Person: Person{"lily", 'f', 19}, id: 2}
    //s3 = {Person:{name:lily sex:102 age:19} id:2 addr:}
    fmt.Printf("s3 = %+v\n", s3)

    //部分紅員初始化2
    s4 := Student{Person: Person{name: "tom"}, id: 3}
    //s4 = {Person:{name:tom sex:0 age:0} id:3 addr:}
    fmt.Printf("s4 = %+v\n", s4)
}
複製代碼

###8.2.3 成員的操做ui

var s1 Student //變量聲明     //給成員賦值     s1.name = "mike" //等價於 s1.Person.name = "mike"     s1.sex = 'm'     s1.age = 18     s1.id = 1     s1.addr = "sz"     fmt.Println(s1) //{{mike 109 18} 1 sz}this

var s2 Student //變量聲明     s2.Person = Person{"lily", 'f', 19}     s2.id = 2     s2.addr = "bj"     fmt.Println(s2) //{{lily 102 19} 2 bj}spa

###8.2.4 同名字段設計

//人
type Person struct {
    name string
    sex  byte
    age  int
}

//學生
type Student struct {
    Person // 匿名字段,那麼默認Student就包含了Person的全部字段
    id     int
    addr   string
    name   string //和Person中的name同名
}

func main() {
    var s Student //變量聲明

    //給Student的name,仍是給Person賦值?
    s.name = "mike"

    //{Person:{name: sex:0 age:0} id:0 addr: name:mike}
    fmt.Printf("%+v\n", s)

    //默認只會給最外層的成員賦值
    //給匿名同名成員賦值,須要顯示調用
    s.Person.name = "yoyo"
    //Person:{name:yoyo sex:0 age:0} id:0 addr: name:mike}
    fmt.Printf("%+v\n", s)
}
複製代碼

###8.2.5 其它匿名字段 ####8.2.5.1 非結構體類型 全部的內置類型和自定義類型都是能夠做爲匿名字段的:指針

type mystr string //自定義類型

type Person struct {
    name string
    sex  byte
    age  int
}

type Student struct {
    Person // 匿名字段,結構體類型
    int     // 匿名字段,內置類型
    mystr   // 匿名字段,自定義類型
}

func main() {
    //初始化
    s1 := Student{Person{"mike", 'm', 18}, 1, "bj"}

    //{Person:{name:mike sex:109 age:18} int:1 mystr:bj}
    fmt.Printf("%+v\n", s1)

    //成員的操做,打印結果:mike, m, 18, 1, bj
    fmt.Printf("%s, %c, %d, %d, %s\n", s1.name, s1.sex, s1.age, s1.int, s1.mystr)
}
複製代碼

####8.2.5.2 結構體指針類型

type Person struct { //人
    name string
    sex  byte
    age  int
}

type Student struct { //學生
    *Person // 匿名字段,結構體指針類型
    id      int
    addr    string
}

func main() {
    //初始化
    s1 := Student{&Person{"mike", 'm', 18}, 1, "bj"}

    //{Person:0xc0420023e0 id:1 addr:bj}
    fmt.Printf("%+v\n", s1)
    //mike, m, 18
    fmt.Printf("%s, %c, %d\n", s1.name, s1.sex, s1.age)

    //聲明變量
    var s2 Student
    s2.Person = new(Person) //分配空間
    s2.name = "yoyo"
    s2.sex = 'f'
    s2.age = 20

    s2.id = 2
    s2.addr = "sz"

    //yoyo 102 20 2 20
    fmt.Println(s2.name, s2.sex, s2.age, s2.id, s2.age)
}
複製代碼

##8.3 方法 ###8.3.1 概述 在面向對象編程中,一個對象其實也就是一個簡單的值或者一個變量,在這個對象中會包含一些函數,這種帶有接收者的函數,咱們稱爲方法(method)。 本質上,一個方法則是一個和特殊類型關聯的函數。

一個面向對象的程序會用方法來表達其屬性和對應的操做,這樣使用這個對象的用戶就不須要直接去操做對象,而是藉助方法來作這些事情。

在Go語言中,能夠給任意自定義類型(包括內置類型,但不包括指針類型)添加相應的方法。

⽅法老是綁定對象實例,並隱式將實例做爲第⼀實參 (receiver),方法的語法以下: func (receiver ReceiverType) funcName(parameters) (results) 參數 receiver 可任意命名。如⽅法中不曾使⽤,可省略參數名。 參數 receiver 類型能夠是 T 或 *T。基類型 T 不能是接⼝或指針。 不支持重載方法,也就是說,不能定義名字相同可是不一樣參數的方法。 ###8.3.2 爲類型添加方法 ####8.3.2.1 基礎類型做爲接收者

type MyInt int //自定義類型,給int更名爲MyInt

//在函數定義時,在其名字以前放上一個變量,便是一個方法
func (a MyInt) Add(b MyInt) MyInt { //面向對象
    return a + b
}

//傳統方式的定義
func Add(a, b MyInt) MyInt { //面向過程
    return a + b
}

func main() {
    var a MyInt = 1
    var b MyInt = 1

    //調用func (a MyInt) Add(b MyInt)
    fmt.Println("a.Add(b) = ", a.Add(b)) //a.Add(b) =  2

    //調用func Add(a, b MyInt)
    fmt.Println("Add(a, b) = ", Add(a, b)) //Add(a, b) =  2
}
複製代碼

經過上面的例子能夠看出,面向對象只是換了一種語法形式來表達。方法是函數的語法糖,由於receiver其實就是方法所接收的第1個參數。

注意:雖然方法的名字如出一轍,可是若是接收者不同,那麼方法就不同。

####8.3.2.2 結構體做爲接收者 方法裏面能夠訪問接收者的字段,調用方法經過點( . )訪問,就像struct裏面訪問字段同樣:

type Person struct {
    name string
    sex  byte
    age  int
}

func (p Person) PrintInfo() { //給Person添加方法
    fmt.Println(p.name, p.sex, p.age)
}

func main() {
    p := Person{"mike", 'm', 18} //初始化
    p.PrintInfo() //調用func (p Person) PrintInfo()
}

8.3.3 值語義和引用語義
type Person struct {
    name string
    sex  byte
    age  int
}

//指針做爲接收者,引用語義
func (p *Person) SetInfoPointer() {
    //給成員賦值
    (*p).name = "yoyo"
    p.sex = 'f'
    p.age = 22
}

//值做爲接收者,值語義
func (p Person) SetInfoValue() {
    //給成員賦值
    p.name = "yoyo"
    p.sex = 'f'
    p.age = 22
}

func main() {
    //指針做爲接收者,引用語義
    p1 := Person{"mike", 'm', 18} //初始化
    fmt.Println("函數調用前 = ", p1)   //函數調用前 =  {mike 109 18}
    (&p1).SetInfoPointer()
    fmt.Println("函數調用後 = ", p1) //函數調用後 =  {yoyo 102 22}

    fmt.Println("==========================")

    p2 := Person{"mike", 'm', 18} //初始化
    //值做爲接收者,值語義
    fmt.Println("函數調用前 = ", p2) //函數調用前 =  {mike 109 18}
    p2.SetInfoValue()
    fmt.Println("函數調用後 = ", p2) //函數調用後 =  {mike 109 18}
}
複製代碼

###8.3.4 方法集 類型的方法集是指能夠被該類型的值調用的全部方法的集合。

用實例實例 value 和 pointer 調用方法(含匿名字段)不受⽅法集約束,編譯器編老是查找所有方法,並自動轉換 receiver 實參。

####8.3.4.1 類型 *T 方法集 一個指向自定義類型的值的指針,它的方法集由該類型定義的全部方法組成,不管這些方法接受的是一個值仍是一個指針。

若是在指針上調用一個接受值的方法,Go語言會聰明地將該指針解引用,並將指針所指的底層值做爲方法的接收者。

類型 *T ⽅法集包含所有 receiver T + *T ⽅法:

type Person struct {
    name string
    sex  byte
    age  int
}

//指針做爲接收者,引用語義
func (p *Person) SetInfoPointer() {
    (*p).name = "yoyo"
    p.sex = 'f'
    p.age = 22
}

//值做爲接收者,值語義
func (p Person) SetInfoValue() {
    p.name = "xxx"
    p.sex = 'm'
    p.age = 33
}

func main() {
    //p 爲指針類型
    var p *Person = &Person{"mike", 'm', 18}
    p.SetInfoPointer() //func (p) SetInfoPointer()

    p.SetInfoValue()    //func (*p) SetInfoValue()
    (*p).SetInfoValue() //func (*p) SetInfoValue()
}
複製代碼

####8.3.4.2 類型 T 方法集 一個自定義類型值的方法集則由爲該類型定義的接收者類型爲值類型的方法組成,可是不包含那些接收者類型爲指針的方法。

但這種限制一般並不像這裏所說的那樣,由於若是咱們只有一個值,仍然能夠調用一個接收者爲指針類型的方法,這能夠藉助於Go語言傳值的地址能力實現。

type Person struct {
    name string
    sex  byte
    age  int
}

//指針做爲接收者,引用語義
func (p *Person) SetInfoPointer() {
    (*p).name = "yoyo"
    p.sex = 'f'
    p.age = 22
}

//值做爲接收者,值語義
func (p Person) SetInfoValue() {
    p.name = "xxx"
    p.sex = 'm'
    p.age = 33
}

func main() {
    //p 爲普通值類型
    var p Person = Person{"mike", 'm', 18}
    (&p).SetInfoPointer() //func (&p) SetInfoPointer()
    p.SetInfoPointer()    //func (&p) SetInfoPointer()
    
    p.SetInfoValue()      //func (p) SetInfoValue()
    (&p).SetInfoValue()   //func (*&p) SetInfoValue()
}
複製代碼

###8.3.5 匿名字段 ####8.3.5.1 方法的繼承 若是匿名字段實現了一個方法,那麼包含這個匿名字段的struct也能調用該方法。

type Person struct {
    name string
    sex  byte
    age  int
}

//Person定義了方法
func (p *Person) PrintInfo() {
    fmt.Printf("%s,%c,%d\n", p.name, p.sex, p.age)
}

type Student struct {
    Person // 匿名字段,那麼Student包含了Person的全部字段
    id     int
    addr   string
}

func main() {
    p := Person{"mike", 'm', 18}
    p.PrintInfo()

    s := Student{Person{"yoyo", 'f', 20}, 2, "sz"}
    s.PrintInfo()
}
複製代碼

####8.3.5.2 方法的重寫

type Person struct {
    name string
    sex  byte
    age  int
}

//Person定義了方法
func (p *Person) PrintInfo() {
    fmt.Printf("Person: %s,%c,%d\n", p.name, p.sex, p.age)
}

type Student struct {
    Person // 匿名字段,那麼Student包含了Person的全部字段
    id     int
    addr   string
}

//Student定義了方法
func (s *Student) PrintInfo() {
    fmt.Printf("Student:%s,%c,%d\n", s.name, s.sex, s.age)
}

func main() {
    p := Person{"mike", 'm', 18}
    p.PrintInfo() //Person: mike,m,18

    s := Student{Person{"yoyo", 'f', 20}, 2, "sz"}
    s.PrintInfo()        //Student:yoyo,f,20
    s.Person.PrintInfo() //Person: yoyo,f,20
}
複製代碼

###8.3.6 表達式 相似於咱們能夠對函數進行賦值和傳遞同樣,方法也能夠進行賦值和傳遞。

根據調用者不一樣,方法分爲兩種表現形式:方法值和方法表達式。二者均可像普通函數那樣賦值和傳參,區別在於方法值綁定實例,⽽方法表達式則須顯式傳參。

####8.3.6.1 方法值

type Person struct {
    name string
    sex  byte
    age  int
}

func (p *Person) PrintInfoPointer() {
    fmt.Printf("%p, %v\n", p, p)
}

func (p Person) PrintInfoValue() {
    fmt.Printf("%p, %v\n", &p, p)
}

func main() {
    p := Person{"mike", 'm', 18}
    p.PrintInfoPointer() //0xc0420023e0, &{mike 109 18}

    pFunc1 := p.PrintInfoPointer //方法值,隱式傳遞 receiver
    pFunc1()                     //0xc0420023e0, &{mike 109 18}

    pFunc2 := p.PrintInfoValue
    pFunc2() //0xc042048420, {mike 109 18}
}
複製代碼

####8.3.6.2 方法表達式

type Person struct {
    name string
    sex  byte
    age  int
}

func (p *Person) PrintInfoPointer() {
    fmt.Printf("%p, %v\n", p, p)
}

func (p Person) PrintInfoValue() {
    fmt.Printf("%p, %v\n", &p, p)
}

func main() {
    p := Person{"mike", 'm', 18}
    p.PrintInfoPointer() //0xc0420023e0, &{mike 109 18}

    //方法表達式, 須顯式傳參
    //func pFunc1(p *Person))
    pFunc1 := (*Person).PrintInfoPointer
    pFunc1(&p) //0xc0420023e0, &{mike 109 18}

    pFunc2 := Person.PrintInfoValue
    pFunc2(p) //0xc042002460, {mike 109 18}
}
複製代碼

##8.4 接口 ###8.4.1 概述 在Go語言中,接口(interface)是一個自定義類型,接口類型具體描述了一系列方法的集合。

接口類型是一種抽象的類型,它不會暴露出它所表明的對象的內部值的結構和這個對象支持的基礎操做的集合,它們只會展現出它們本身的方法。所以接口類型不能將其實例化。

Go經過接口實現了鴨子類型(duck-typing):「當看到一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼這隻鳥就能夠被稱爲鴨子」。咱們並不關心對象是什麼類型,究竟是不是鴨子,只關心行爲。

###8.4.2 接口的使用 ####8.4.2.1 接口定義

type Humaner interface {
    SayHi()
}
複製代碼

接⼝命名習慣以 er 結尾 接口只有方法聲明,沒有實現,沒有數據字段 接口能夠匿名嵌入其它接口,或嵌入到結構中

####8.4.2.2 接口實現 接口是用來定義行爲的類型。這些被定義的行爲不禁接口直接實現,而是經過方法由用戶定義的類型實現,一個實現了這些方法的具體類型是這個接口類型的實例。

若是用戶定義的類型實現了某個接口類型聲明的一組方法,那麼這個用戶定義的類型的值就能夠賦給這個接口類型的值。這個賦值會把用戶定義的類型的值存入接口類型的值。

type Humaner interface {
    SayHi()
}

type Student struct { //學生
    name  string
    score float64
}

//Student實現SayHi()方法
func (s *Student) SayHi() {
    fmt.Printf("Student[%s, %f] say hi!!\n", s.name, s.score)
}

type Teacher struct { //老師
    name  string
    group string
}

//Teacher實現SayHi()方法
func (t *Teacher) SayHi() {
    fmt.Printf("Teacher[%s, %s] say hi!!\n", t.name, t.group)
}

type MyStr string

//MyStr實現SayHi()方法
func (str MyStr) SayHi() {
    fmt.Printf("MyStr[%s] say hi!!\n", str)
}

//普通函數,參數爲Humaner類型的變量i
func WhoSayHi(i Humaner) {
    i.SayHi()
}

func main() {
    s := &Student{"mike", 88.88}
    t := &Teacher{"yoyo", "Go語言"}
    var tmp MyStr = "測試"

    s.SayHi()   //Student[mike, 88.880000] say hi!!
    t.SayHi()   //Teacher[yoyo, Go語言] say hi!!
    tmp.SayHi() //MyStr[測試] say hi!!

    //多態,調用同一接口,不一樣表現
    WhoSayHi(s)   //Student[mike, 88.880000] say hi!!
    WhoSayHi(t)   //Teacher[yoyo, Go語言] say hi!!
    WhoSayHi(tmp) //MyStr[測試] say hi!!

    x := make([]Humaner, 3)
    //這三個都是不一樣類型的元素,可是他們實現了interface同一個接口
    x[0], x[1], x[2] = s, t, tmp
    for _, value := range x {
        value.SayHi()
    }
    /*
        Student[mike, 88.880000] say hi!!
        Teacher[yoyo, Go語言] say hi!!
        MyStr[測試] say hi!!
    */
}
複製代碼

經過上面的代碼,你會發現接口就是一組抽象方法的集合,它必須由其餘非接口類型實現,而不能自我實現。

###8.4.3 接口組合 ####8.4.3.1 接口嵌入 若是一個interface1做爲interface2的一個嵌入字段,那麼interface2隱式的包含了interface1裏面的方法。

type Humaner interface {
    SayHi()
}

type Personer interface {
    Humaner //這裏想寫了SayHi()同樣
    Sing(lyrics string)
}

type Student struct { //學生
    name  string
    score float64
}

//Student實現SayHi()方法
func (s *Student) SayHi() {
    fmt.Printf("Student[%s, %f] say hi!!\n", s.name, s.score)
}

//Student實現Sing()方法
func (s *Student) Sing(lyrics string) {
    fmt.Printf("Student sing[%s]!!\n", lyrics)
}

func main() {
    s := &Student{"mike", 88.88}

    var i2 Personer
    i2 = s
    i2.SayHi()     //Student[mike, 88.880000] say hi!!
    i2.Sing("學生哥") //Student sing[學生哥]!!
}
複製代碼

####8.4.3.2 接口轉換 超集接⼝對象可轉換爲⼦集接⼝,反之出錯:

type Humaner interface {
    SayHi()
}

type Personer interface {
    Humaner //這裏像寫了SayHi()同樣
    Sing(lyrics string)
}

type Student struct { //學生
    name  string
    score float64
}

//Student實現SayHi()方法
func (s *Student) SayHi() {
    fmt.Printf("Student[%s, %f] say hi!!\n", s.name, s.score)
}

//Student實現Sing()方法
func (s *Student) Sing(lyrics string) {
    fmt.Printf("Student sing[%s]!!\n", lyrics)
}

func main() {
    //var i1 Humaner = &Student{"mike", 88.88}
    //var i2 Personer = i1 //err

    //Personer爲超集,Humaner爲子集
    var i1 Personer = &Student{"mike", 88.88}
    var i2 Humaner = i1
    i2.SayHi() //Student[mike, 88.880000] say hi!!
}
複製代碼

###8.4.4 空接口 空接口(interface{})不包含任何的方法,正由於如此,全部的類型都實現了空接口,所以空接口能夠存儲任意類型的數值。它有點相似於C語言的void *類型。

var v1 interface{} = 1 // 將int類型賦值給interface{}     var v2 interface{} = "abc" // 將string類型賦值給interface{}     var v3 interface{} = &v2 // 將*interface{}類型賦值給interface{}     var v4 interface{} = struct{ X int }{1}     var v5 interface{} = &struct{ X int }{1}

當函數能夠接受任意的對象實例時,咱們會將其聲明爲interface{},最典型的例子是標準庫fmt中PrintXXX系列的函數,例如:

    func Printf(fmt string, args ...interface{})
    func Println(args ...interface{})
複製代碼

###8.4.5 類型查詢 咱們知道interface的變量裏面能夠存儲任意類型的數值(該類型實現了interface)。那麼咱們怎麼反向知道這個變量裏面實際保存了的是哪一個類型的對象呢?目前經常使用的有兩種方法: comma-ok斷言 switch測試

####8.4.5.1 comma-ok斷言 Go語言裏面有一個語法,能夠直接判斷是不是該類型的變量: value, ok = element.(T),這裏value就是變量的值,ok是一個bool類型,element是interface變量,T是斷言的類型。

若是element裏面確實存儲了T類型的數值,那麼ok返回true,不然返回false。

示例代碼:

type Element interface{}

type Person struct {
    name string
    age  int
}

func main() {
    list := make([]Element, 3)
    list[0] = 1       // an int
    list[1] = "Hello" // a string
    list[2] = Person{"mike", 18}

    for index, element := range list {
        if value, ok := element.(int); ok {
            fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
        } else if value, ok := element.(string); ok {
            fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
        } else if value, ok := element.(Person); ok {
            fmt.Printf("list[%d] is a Person and its value is [%s, %d]\n", index, value.name, value.age)
        } else {
            fmt.Printf("list[%d] is of a different type\n", index)
        }
    }

    /*  打印結果:
    list[0] is an int and its value is 1
    list[1] is a string and its value is Hello
    list[2] is a Person and its value is [mike, 18]
    */
}

8.4.5.2 switch測試
type Element interface{}

type Person struct {
    name string
    age  int
}

func main() {
    list := make([]Element, 3)
    list[0] = 1       //an int
    list[1] = "Hello" //a string
    list[2] = Person{"mike", 18}

    for index, element := range list {
        switch value := element.(type) {
        case int:
            fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
        case string:
            fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
        case Person:
            fmt.Printf("list[%d] is a Person and its value is [%s, %d]\n", index, value.name, value.age)
        default:
            fmt.Println("list[%d] is of a different type", index)
        }
    }
}
複製代碼
相關文章
相關標籤/搜索