接口是一種重要的類型,他是一組肯定的方法集合。html
一個接口變量能夠存儲任何實現了接口方法的具體值。
一個重要的例子就是io.Reader和io.Writergolang
type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte)(n int, err error) }
若是一個方法實現了io.Reader或者io.Writer裏的方法,那麼它便實現了io.Reader 或 io.Writer 。 這就less
意味着一個io.Reader 變量能夠持有任何一個實現了Read方法的類型的值 [1]ide
例 1:
var r io.Reader
r = os.Stdin
os.Stdin 在 os 包中定義:函數
var ( Stdin = NewFile(uintptr(syscall.Stdin), "/dev/stdin") Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout") )
NewFile 返回的是一個 *File 結構體,在 os/types.go, File 結構體代碼,裏面是一個匿名的file結構體ui
// File represents an open file descriptor.
type File struct { *file // os specific
}
file也是一個結構體,os/file_plan9.gospa
type file struct { fd int name string dirinfo *dirInfo // nil unless directory being read
}
這個 File 結構體實現了 Read(p []byte) (n int, err error) 方法, 而這個恰好是接口 io.Reader 裏的方法
因此os.Stdin 能夠賦值給 io.Readercode
例 2:
var r io.Reader
r = bufio.NewReader(r)htm
在包 bufio.goblog
// NewReader returns a new Reader whose buffer has the default size.
func NewReader(rd io.Reader) *Reader { return NewReaderSize(rd, defaultBufSize) }
func NewReaderSize(rd io.Reader, size int) *Reader { // Is it already a Reader?
b, ok := rd.(*Reader) if ok && len(b.buf) >= size { return b } if size < minReadBufferSize { size = minReadBufferSize } r := new(Reader) r.reset(make([]byte, size), rd) return r }
2)上面[1]中下劃線 接口變量能夠持有任何一個實現了Read方法的類型的值,那麼接口變量又是什麼呢?
變量裏是具體值和這個值類型
3)一個類型實現了 interface 中的全部方法,咱們就說該類型實現了該 interface,
因此全部的類型都實現了 empty interface,由於任何類型至少有0個方法。
go中沒有顯示的關鍵字來實現interface,只須要實現interface包含的方法便可
interface類型定義
type interfaceNamer interface { Method1(param_list) return_type Method2(param_list) return_type ... }
按照約定,只包含一個方法的)接口的名字由方法名加 [e]r 後綴組成,例如 Printer、Reader、Writer、Logger、Converter 等等。還有一些不經常使用的方式(當後綴 er 不合適時),好比 Recoverable,此時接口名以 able 結尾,或者以 I 開頭(像 .NET 或 Java 中那樣)
接口能夠屏蔽內部細節,和具體的實現方法。只關注我須要作什麼,而不關注怎麼作
示例:
package main import ( "fmt" ) type Ager interface { Get() int Set(int) } type Student struct { Age int } func (s Student) Get() int { return s.Age } func (s *Student) Set(age int) { s.Age = age } func funone(ager Ager) { ager.Set(10) fmt.Println(ager.Get()) } func main() { //=== 第一部分 stu 能夠做爲一個變量傳遞給函數,由於它做爲一個struct實現了接口裏面的方法
stu := Student{} funone(&stu) //=== 第二部分 i 做爲一個接口變量,它存儲了實現了這個接口方法的類型的值
var i Ager i = &stu fmt.Println(i.Get()) }
在上面程序中,定義來一個名爲Ager的 interface 接口,裏面定義了2個方法Get() , Set(), 定義了一個 student 的結構體,而且結構體實現了 interface 裏面的2個方法Get,Set,咱們就說結構體實現了 這個接口
定義了一個 funcone 的函數,裏面參數ager是一個interface類型(interface類型做爲函數參數),那麼實現了這個接口的任何變量均可以做爲參數傳遞給 funcone 了, (在 main 函數中 第一部分註釋)
interface變量存儲了實現者變量的值 (main函數中的 第二部分註釋),因此它又能夠調用結構體的方法
一個類型實現了接口裏的全部方法,那麼這個類型就實現了這個接口
一個接口能夠包含一個或者多個其餘接口
至關於直接將這些內嵌接口的方法加入到了外層接口中,至關於組合了方法
仍是拿最典型的 Reader 和 Writer 接口
type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } type ReadWriter interface { Reader Writer }
若是一個類型既實現了 Reader 接口,也實現了Writer接口,那麼它就自動實現了 ReadWriter 接口
接口和結構體的定義很類似,也能夠完成嵌入接口的功能,嵌入的匿名的接口,能夠自動的具有被嵌入的接口的方法
interface{} 空interface,它能存儲任意類型的值, 它至關於 c 系的 void * 類型
定義一個空接口
var i interface{} s := "hello" num : 10 i = s i = num
空接口在打印包中的應用,它又是一個可變長參數 fmt/print.go
func Println(a ...interface{}) (n int, err error) { return Fprintln(os.Stdout, a...) }
既然接口類型可以實現多種類型的值,那麼咱們應該有一種方式來檢測這種動態類型的值,即運行時變量中存儲的值的實際類型。 在執行過程當中動態類型可能會有所不一樣,可是它老是能夠分配給接口變量自己的類型。
那就是 類型斷言
1):type-ok 形式
1.1:第一種
直接判斷是不是某類型的變量 value,ok = element.(T), 這裏value是變量的值,ok是bool類型, element必須是一個 interface 變量, T是斷言的類型
1.2 第二種 簡單形式:
value := element.(T)
示例:
package main import ( "fmt"
"math" ) type Square struct { side float32 } type Circle struct { radius float32 } type Shaper interface { Area() float32 } func main() { var area Shaper sq1 := new(Square) sq1.side = 5 area = sq1 //is Square the type of area
if t, ok := area.(*Square); ok { fmt.Printf("The type of area is: %T \n", t) } if u, ok := area.(*Circle); ok { fmt.Printf("The type of area is: %T \n", u) } else { fmt.Println("area does not cotain a variable of type Cicle") } //輸出結果 // go run assert1.go // The type of area is: *main.Square // area does not cotain a variable of type Cicle
} func (sq *Square) Area() float32 { return sq.side * sq.side } func (ci *Circle) Area() float32 { return ci.radius * ci.radius * math.Pi }
2):switch 形式
上面也能夠用 switch的形式
switch t := area.(type) { case *Square: fmt.Printf("Type Square %T with value %v\n", t, t) case *Circle: fmt.Printf("Type Circle %T with value %v\n", t, t) case nil: fmt.Printf("nil value: nothing to check?\n") default: fmt.Printf("Unexpected type %T\n", t) }
參考:
1. http://wiki.jikexueyuan.com/project/the-way-to-go/11.4.html
2. https://research.swtch.com/interfaces
3. https://blog.golang.org/laws-of-reflection