一、接口的定义
- 接口是一种抽象类型
- 接口定义了方法的集合,简单地概括接口的作用:不关心是什么,只关心能做什么
二、接口的声明
声明一个接口很简单,使用interface关键字,然后定义接口包含哪些方法即可
1 2 3 4
| type IF interface { functionA() functionB(string) string }
|
接口的组合,类似结构体的组合
1 2 3 4 5 6 7 8 9 10 11 12 13
| type IF interface { functionA() functionB(string) string }
type IF2 interface { functionC() }
type IF3 interface { IF IF2 }
|
三、接口的实现
只要一个对象实现了某个接口的所有方法,那么就实现了这个接口,也可以称这个变量是该接口类型。或者说 一个具体类型 是一个(is-a) 特定的接口类型。例如,
*os.File
是一个io.ReaderWriter
Example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| package main
import "fmt"
type IF interface { getName() string }
type Human struct { fName, lName string }
type Car struct { model, vendor string }
type Animal struct { Name string }
func (a Animal) getName() string { return a.Name }
func (h *Human) getName() string { return h.fName + h.lName }
func (c *Car) getName() string { return c.model + c.vendor }
func main() { ifs := []IF{} h := Human{fName: "w", lName: "e"} c := new(Car) c.model = "x5" c.vendor = "bmw" a := Animal{Name: "a cat"} ifs = append(ifs, &h) ifs = append(ifs, c) ifs = append(ifs, a) for _, v := range ifs { fmt.Println(v.getName()) } }
|
Example2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| package main
import "fmt"
type WeChatPay struct { method string cert string } type AliPay struct { method string apiKey string }
func (w *WeChatPay) doPay(amount int) { fmt.Println("使用微信支付, 通过cert处理") fmt.Println("使用", w.method, "支付金额", amount) } func (a *AliPay) doPay(amount int) { fmt.Println("使用支付宝支付, 通过apiKey处理") fmt.Println("使用", a.method, "支付金额", amount) }
type Payer interface { doPay(int) }
func Pay(p Payer, amount int) { p.doPay(amount) }
func main() {
wechat := WeChatPay{method: "微信支付", cert: "xxx.cert"} alipay := AliPay{method: "支付宝支付", apiKey: "xxxxxx"}
Pay(&wechat, 50) Pay(&alipay, 100) }
|
四、接口值的组成
从概念上来讲,接口的值分为两个部分:
- 一个具体的类型(称为动态类型)
- 该类型的值(称为动态值)
对于Go这样的静态类型语言,类型仅仅是编译时的概念,并不是一个值
对于接口,是用类型描述符存储动态类型。然后动态值部分,是对应值的一个副本,即指向该值的一个指针
1 2 3 4 5
| var w io.Writer
w = os.Stdout w = new(bytes.Buffer) w = nil
|
五、接口是可比较的
接口值是可比较的
1 2 3 4 5 6 7 8
| var w1 io.Writer var w2 io.Writer
fmt.Println(w1 == w2)
w1 = os.Stdout w2 = new(bytes.Buffer) fmt.Println(w1 == w2)
|
由于是可以比较的,因此,接口可以作为map的key,switch语句的操作数。
但是需要注意,如果动态类型一致,但是动态值本身是不可比较的,那么也无法将两个接口进行比较
1 2 3
| var x interface{} x = []int{1, 2, 3} fmt.Println(x == x)
|
六、含有空指针的非空接口
以下程序会导致panic
1 2 3 4 5 6 7 8 9 10 11 12 13
| main(){ var buf *bytes.Buffer fmt.Printf("%v\n", buf) f(buf) }
func f(out io.Writer) { if out != nil { out.Write([]byte("done\n")) } }
|
原因是,out接口的类型不为空而值为空,此时接口的值是不等于nil的。
出现panic是因为(*bytes.Buffer).Write
的接收者不能为空;正确的写法如下
1 2 3 4 5
| main(){ var buf io.Writer buf = new(bytes.Buffer) f(buf) }
|
七、接口排序
接口的排序主要是使用sort.Interface
一个内置的排序算法需要知道以下内容:
- 序列的长度
- 元素比较的结果
- 交换两个元素的方式
1 2 3 4 5 6 7 8
| package sort
type Interface interface{ Len() int Less(i, j int) bool Swap(i, j int) }
|
常用的排序方法
sort包里面默认实现了一些常用类型的排序
字符串排序
使用sort.StringSlice类型
1 2
| names := sort.StringSlice{"1", "3", "2"} sort.Sort(names)
|
或者使用更简便的sort.Strings函数
1 2
| names := []string{"1", "3", "2"} sort.Strings(names)
|
整型排序
1 2 3
| ages := []int{18, 17, 19} sort.Ints(ages)
|
浮点型排序
1 2
| scores := []float64{80.5, 75.0, 99.5} sort.Float64s(scores)
|
实现字符串切片排序实现
我们可以自己定义一个类型,来实现字符串切片的排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| package main
import ( "fmt" "sort" )
type MyStringList []string
func (m MyStringList) Len() int { return len(m) } func (m MyStringList) Less(i, j int) bool { return m[i] < m[j] } func (m MyStringList) Swap(i, j int) { m[i], m[j] = m[j], m[i] } func main() { names := []string{ "3.cx", "4.dx", "2.bx", "1.ax", "1.bx", } sort.Sort(MyStringList(names)) for _, v := range names { fmt.Printf("%s\n", v) } }
|
输出结果:
1 2 3 4 5
| 1.ax 1.bx 2.bx 3.cx 4.dx
|