1. 程式人生 > >GoLang的方法與介面

GoLang的方法與介面

轉自:https://www.cnblogs.com/chenny7/p/4497969.html

方法

Go 語言中同時有函式和方法。方法就是一個包含了接受者(receiver)的函式,receiver可以是內建型別或者結構體型別的一個值或者是一個指標。所有給定型別的方法屬於該型別的方法集。

如下面的這個例子,定義了一個新型別Integer,它和int一樣,只是為它內建的int型別增加了個新方法Less()

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指標,而是在定義成員方法時顯式聲明瞭其所屬的物件。
method的語法如下:

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


當呼叫method時,會將receiver作為函式的第一個引數:

funcName(r, parameters);

所以,receiver是值型別還是指標型別要看method的作用。如果要修改物件的值,就需要傳遞物件的指標。
指標作為Receiver會對例項物件的內容發生操作,而普通型別作為Receiver僅僅是以副本作為操作物件,並不對原例項物件發生操作。

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語言函式的引數也是基於值傳遞。
注意:當方法的接受者是指標時,即使用值型別呼叫那麼方法內部也是對指標的操作。

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、float32、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。