接口是每一个语言都有的概念,接口是一种约束形式,其中只包括成员函数定义,不包含成员函数实现,一个对象通过实现不同的接口,可以灵活地完成很多任务。
创新互联专业为企业提供乐业网站建设、乐业做网站、乐业网站设计、乐业网站制作等企业网站建设、网页设计与制作、乐业企业网站模板建站服务,十载乐业做网站经验,不只是建网站,更提供有价值的思路和整体网络服务。
在Go中使用interface关键字声明一个接口:
type Shaper interface {
Area() float64
Perimeter() float64
}
如果我们直接使用fmt库进行输出,会得到什么结果呢?
func main() {
var s Shaper
fmt.Println("value of s is ", s)
fmt.Printf("type of s is %T\n", s)
}
输出:
value of s is
type of s is
在这里,引出接口的概念。接口有两种类型。接口的静态类型是接口本身,例如上述程序中的Shape。接口没有静态值,而是指向动态值。
接口类型的变量可以保存实现接口的类型的值。该类型的值成为接口的动态值,并且该类型成为接口的动态类型。
从上面的示例开始,我们可以看到零值和接口的类型为nil。这是因为,此刻,我们已声明类型Shaper的变量s,但未分配任何值。当我们使用带有接口参数的fmt包中的Println函数时,它指向接口的动态值,Printf功能中的%T语法是指动态类型的接口。实际上,接口静态类型是Shaper。
当我们使用一个类型去实现该接口后,会是什么效果。
type Rect struct {
width float64
height float64
}
func (r Rect) Area() float64 {
return r.width * r.height
}
func (r Rect) Perimeter() float64 {
return 2 * (r.width + r.height)
}
// main
func main() {
var s Shaper
fmt.Println("value of s is ", s)
fmt.Printf("type of s is %T\n", s)
s = Rect{5.0, 4.0}
r := Rect{5.0, 4.0}
fmt.Printf("type of s is %T\n", s)
fmt.Printf("value of s is %v\n", s)
fmt.Printf("area of rect is %v\n", s.Area())
fmt.Println("s == r is", s == r)
}
输出:
value of s is
type of s is
type of s is main.Rect
value of s is {5 4}
area of rect is 20
s == r is tru
可以看到此时s变成了动态类型,存储的是main.Rect,值变成了{5,4}。
有时,动态类型的接口也称为具体类型,因为当我们访问接口类型时,它会返回其底层动态值的类型,并且其静态类型保持隐藏。
我们可以在s上调用Area方法,因为接口Shaper定义了Area方法,而s的具体类型是Rect,它实现了Area方法。该方法将在接口保存的动态值上被调用。
此外,我们可以看到我们可以使用s与r进行比较,因为这两个变量都保存相同的动态类型(Rect类型的结构)和动态值{5 4}。
我们接着使用圆来实现该接口:
type Circle struct {
radius float64
}
func (c Circle) Area() float64 {
return 3.14 * c.radius * c.radius
}
func (c Circle) Perimeter() float64 {
return 2 * 3.14 * c.radius
}
// main
s = Circle{10}
fmt.Printf("type of s is %T\n", s)
fmt.Printf("value of s is %v\n", s)
fmt.Printf("area of rect is %v\n", s.Area())
此时输出:
type of s is main.Circle
value of s is {10}
area of rect is 314
这里进一步理解了接口保存的动态类型。从切片角度出发,可以说,接口也以类似的方式工作,即动态保存对底层类型的引用。
当我们删除掉Perimeter的实现,可以看到如下报错结果。
./rect.go:34:4: cannot use Rect{...} (type Rect) as type Shaper in assignment:
Rect does not implement Shaper (missing Perimeter method)
从上面的错误应该是显而易见的,为了成功实现接口,需要实现与完全签名的接口声明的所有方法。
当一个接口没有任何方法时,它被称为空接口。这由接口{}表示。因为空接口没有方法,所以所有类型都隐式地实现了这个接口。
空接口的作用之一在于:函数可以接收多个不同类型参数。
例如:fmt的Println函数。
func Println(a ...interface{}) (n int, err error)
Println是一个可变函数,它接受interface{}类型的参数。
例如:
type MyString string
func explain(i interface{}) {
fmt.Printf("type: %T, value: %v\n", i, i)
}
// main
s := MyString("hello")
explain(s)
r := Rect{1, 2}
explain(r)
输出:
type: inter.MyString, value: hello
type: inter.Rect, value: {1 2}
可以看到空接口的类型与值是动态的。
在下面的程序中,我们用Area方法创建了Shape接口,用Volume方法创建了Object接口。因为结构类型Cube实现了这两个方法,所以它实现了这两个接口。因此,我们可以将结构类型Cube的值赋给类型为Shape或Object的变量。
type IShape interface {
Area() float64
}
type Object interface {
Volume() float64
}
type Cube struct {
side float64
}
func (c Cube) Area() float64 {
return 6 * c.side * c.side
}
func (c Cube) Volume() float64 {
return c.side * c.side * c.side
}
// main
c := Cube{3}
var s IShape = c
var o Object = c
fmt.Println("area is", s.Area())
fmt.Println("Volume is", o.Volume())
这种调用是没有问题的,调用各自动态类型的方法。
那如果是这样呢?
fmt.Println("area of s of interface type IShape is", s.Volume())
fmt.Println("volume of o of interface type Object is", o.Area())
输出:
s.Volume undefined (type Shape has no field or method Volume)
o.Area undefined (type Object has no field or method Area)
这个程序无法编译,因为s的静态类型是IShape,而o的静态类型是Object。因为IShape没有定义Volume方法,Object也没有定义Area方法,所以我们得到了上面的错误。
要使其工作,我们需要以某种方式提取这些接口的动态值,这是一个立方体类型的结构体,立方体实现了这些方法。这可以使用类型断言来完成。
我们可以通过i.(Type)确定接口i的底层动态值,Go将检查i的动态类型是否与type相同,并返回可能的动态值。
var s1 IShape = Cube{3}
c1 := s1.(Cube)
fmt.Println("area of s of interface type IShape is", c1.Volume())
fmt.Println("volume of o of interface type Object is", c1.Area())
这样便可以正常工作了。
如果IShape没有存储Cube类型,且Cube没有实现IShape,那么报错:
impossible type assertion:
Cube does not implement IShape (missing Area method)
如果IShape没有存储Cube类型,且Cube实现Shape,那么报错:
panic: interface conversion: inter.IShape is nil, not inter.Cub
幸运的是,语法中还有另一个返回值:
value, ok := i.(Type)
在上面的语法中,如果i有具体的type类型或type的动态值,我们可以使用ok变量来检查。如果不是,那么ok将为假,value将为Type的零值(nil)。
此外,使用类型断言可以检查该接口的动态类型是否实现了其他接口,就像前面的IShape的动态类型是Cube,它实现了IShape、Object接口,如下例子:
vaule1, ok1 := s1.(Object)
value2, ok2 := s1.(Skin)
fmt.Printf("IShape s的动态类型值是: %v, 该动态类型是否实现了Object接口: %v\n", vaule1, ok1)
fmt.Printf("IShape s的动态类型值是: %v, 该动态类型是否实现了Skin接口: %v\n", value2, ok2)
输出:
IShape s的动态类型值是: {3}, 该动态类型是否实现了Object接口: true
IShape s的动态类型值是: , 该动态类型是否实现了Skin接口: false
类型断言不仅用于检查接口是否具有某个给定类型的具体值,而且还用于将接口类型的给定变量转换为不同的接口类型。
在前面的空接口中,我们知道将一个空接口作为函数参数,那么该函数可以接受任意类型,那如果我有一个需求是:当传递的数据类型是字符串时,要求全部变为大写,其他类型不进行操作?
针对这样的需求,我们可以采用Type Switch,即:i.(type)+switch。
func switchProcess(i interface{}) {
switch i.(type) {
case string:
fmt.Println("process string")
case int:
fmt.Println("process int")
default:
fmt.Printf("type is %T\n", i)
}
}
输出:
process int
process string
在Go中,一个接口不能实现或扩展其他接口,但我们可以通过合并两个或多个接口来创建一个新的接口。
例如:
这里使用Runner与Eater两个接口,组合成了一个新接口RunEater,该接口为Embedding interfaces。
type Runner interface {
run() string
}
type Eater interface {
eat() string
}
type RunEater interface {
Runner
Eater
}
type Dog struct {
age int
}
func (d Dog) run() string {
return "run"
}
func (d Dog) eat() string {
return "eat"
}
// main
d := Dog{10}
var re RunEater = d
var r Runner = d
var e Eater = d
fmt.Printf("RunnEater dynamic type: %T, value: %v\n", re, re)
fmt.Printf("Runn dynamic type: %T, value: %v\n", r, r)
fmt.Printf("Eater dynamic type: %T, value: %v\n", e, e)
输出:
RunnEater dynamic type: inter.Dog, value: {10}
Runn dynamic type: inter.Dog, value: {10}
Eater dynamic type: inter.Dog, value: {10}
如果基础动态值为nil,则两个接口总是相等的,这意味着两个nil接口总是相等的,因此== operation返回true。
var a, b interface{}
fmt.Println( a == b ) // true
如果这些接口不是nil,那么它们的动态类型(具体值的类型)应该相同,具体值应该相等。
如果接口的动态类型不具有可比性,例如slice、map、function,或者接口的具体值是包含这些不可比较性值的复杂数据结构,如切片或数组,则==或!=操作将导致运行时panic。
本文题目:讲解Go接口
文章URL:http://www.36103.cn/qtweb/news39/10189.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联