1、Go语言接口
Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些
方法就是实现了这个接口。
/* 定义接口 */
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
method_name3 [return_type]
...
method_namen [return_type]
}
/* 定义结构体 */
type struct_name struct {
/* variables */
}
/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
/* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
/* 方法实现*/
}
实例1:
package main
import (
"fmt"
)
type Phone interface {
call()
}
type NokiaPhone struct {
}
func (nokiaPhone NokiaPhone) call() {
fmt.Println("I am Nokia, I can call you!")
}
type IPhone struct {
}
func (iPhone IPhone) call() {
fmt.Println("I am iPhone, I can call you!")
}
func main() {
var phone Phone
phone = new(NokiaPhone)
// I am Nokia, I can call you!
phone.call()
phone = new(IPhone)
// I am iPhone, I can call you!
phone.call()
}
实例2:
给接口增加参数。
package main
import (
"fmt"
)
type Man interface {
name() string
age() int
}
type Woman struct {
}
func (woman Woman) name() string {
return "Jin Yawei"
}
func (woman Woman) age() int {
return 23
}
type Men struct {
}
func (men Men) name() string {
return "liweibin"
}
func (men Men) age() int {
return 27
}
func main() {
var man Man
man = new(Woman)
// Jin Yawei
fmt.Println(man.name())
// 23
fmt.Println(man.age())
man = new(Men)
// liweibin
fmt.Println(man.name())
// 27
fmt.Println(man.age())
}
实例3:
接口方法传参,以及返回结果。
package main
import "fmt"
type Phone interface {
call(param int) string
takephoto()
}
type Huawei struct {
}
func (huawei Huawei) call(param int) string {
fmt.Println("i am Huawei, i can call you!", param)
return "damon"
}
func (huawei Huawei) takephoto() {
fmt.Println("i can take a photo for you")
}
func main() {
/*
i can take a photo for you
i am Huawei, i can call you! 50
damon
*/
var phone Phone
phone = new(Huawei)
phone.takephoto()
r := phone.call(50)
fmt.Println(r)
}
实例4:
package main
import (
"fmt"
)
//定义接口
type Phone interface {
call()
call2()
}
//一直都搞不懂这是干啥的
//原来是用来定义结构体内的数据类型的
type Phone1 struct {
id int
name string
category_id int
category_name string
}
//第一个类的第一个回调函数
func (test Phone1) call() {
fmt.Println("这是第一个类的第一个接口回调函数 结构体数据:", Phone1{id: 1, name: "浅笑"})
}
//第一个类的第二个回调函数
func (test Phone1) call2() {
fmt.Println("这是一个类的第二个接口回调函数call2", Phone1{id: 1, name: "浅笑", category_id: 4, category_name: "分类名称"})
}
//第二个结构体的数据类型
type Phone2 struct {
member_id int
member_balance float32
member_sex bool
member_nickname string
}
//第二个类的第一个回调函数
func (test2 Phone2) call() {
fmt.Println("这是第二个类的第一个接口回调函数call", Phone2{member_id: 22, member_balance: 15.23, member_sex: false, member_nickname: "浅笑18"})
}
//第二个类的第二个回调函数
func (test2 Phone2) call2() {
fmt.Println("这是第二个类的第二个接口回调函数call2", Phone2{member_id: 44, member_balance: 100, member_sex: true, member_nickname: "陈超"})
}
//开始运行
func main() {
var phone Phone
//先实例化第一个接口
phone = new(Phone1)
// 这是第一个类的第一个接口回调函数 结构体数据: {1 浅笑 0 }
// 这是一个类的第二个接口回调函数call2 {1 浅笑 4 分类名称}
phone.call()
phone.call2()
//实例化第二个接口
phone = new(Phone2)
// 这是第二个类的第一个接口回调函数call {22 15.23 false 浅笑18}
// 这是第二个类的第二个接口回调函数call2 {44 100 true 陈超}
phone.call()
phone.call2()
}
实例5:
将接口做为参数。
package main
import (
"fmt"
)
type Phone interface {
call() string
}
type Android struct {
brand string
}
type IPhone struct {
version string
}
func (android Android) call() string {
return "I am Android " + android.brand
}
func (iPhone IPhone) call() string {
return "I am iPhone " + iPhone.version
}
func printCall(p Phone) {
fmt.Println(p.call() + ", I can call you!")
}
func main() {
var vivo = Android{brand: "Vivo"}
var hw = Android{"HuaWei"}
i7 := IPhone{"7 Plus"}
ix := IPhone{"X"}
// I am Android Vivo, I can call you!
// I am Android HuaWei, I can call you!
// I am iPhone 7 Plus, I can call you!
// I am iPhone X, I can call you!
printCall(vivo)
printCall(hw)
printCall(i7)
printCall(ix)
}
实例6:
如果想要通过接口方法修改属性,需要在传入指针的结构体才行。
将接口做为参数。
package main
import (
"fmt"
)
type fruit interface {
getName() string
setName(name string)
}
type apple struct {
name string
}
//[1]
func (a *apple) getName() string {
return a.name
}
//[2]
func (a *apple) setName(name string) {
a.name = name
}
func main() {
a := apple{"红富士"}
// 红富士
fmt.Print(a.getName())
a.setName("树顶红")
// 树顶红
fmt.Print(a.getName())
}
带参数的实现:
package main
import "fmt"
type Animal interface {
eat()
}
type Cat struct {
name string
}
func (cat Cat) eat() {
fmt.Println(cat.name + "猫吃东西")
}
type Dog struct{}
func (dog Dog) eat() {
fmt.Println("狗吃东西")
}
func main() {
var animal1 Animal = Cat{"maomao"}
var animal2 Animal = Dog{}
// maomao猫吃东西
animal1.eat()
// 狗吃东西
animal2.eat()
}
组合接口:
package main
import "fmt"
type reader interface {
read() string
}
type writer interface {
write() string
}
type rw interface {
reader
writer
}
type mouse struct{}
func (m mouse) read() string {
return "mouse reading..."
}
func (m *mouse) write() string {
return "mouse writing..."
}
func main() {
var rw1 rw
// 只要有一个指针实现,则此处必须是指针
rw1 = &mouse{}
// rw1 = new(mouse)
// mouse reading...
fmt.Println(rw1.read())
// mouse writing...
fmt.Println(rw1.write())
}
interface 本质是一个指针:
package main
import "fmt"
// interface本质就是一个指针
type Reader interface {
ReadBook()
}
type Writer interface {
WriteBook()
}
type Book struct {
}
func (t *Book) ReadBook() {
fmt.Println("read a book")
}
func (t *Book) WriteBook() {
fmt.Println("write a book")
}
func main() {
// b : pair<type:Book, value:book{}地址>
b := &Book{}
// r: pair<type, value>
var r Reader
// r : pair<type:Book, value:book{}地址> pair是不变的
r = b // interface r 本质就是一个指针,所以b也要是指针类型
// read a book
r.ReadBook()
var w Writer
// w : pair<type:Book, value:book{}地址> pair是不变的
w = r.(Writer) // w和r的type一致,所以断言成功(断言就是强转?)
// write a book
w.WriteBook()
}
如下3种声明是等价的,最终的展开模式都是第3种格式:
type ReadWriter interface{
Reader
Writer
}
type Readwriter interface {
Reader
Write(p[]byte) (n int, err error)
}
type ReadWriter interface{
Read(p[]byte) (n int,err error)
Write(p []byte) (n int, err error)
}
其中:
type Reader interface{
Read(p[]byte)(n int, err error)
}
type Writer interface{
Write(p[]byte)(n int, err error)
}
接口方法调用:
package main
type Printer interface {
Print()
}
type S struct{}
func (s S) Print() {
println("print")
}
func main() {
var i Printer
// i.Print() // panic: runtime error: invalid memory address or nil pointer dereference
//必须初始化
i = S{}
// print
i.Print()
}
1.1 接口运算
已经初始化的接口类型变量a直接赋值给另一种接口变量b,要求b的方法集是a的方法集的子集,如果b的方法集不
是a的方法集的子集,此时如果直接将a赋值给接口变量(b=a),则编译器在做静态检查时会报错。此种情况下要想
确定接口变量a指向的实例是否满足接口变量b,就需要检查运行时的接口类型。
除了上面这种情景,编程过程中有时需要确认已经初始化的接口变量指向实例的具体类型是什么,也需要检查运行
时的接口类型。
Go语言提供两种语法结构来支持这两种需求,分别是类型断言和接口类型查询。
类型断言和接口类型查询在其它文章中有介绍。
package main
import "fmt"
// 接口包含两个方法
type Inter interface {
Ping()
Pang()
}
// 接口
type Anter interface {
Inter
String()
}
// 实现
type St struct {
Name string
}
func (St) Ping() {
println("ping")
}
func (*St) Pang() {
println("pang")
}
func main() {
st := &St{"andes"}
var i interface{} = st
// 判断i绑定的实例是否实现了接口类型Inter
o := i.(Inter)
o.Ping()
o.Pang()
// 如下语句会引发panic
// p := i.(Anter)
// p.String()
//判断i绑定的实例是否就是具体类型St
s := i.(*St)
fmt.Printf("name : %s\n", s.Name)
}
# 输出结果
name : andes
ping
pang
为了避免异常,可以这样写:
package main
import "fmt"
// 接口包含两个方法
type Inter interface {
Ping()
Pang()
}
// 接口
type Anter interface {
Inter
String()
}
// 实现
type St struct {
Name string
}
func (St) Ping() {
println("ping")
}
func (*St) Pang() {
println("pang")
}
func main() {
st := &St{"andes"}
var i interface{} = st
//判断i绑定的实例是否实现了接口类型Inter
if o, ok := i.(Inter); ok {
o.Ping()
o.Pang()
}
if p, ok := i.(Anter); ok {
p.String()
}
//判断i绑定的实例是否就是具体类型St
if s, ok := i.(*St); ok {
fmt.Printf("%s\n", s.Name)
}
}
# 输出结果
andes
ping
pang
1.2 空接口
最常使用的接口字面量类型就是空接口 interface{},由于空接口的方法集为空,所以任意类型都被认为实现了
空接口,任意类型的实例都可以赋值或传递给空接口,包括非命名类型的实例。
注意:非命名类型由于不能定义自己的方法,所以方法集为空,因此其类型变量除了传递给空接口,不能传递给任
何其他接口。
没有任何方法的接口,我们称之为空接口。空接口表示为 interface{}。系统中任何类型都符合空接口的要求,
空接口有点类于Java 语言中的 Object。不同之处在于,Go中的基本类型 int、float 和 string 也符合空接口。
Go的类型系统里面没有类的概念,所有的类型都是一样的身份,没有 Java 里面对基本类型的开箱和装箱操作,所
有的类型都是统一的。Go 语言的空接口有点像C语言中的 void *,只不过 void *是指针,而Go 语言的空接口
内部封装了指针而已。
空接口和泛型:
Go 语言没有泛型,如果一个函数需要接收任意类型的参数,则参数类型可以使用空接口类型,这是弥补没有泛型
的一种手段。
// 典型的就是fmt标准包里面的Fprint函数
func Fprint(w io.Writer, a ...interface{}) (n int, err error)
空接口和反射:
空接口是反射实现的基础,反射库就是将关具体的类型转换并赋值给空接口后才去处理。
空接口和nil:
空接口不是真的为空。
package main
import "fmt"
type Inter interface {
Ping()
Pang()
}
type St struct{}
func (St) Ping() {
println("ping")
}
func (*St) Pang() {
println("pang")
}
func main() {
var st *St = nil
var it Inter = st
// 0x0
fmt.Printf("%p\n", st)
// 0x0
fmt.Printf("%p\n", it)
// true
fmt.Printf("%t\n", st == nil)
// false
fmt.Printf("%t\n", it == nil)
if it != nil {
it.Pang()
// 下面的语句会导致panic
// panic: value method main.St.Ping called using nil *St pointer
// 方法转换为函数调用,第一个参数是St类型,由于*St是nil,无法获取指针所指的对象值,所以panic
// it.Ping()
}
}
# 程序输出
0x0
0x0
true
false
pang
这个程序暴露出Go语言的一点瑕疵,fmt.Printf(“%p\n”,it) 的结果是 0x0,但 it!=nil 的判断结果却是
true。空接口有两个字段,一个是实例类型,另一个是指向绑定实例的指针,只有两个都为nil时,空接口才为
nil。
1.3 接口数据结构
package main
import (
"fmt"
"unsafe"
)
func main() {
var str interface{} = "Hello World!"
p := (*struct {
tab uintptr
data uintptr
})(unsafe.Pointer(&str))
fmt.Printf("%+v\n", p)
fmt.Printf("%v\n", p.data)
}
# 程序输出
&{tab:5262144 data:5471176}
5471176