GO方法與接口

Go語言沒有沿襲傳統面向對象編程中的諸多概念,好比繼承、虛函數、構造函數和析構函數、隱藏的this指針等。編程

 

方法

Go 語言中同時有函數和方法。方法就是一個包含了接受者(receiver)的函數,receiver能夠是內置類型或者結構體類型的一個值或者是一個指針。全部給定類型的方法屬於該類型的方法集。數組

以下面的這個例子,定義了一個新類型Integer,它和int同樣,只是爲它內置的int類型增長了個新方法Less()app

複製代碼
type Integer int 

func (a Integer) Less(b Integer) bool {
    return a < b 
}

func main() {
    var a Integer = 1 

    if a.Less(2) {
        fmt.Println("less then 2")
    }   
}
複製代碼

能夠看出,Go語言在自定義類型的對象中沒有C++/Java那種隱藏的this指針,而是在定義成員方法時顯式聲明瞭其所屬的對象。less

 

method的語法以下:函數

func (r ReceiverType) funcName(parameters) (results)

當調用method時,會將receiver做爲函數的第一個參數:測試

funcName(r, parameters);

因此,receiver是值類型仍是指針類型要看method的做用。若是要修改對象的值,就須要傳遞對象的指針。this

指針做爲Receiver會對實例對象的內容發生操做,而普通類型做爲Receiver僅僅是以副本做爲操做對象,並不對原實例對象發生操做。spa

複製代碼
func (a *Ingeger) Add(b Integer) {
    *a += b
}

func main() {
    var a Integer = 1 
    a.Add(3)
    fmt.Println("a =", a)     //  a = 4
}
複製代碼

若是Add方法不使用指針,則a返回的結果不變,這是由於Go語言函數的參數也是基於值傳遞。指針

注意:當方法的接受者是指針時,即便用值類型調用那麼方法內部也是對指針的操做。code

 

以前說過,Go語言沒有構造函數的概念,一般使用一個全局函數來完成。例如:

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

func main() {
    rect1 := NewRect(1,2,10,20)
    fmt.Println(rect1.width)
}
複製代碼

 

 


匿名組合

Go語言提供了繼承,可是採用了組合的語法,咱們將其稱爲匿名組合,例如:

複製代碼
type Base struct {
    name string
}

func (base *Base) Set(myname string) {
    base.name = myname
}

func (base *Base) Get() string {
    return base.name
}

type Derived struct {
    Base
    age int 
}

func (derived *Derived) Get() (nm string, ag int) {
    return derived.name, derived.age
}


func main() {
    b := &Derived{}

    b.Set("sina")
    fmt.Println(b.Get())
}
複製代碼

例子中,在Base類型定義了get()和set()兩個方法,而Derived類型繼承了Base類,並改寫了Get()方法,在Derived對象調用Set()方法,會加載基類對應的方法;而調用Get()方法時,加載派生類改寫的方法。

 

組合的類型和被組合的類型包含同名成員時, 會不會有問題呢?能夠參考下面的例子:

複製代碼
type Base struct {
    name string
    age int
}

func (base *Base) Set(myname string, myage int) {
    base.name = myname
    base.age = myage
}

type Derived struct {
    Base
    name string
}

func main() {
    b := &Derived{}

    b.Set("sina", 30)
    fmt.Println("b.name =",b.name, "\tb.Base.name =", b.Base.name)
    fmt.Println("b.age =",b.age, "\tb.Base.age =", b.Base.age)
}
複製代碼

 

 

 


值語義和引用語義

值語義和引用語義的差異在於賦值,好比

b = a
b.Modify()

若是b的修改不會影響a的值,那麼此類型屬於值類型;若是會影響a的值,那麼此類型是引用類型。

Go語言中的大多數類型都基於值語義,包括:

  • 基本類型,如byte、int、bool、float3二、string等;
  • 複合類型,如arry、struct、pointer等;

 

C語言中的數組比較特別,經過函數傳遞一個數組的時候基於引用語義,可是在結構體定義數組變量的時候基於值語義。而在Go語言中,數組和基本類型沒有區別,是很純粹的值類型,例如:

var a = [3] int{1,2,3}
var b = a
b[1]++
fmt.Println(a, b)   // [1 2 3] [1 3 3]

從結果看,b=a賦值語句是數組內容的完整複製,要想表達引用,須要用指針:

var a = [3] int{1,2,3}
var b = &a    // 引用語義
b[1]++
fmt.Println(a, b)   // [1 3 3] [1 3 3]

 

 


接口

Interface 是一組抽象方法(未具體實現的方法/僅包含方法名參數返回值的方法)的集合,若是實現了 interface 中的全部方法,即該類/對象就實現了該接口。

Interface 的聲明格式:

type interfaceName interface {  
    //方法列表  
}  

Interface 能夠被任意對象實現,一個類型/對象也能夠實現多個 interface;
interface的變量能夠持有任意實現該interface類型的對象。

 以下面的例子:

複製代碼
package main

    import "fmt"

    type Human struct {
        name string
        age int
        phone string
    }

    type Student struct {
        Human //匿名字段
        school string
        loan float32
    }

    type Employee struct {
        Human //匿名字段
        company string
        money float32
    }

    //Human實現SayHi方法
    func (h Human) SayHi() {
        fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
    }

    //Human實現Sing方法
    func (h Human) Sing(lyrics string) {
        fmt.Println("La la la la...", lyrics)
    }

    //Employee重載Human的SayHi方法
    func (e Employee) SayHi() {
        fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
            e.company, e.phone)
        }

    // Interface Men被Human,Student和Employee實現
    // 由於這三個類型都實現了這兩個方法
    type Men interface {
        SayHi()
        Sing(lyrics string)
    }

    func main() {
        mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00}
        paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100}
        sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000}
        tom := Employee{Human{"Tom", 37, "222-444-XXX"}, "Things Ltd.", 5000}

        //定義Men類型的變量i
        var i Men

        //i能存儲Student
        i = mike    
        fmt.Println("This is Mike, a Student:")
        i.SayHi()
        i.Sing("November rain")

        //i也能存儲Employee
        i = tom
        fmt.Println("This is tom, an Employee:")
        i.SayHi()
        i.Sing("Born to be wild")

        //定義了slice Men
        fmt.Println("Let's use a slice of Men and see what happens")
        x := make([]Men, 3)
        //這三個都是不一樣類型的元素,可是他們實現了interface同一個接口
        x[0], x[1], x[2] = paul, sam, mike

        for _, value := range x{
            value.SayHi()
        }
    }
複製代碼

 

空接口

空interface(interface{})不包含任何的method,正由於如此,全部的類型都實現了空interface。空interface對於描述起不到任何的做用(由於它不包含任何的method),可是空interface在咱們須要存儲任意類型的數值的時候至關有用,由於它能夠存儲任意類型的數值。它有點相似於C語言的void*類型。

複製代碼
// 定義a爲空接口
    var a interface{}
    var i int = 5
    s := "Hello world"
    // a能夠存儲任意類型的數值
    a = i
    a = s
複製代碼

 

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

 

switch測試以下:

複製代碼
type Element interface{}
type List [] Element

type Person struct {
    name string
    age int 
}

//打印
func (p Person) String() string {
    return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)"
}

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

    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\n", index, value)
            default:
                fmt.Println("list[%d] is of a different type", index)
        }   
    }   
}
複製代碼

 

若是使用Comma-ok斷言的話:

複製代碼
func main() {
    list := make(List, 3)
    list[0] = 1 // an int
    list[1] = "Hello" // a string
    list[2] = Person{"Dennis", 70}

    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\n", index, value)
        } else {
            fmt.Printf("list[%d] is of a different type\n", index)
        }
    }
}
複製代碼

 

 

嵌入接口

正如struct類型能夠包含一個匿名字段,interface也能夠嵌套另一個接口。

若是一個interface1做爲interface2的一個嵌入字段,那麼interface2隱式的包含了interface1裏面的method。

相關文章
相關標籤/搜索