[TOC]
# 接口
在go中,接口是一個自定義類型
接口類型是一個抽象類型,他不會暴露他代表的對象的內部值的結構和這個對象支持的基礎操作的集合,他們只會展示出自己的方法.**因此接口類型不能將他實例化**
**定義**
~~~
type Humaner interface {
sayHi()
}
~~~
* 接口命名習慣以er結尾
* 接口只有方法聲明,沒有實現,沒有數據字段
* 接口可以匿名嵌入其他接口,或嵌入到結構中
**實現**
~~~
//定義接口類型
type Humaner interface {
sayHi()
}
type Student struct {
name string
id int
}
//Student類型實現了這個方法
func (tmp *Student) sayHi() {
fmt.Println(*tmp)
}
func main() {
//定義接口類型的變量
var i Humaner
//只要實現了此接口方法的類型,那么這個類型的變量(接收者類型)就可以給i賦值
s := &Student{"Mike", 666}
//引用賦值
i= s
//調用實現者的方法
i.sayHi()
}
~~~
## 多態
~~~
//定義接口類型
type Humaner interface {
sayHi()
}
type Student struct {
name string
id int
}
//Student類型實現了這個方法
func (tmp *Student) sayHi() {
fmt.Println(*tmp)
}
type Mystr string
//MyStr實現了這個方法
func (tmp *Mystr) sayHi() {
fmt.Println(*tmp)
}
//定義一個普通函數,函數的參數為接口類型
//只有一個函數,缺有不同表現
func WhoSayHi(i Humaner) {
i.sayHi()
}
func main() {
s := &Student{"Mike", 666}
t := &Student{"sds", 6556}
var str Mystr = "HELLO"
//要傳地址
WhoSayHi(s)
WhoSayHi(t)
WhoSayHi(&str)
}
~~~
## 接口的繼承
~~~
//定義接口類型
type Humaner interface { //子集
sayHi()
}
type Person interface { //超集
Humaner //繼承了sayHi()
sing(lrc string)
}
type Student struct {
name string
id int
}
//Student類型實現了這個方法
func (tmp *Student) sayHi() {
fmt.Println(*tmp)
}
func (tmp *Student) sing(lrc string) {
fmt.Println(*tmp)
}
func main() {
//定義一個接口類型的變量
var i Person
s := &Student{"mike", 666}
i = s
i.sayHi() //繼承過來的方法
i.sing("abc")
}
~~~
## 接口轉換
超級可以轉換為子集,反過來不可以
~~~
func main() {
//定義一個接口類型的變量
var i Person
var h Humaner
i = h //不可以
}
~~~
## 空接口
空接口(interface{})不包含任何方法,所有類型都實現了空接口,因此空接口可以存儲任意類型的數值.有點類似于c語言的`void *`類型
~~~
//將int類型賦值給interface{}
var v1 interface{} = 1
//將string類型賦值給interface{}
var v2 interface{} = "abc"
//將*interface{]類型賦值給interface{}
var v3 interface{} = &v2
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{})
~~~
~~~
func main() {
var i interface{} = 1
fmt.Println(i)
}
~~~
## 動態類型
對于任何數據類型,只要它的方法集合中完全包含了一個接口的全部特征(即全部的方法),那么它就一定是這個接口的實現類型。比如下面這樣:
~~~
type Pet interface {
SetName(name string)
Name() string
Category() string
}
~~~
怎樣判定一個數據類型的某一個方法實現的就是某個接口類型中的某個方法呢?
這有兩個充分必要條件,一個是“兩個方法的簽名需要完全一致”,另一個是“兩個方法的名稱要一模一樣”。顯然,這比判斷一個函數是否實現了某個函數類型要更加嚴格一些。
如果你查閱了上篇文章附帶的最后一個示例的話,那么就一定會知道,雖然結構體類型Cat不是Pet接口的實現類型,但它的指針類型\*Cat卻是這個的實現類型。
我聲明的類型Dog附帶了 3 個方法。其中有 2 個值方法,分別是Name和Category,另外還有一個指針方法SetName。
這就意味著,Dog類型本身的方法集合中只包含了 2 個方法,也就是所有的值方法。而它的指針類型\*Dog方法集合卻包含了 3 個方法,
也就是說,它擁有Dog類型附帶的所有值方法和指針方法。又由于這 3 個方法恰恰分別是Pet接口中某個方法的實現,所以\*Dog類型就成為了Pet接口的實現類型。
~~~
dog := Dog{"little pig"}
var pet Pet = &dog
~~~
正因為如此,我可以聲明并初始化一個Dog類型的變量dog,然后把它的指針值賦給類型為Pet的變量pet。
這里有幾個名詞需要你先記住。對于一個接口類型的變量來說,例如上面的變量pet,我們賦給它的值可以被叫做它的實際值(也稱動態值),而該值的類型可以被叫做這個變量的實際類型(也稱動態類型)。
比如,我們把取址表達式&dog的結果值賦給了變量pet,這時這個結果值就是變量pet的動態值,而此結果值的類型\*Dog就是該變量的動態類型。
動態類型這個叫法是相對于靜態類型而言的。對于變量pet來講,它的靜態類型就是Pet,并且永遠是Pet,但是它的動態類型卻會隨著我們賦給它的動態值而變化。
比如,只有我把一個\*Dog類型的值賦給變量pet之后,該變量的動態類型才會是\*Dog。如果還有一個Pet接口的實現類型\*Fish,并且我又把一個此類型的值賦給了pet,那么它的動態類型就會變為\*Fish。
還有,在我們給一個接口類型的變量賦予實際的值之前,它的動態類型是不存在的
~~~
type Pet interface {
SetName(name string)
Name() string
Category() string
}
type Dog struct {
name string
}
func (dog *Dog) SetName(name string) {
dog.name = name
}
func (dog *Dog) Name() string {
return dog.name
}
func (dog *Dog) Category() string {
return "dog"
}
func main() {
// 示例1
dog := Dog{"little pig"}
_, ok := interface{}(dog).(Pet)
fmt.Printf("Dog是接口Pet的實現類型嗎: %v\n", ok)
_, ok = interface{}(&dog).(Pet)
fmt.Printf("*Dog是接口Pet的實現類型嗎: %v\n", ok)
fmt.Println()
// 示例2
var pet Pet = &dog
fmt.Printf("This pet is a %s, the name is %q.\n", pet.Category(), pet.Name())
}
~~~
## 實現規則
接口變量的值并不等同于這個可被稱為動態值的副本。它會包含兩個指針,一個指針指向動態值,一個指針指向類型信息
規則一:如果使用指針方法來實現一個接口,那么只有指向那個類型的指針才能夠實現對應的接口。
規則二:如果使用值方法來實現一個接口,那么那個類型的值和指針都能夠實現對應的接口
~~~
type Pet interface {
SetName(name string)
Name() string
Category() string
}
type Dog struct {
name string // 名字。
}
func (dog *Dog) SetName(name string) {
dog.name = name
}
func (dog Dog) Name() string {
return dog.name
}
func (dog Dog) Category() string {
return "dog"
}
func main() {
// 示例1。
dog := Dog{"little pig"}
_, ok := interface{}(dog).(Pet)
fmt.Printf("Dog implements interface Pet: %v\n", ok)
_, ok = interface{}(&dog).(Pet)
fmt.Printf("*Dog implements interface Pet: %v\n", ok)
fmt.Println()
// 示例2。
var pet Pet = &dog
fmt.Printf("This pet is a %s, the name is %q.\n",
pet.Category(), pet.Name())
}
~~~
## Stringer
在fmt包里有一個interface叫做Stringer:
~~~
type Stringer interface {
String() string
}
~~~
作用:fmt.Println或打印一個變量的值的時候,會判斷這個變量是否實現了Stringer接口,如果實現了,則調用這個變量的String()方法,并將返回值打印到屏幕上
fmt.Printf的`%v`也會讀取Stringer
例子:
~~~
package main
import "fmt"
type Person struct {
Name string
Age int
}
func (p Person) String() string {
return fmt.Sprintf("(Name: %v) (Age: %v)", p.Name, p.Age)
}
func main() {
a := Person{"benz", 21}
fmt.Println(a)
fmt.Printf("%v\n", a)
}
~~~
輸出
~~~
(Name: benz) (Age: 21)
(Name: benz) (Age: 21)
~~~
可以看出,用String()修改了輸出格式
再舉個例子:
~~~
package main
import "fmt"
type IPAddr [4]byte
/*
func (ip IPAddr) String() string {
return fmt.Sprintf("%v.%v.%v.%v", ip[0], ip[1], ip[2], ip[3])
}
*/
func main() {
hosts := map[string]IPAddr{
"loopback": {127, 0, 0, 1},
"googleDNS": {8, 8, 8, 8},
}
for name, ip := range hosts {
fmt.Printf("%v: %v\n", name, ip)
}
}
~~~
輸出
~~~
loopback: [127 0 0 1]
googleDNS: [8 8 8 8]
~~~
如果把上面的String的注釋去掉,輸出則是
~~~
loopback: 127.0.0.1
googleDNS: 8.8.8.8
~~~
## type switch
## **對接口變量hold值做類型判斷**[?](https://cyent.github.io/golang/method/interface_typeswitch/#hold "Permanent link")
* * *
當i是個接口變量的時候,可以用i.(type)來對這個接口變量hold的值類型做判斷
~~~
switch v := i.(type) {
case int:
...
case string:
...
default:
...
}
~~~
注意:之前類型斷言是用i.($TYPE)比如i.(int)來判斷是不是int類型,但是這里用關鍵字type。關鍵字type只能用在switch語句里,如果用在switch外面會報錯,比如:
~~~
a := i.(type)
fmt.Printf("%v %T\n", a, a)
~~~
報錯:
~~~
use of .(type) outside type switch
~~~
除了能識別內置類型,也可以識別其他類型,比如函數類型,或者帶指針的struct,比如這個例子是帶指針的struct
~~~
type Foo interface {
foo() int
}
type MyStruct struct {
X, Y int
}
func (a *MyStruct) foo() int {
return a.X + a.Y
}
func main() {
var f Foo
s := MyStruct{3, 4}
f = &s
fmt.Printf("%v,%T\n", f, f)
switch v := f.(type) {
case *MyStruct:
fmt.Printf("1,%v,%T\n", v, v)
default:
fmt.Printf("2,%v,%T\n", v, v)
}
}
~~~
輸出
~~~
&{3 4},*main.MyStruct
1,&{3 4},*main.MyStruct
~~~
注意:這個type用%T顯示出來的是\*main.MyStruct,而case里是\*MyStruct,沒有main喔。
這個例子是函數:
~~~
func main() {
pos := func () int { return 1 }
fmt.Printf("%v,%T\n", pos, pos)
var i interface{}
i = pos
fmt.Printf("%v,%T\n", i, i)
switch v := i.(type) {
case int:
fmt.Printf("1,%v,%T\n", v, v)
case func() int:
fmt.Printf("2,%v,%T\n", v, v)
case func(int) int:
fmt.Printf("3,%v,%T\n", v, v)
default:
fmt.Printf("4,%v,%T\n", v, v)
}
}
~~~
輸出:
~~~
0x1088f30,func() int
0x1088f30,func() int
2,0x1088f30,func() int
~~~
可以看出:case后面跟的就是i值的類型
注意:case后面不能跟不存在的自定義類型,比如:
~~~
func main() {
var i interface{}
i = 1
fmt.Printf("%v,%T\n", i, i)
switch v := i.(type) {
case int:
fmt.Printf("1,%v,%T\n", v, v)
case func() int:
fmt.Printf("2,%v,%T\n", v, v)
case func(int) int:
fmt.Printf("3,%v,%T\n", v, v)
case *MyStruct:
fmt.Printf("4,%v,%T\n", v, v)
default:
fmt.Printf("5,%v,%T\n", v, v)
}
}
~~~
報錯:
~~~
undefined: MyStruct
~~~
## **可以用是否實現接口做判斷**[?](https://cyent.github.io/golang/method/interface_typeswitch/#_1 "Permanent link")
* * *
~~~
package main
import "fmt"
type Adder interface {
Add()
}
type MyStruct struct {
X, Y int
}
func (this MyStruct) Add() {
fmt.Println(this.X + this.Y)
}
func main() {
s := MyStruct{3, 4}
//var i interface{} = s
var i Adder = s
switch v := i.(type) {
case MyStruct:
fmt.Printf("case MyStruct: %T %v\n", v, v)
case interface{}:
fmt.Printf("case interface{}: %T %v\n", v, v)
case Adder:
fmt.Printf("case Adder: %T %v\n", v, v)
default:
fmt.Printf("not case: %T %v\n", v, v)
}
}
~~~
輸出
~~~
case MyStruct: main.MyStruct {3 4}
~~~
另外上面
1. `var i interface{} = s`或`var i Adder = s`用哪個都一樣
2. 這3個case不管用哪個,第一個case都能匹配到
Warning
case只能用于類型判斷(包括實現接口),沒有辦法判斷是否為一個接口。就是說沒有辦法判斷一個變量是否為接口變量,即使用反射也是無法判斷的
- 基礎
- 簡介
- 主要特征
- 變量和常量
- 編碼轉換
- 數組
- byte與rune
- big
- sort接口
- 和mysql類型對應
- 函數
- 閉包
- 工作區
- 復合類型
- 指針
- 切片
- map
- 結構體
- sync.Map
- 隨機數
- 面向對象
- 匿名組合
- 方法
- 接口
- 權限
- 類型查詢
- 異常處理
- error
- panic
- recover
- 自定義錯誤
- 字符串處理
- 正則表達式
- json
- 文件操作
- os
- 文件讀寫
- 目錄
- bufio
- ioutil
- gob
- 棧幀的內存布局
- shell
- 時間處理
- time詳情
- time使用
- new和make的區別
- container
- list
- heap
- ring
- 測試
- 單元測試
- Mock依賴
- delve
- 命令
- TestMain
- path和filepath包
- log日志
- 反射
- 詳解
- plugin包
- 信號
- goto
- 協程
- 簡介
- 創建
- 協程退出
- runtime
- channel
- select
- 死鎖
- 互斥鎖
- 讀寫鎖
- 條件變量
- 嵌套
- 計算單個協程占用內存
- 執行規則
- 原子操作
- WaitGroup
- 定時器
- 對象池
- sync.once
- 網絡編程
- 分層模型
- socket
- tcp
- udp
- 服務端
- 客戶端
- 并發服務器
- Http
- 簡介
- http服務器
- http客戶端
- 爬蟲
- 平滑重啟
- context
- httptest
- 優雅中止
- web服務平滑重啟
- beego
- 安裝
- 路由器
- orm
- 單表增刪改查
- 多級表
- orm使用
- 高級查詢
- 關系查詢
- SQL查詢
- 元數據二次定義
- 控制器
- 參數解析
- 過濾器
- 數據輸出
- 表單數據驗證
- 錯誤處理
- 日志
- 模塊
- cache
- task
- 調試模塊
- config
- 部署
- 一些包
- gjson
- goredis
- collection
- sjson
- redigo
- aliyunoss
- 密碼
- 對稱加密
- 非對稱加密
- 單向散列函數
- 消息認證
- 數字簽名
- mysql優化
- 常見錯誤
- go run的錯誤
- 新手常見錯誤
- 中級錯誤
- 高級錯誤
- 常用工具
- 協程-泄露
- go env
- gometalinter代碼檢查
- go build
- go clean
- go test
- 包管理器
- go mod
- gopm
- go fmt
- pprof
- 提高編譯
- go get
- 代理
- 其他的知識
- go內存對齊
- 細節總結
- nginx路由匹配
- 一些博客
- redis為什么快
- cpu高速緩存
- 常用命令
- Go 永久阻塞的方法
- 常用技巧
- 密碼加密解密
- for 循環迭代變量
- 備注
- 垃圾回收
- 協程和纖程
- tar-gz
- 紅包算法
- 解決golang.org/x 下載失敗
- 逃逸分析
- docker
- 鏡像
- 容器
- 數據卷
- 網絡管理
- 網絡模式
- dockerfile
- docker-composer
- 微服務
- protoBuf
- GRPC
- tls
- consul
- micro
- crontab
- shell調用
- gorhill/cronexpr
- raft
- go操作etcd
- mongodb