深入學習golang(5)—介面

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

介面

概述

如果說goroutine和channel是Go並發的兩大基石,那麼介面是Go語言編程中資料類型的關鍵。在Go語言的實際編程中,幾乎所有的資料結構都圍繞介面展開,介面是Go語言中所有資料結構的核心。
Go語言中的介面是一些方法的集合(method set),它指定了對象的行為:如果它(任何資料類型)可以做這些事情,那麼它就可以在這裡使用。

type Reader interface {Read(p []byte) (n int, err error)}type Writer interface {Write(p []byte) (n int, err error)}type Closer interface {Close() error}type Seeker interface {Seek(offset int64, whence int) (int64, error)}

上面的代碼定義了4個介面。
假設我們在另一個地方中定義了下面這個結構體:

type File struct { // ...}func (f *File) Read(buf []byte) (n int, err error)func (f *File) Write(buf []byte) (n int, err error)func (f *File) Seek(off int64, whence int) (pos int64, err error) func (f *File) Close() error

我們在實現File的時候,可能並不知道上面4個介面的存在,但不管怎樣,File實現了上面所有的4個介面。我們可以將File對象賦值給上面任何一個介面。

var file1 Reader = new(File) var file2 Writer = new(File) var file3 Closer = new(File)var file4 Seeker = new(File)

因為File可以做這些事情,所以,File就可以在這裡使用。File在實現的時候,並不需要指定實現了哪個介面,它甚至根本不知道這4個介面的存在。這種“松耦合”的做法完全不同於傳統的物件導向編程。

實際上,上面4個介面來自標準庫中的io package。

介面賦值

我們可以將一個實現介面的對象執行個體賦值給介面,也可以將另外一個介面賦值給介面。

(1)通過對象執行個體賦值

將一個對象執行個體賦值給一個介面之前,要保證該對象實現了介面的所有方法。考慮如下樣本:

type Integer intfunc (a Integer) Less(b Integer) bool {return a < b}func (a *Integer) Add(b Integer) {*a += b}type LessAdder interface { Less(b Integer) bool Add(b Integer)}var a Integer = 1var b1 LessAdder = &a //OKvar b2 LessAdder = a   //not OK

b2的賦值會報編譯錯誤,為什麼呢?還記得<類型方法>一章中討論的Go語言規範的規定嗎?

The method set of any other named type T consists of all methods with receiver type T. The method set of the corresponding pointer type T is the set of all methods with receiver T or T (that is, it also contains the method set of T).
也就是說*Integer實現了介面LessAdder的所有方法,而Integer只實現了Less方法,所以不能賦值。

(2)通過介面賦值

        var r io.Reader = new(os.File)        var rw io.ReadWriter = r   //not ok        var rw2 io.ReadWriter = new(os.File)        var r2 io.Reader = rw2    //ok

因為r沒有Write方法,所以不能賦值給rw。

介面嵌套

我們來看看io package中的另外一個介面:

// ReadWriter is the interface that groups the basic Read and Write methods.type ReadWriter interface {ReaderWriter}

該介面嵌套了io.Reader和io.Writer兩個介面,實際上,它等同於下面的寫法:

type ReadWriter interface {Read(p []byte) (n int, err error) Write(p []byte) (n int, err error)}

注意,Go語言中的介面不能遞迴嵌套,

// illegal: Bad cannot embed itselftype Bad interface {Bad}// illegal: Bad1 cannot embed itself using Bad2type Bad1 interface {Bad2}type Bad2 interface {Bad1}

空介面(empty interface)

空介面比較特殊,它不包含任何方法:

interface{}

在Go語言中,所有其它資料類型都實現了空介面。

var v1 interface{} = 1var v2 interface{} = "abc"var v3 interface{} = struct{ X int }{1}

如果函數打算接收任何資料類型,則可以將參考聲明為interface{}。最典型的例子就是標準庫fmt包中的Print和Fprint系列的函數:

func Fprint(w io.Writer, a ...interface{}) (n int, err error) func Fprintf(w io.Writer, format string, a ...interface{})func Fprintln(w io.Writer, a ...interface{})func Print(a ...interface{}) (n int, err error)func Printf(format string, a ...interface{})func Println(a ...interface{}) (n int, err error)

注意,[]T不能直接賦值給[]interface{}

        t := []int{1, 2, 3, 4}        var s []interface{} = t

編譯時間會輸出下面的錯誤:

cannot use t (type []int) as type []interface {} in assignment

我們必須通過下面這種方式:

t := []int{1, 2, 3, 4}s := make([]interface{}, len(t))for i, v := range t {    s[i] = v}

類型轉換(Conversions)

類型轉換的文法:

Conversion = Type "(" Expression [ "," ] ")" .

當以運算子*或者<-開始,有必要加上括弧避免模糊:

*Point(p)        // same as *(Point(p))(*Point)(p)      // p is converted to *Point<-chan int(c)    // same as <-(chan int(c))(<-chan int)(c)  // c is converted to <-chan intfunc()(x)        // function signature func() x(func())(x)      // x is converted to func()(func() int)(x)  // x is converted to func() intfunc() int(x)    // x is converted to func() int (unambiguous)

Type switch與Type assertions

在Go語言中,我們可以使用type switch語句查詢介面變數的真實資料類型,文法如下:

switch x.(type) {// cases}

x必須是介面類型。

來看一個詳細的樣本:

type Stringer interface {    String() string}var value interface{} // Value provided by caller.switch str := value.(type) {case string:    return str //type of str is stringcase Stringer: //type of str is Stringer    return str.String()}

語句switch中的value必須是介面類型,變數str的類型為轉換後的類型。

If the switch declares a variable in the expression, the variable will have the corresponding type in each clause. It's also idiomatic to reuse the name in such cases, in effect declaring a new variable with the same name but a different type in each case.
如果我們只關心一種類型該如何做?如果我們知道值為一個string,只是想將它抽取出來該如何做?只有一個case的類型switch是可以的,不過也可以用類型斷言(type assertions)。類型斷言接受一個介面值,從中抽取出顯式指定類型的值。其文法借鑒了類型switch子句,不過是使用了顯式的類型,而不是type關鍵字,如下:

x.(T)

同樣,x必須是介面類型。

str := value.(string)

上面的轉換有一個問題,如果該值不包含一個字串,則程式會產生一個執行階段錯誤。為了避免這個問題,可以使用“comma, ok”的習慣用法來安全地測試值是否為一個字串:

str, ok := value.(string)if ok {    fmt.Printf("string value is: %q\n", str)} else {    fmt.Printf("value is not a string\n")}

如果類型宣告失敗,則str將依然存在,並且類型為字串,不過其為零值,即一個Null 字元串。
我們可以使用類型斷言來實現type switch的中例子:

if str, ok := value.(string); ok {    return str} else if str, ok := value.(Stringer); ok {    return str.String()}

這種做法沒有多大實用價值。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.