这篇文章给大家分享的是有关Go接口怎么用的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。

接口是一种抽象类型,是对其他类型行为的概括与抽象,从语法角度来看,接口是一组方法定义的集合。很多面向对象的语言都有接口这个概念,但Go语言接口的独特之处在于它是隐式实现。

1.接口

在Go中使用interface关键字声明一个接口:

typeShaperinterface{Area()float64Perimeter()float64}

如果我们直接使用fmt库进行输出,会得到什么结果呢?

funcmain(){varsShaperfmt.Println("valueofsis",s)fmt.Printf("typeofsis%T\n",s)}

输出:

valueofsistypeofsis

在这里,引出接口的概念。接口有两种类型。接口的静态类型是接口本身,例如上述程序中的Shape。接口没有静态值,而是指向动态值。

接口类型的变量可以保存实现接口的类型的值。该类型的值成为接口的动态值,并且该类型成为接口的动态类型。

从上面的示例开始,我们可以看到零值和接口的类型为nil。这是因为,此刻,我们已声明类型Shaper的变量s,但未分配任何值。当我们使用带有接口参数的fmt包中的Println函数时,它指向接口的动态值,Printf功能中的%T语法是指动态类型的接口。实际上,接口静态类型是Shaper。

当我们使用一个类型去实现该接口后,会是什么效果。

typeRectstruct{widthfloat64heightfloat64}func(rRect)Area()float64{returnr.width*r.height}func(rRect)Perimeter()float64{return2*(r.width+r.height)}//mainfuncmain(){varsShaperfmt.Println("valueofsis",s)fmt.Printf("typeofsis%T\n",s)s=Rect{5.0,4.0}r:=Rect{5.0,4.0}fmt.Printf("typeofsis%T\n",s)fmt.Printf("valueofsis%v\n",s)fmt.Printf("areaofrectis%v\n",s.Area())fmt.Println("s==ris",s==r)}

输出:

valueofsistypeofsistypeofsismain.Rectvalueofsis{54}areaofrectis20s==ristru

可以看到此时s变成了动态类型,存储的是main.Rect,值变成了{5,4}。

有时,动态类型的接口也称为具体类型,因为当我们访问接口类型时,它会返回其底层动态值的类型,并且其静态类型保持隐藏。

我们可以在s上调用Area方法,因为接口Shaper定义了Area方法,而s的具体类型是Rect,它实现了Area方法。该方法将在接口保存的动态值上被调用。

此外,我们可以看到我们可以使用s与r进行比较,因为这两个变量都保存相同的动态类型(Rect类型的结构)和动态值{5 4}。

我们接着使用圆来实现该接口:

typeCirclestruct{radiusfloat64}func(cCircle)Area()float64{return3.14*c.radius*c.radius}func(cCircle)Perimeter()float64{return2*3.14*c.radius}//mains=Circle{10}fmt.Printf("typeofsis%T\n",s)fmt.Printf("valueofsis%v\n",s)fmt.Printf("areaofrectis%v\n",s.Area())

此时输出:

typeofsismain.Circlevalueofsis{10}areaofrectis314

这里进一步理解了接口保存的动态类型。从切片角度出发,可以说,接口也以类似的方式工作,即动态保存对底层类型的引用。

当我们删除掉Perimeter的实现,可以看到如下报错结果。

./rect.go:34:4:cannotuseRect{...}(typeRect)astypeShaperinassignment:RectdoesnotimplementShaper(missingPerimetermethod)

从上面的错误应该是显而易见的,为了成功实现接口,需要实现与完全签名的接口声明的所有方法。

2.空接口

当一个接口没有任何方法时,它被称为空接口。这由接口{}表示。因为空接口没有方法,所以所有类型都隐式地实现了这个接口。

空接口的作用之一在于:函数可以接收多个不同类型参数。

例如:fmt的Println函数。

funcPrintln(a...interface{})(nint,errerror)Println是一个可变函数,它接受interface{}类型的参数。

例如:

typeMyStringstringfuncexplain(iinterface{}){fmt.Printf("type:%T,value:%v\n",i,i)}//mains:=MyString("hello")explain(s)r:=Rect{1,2}explain(r)

输出:

type:inter.MyString,value:hellotype:inter.Rect,value:{12}

可以看到空接口的类型与值是动态的。

3.多个接口

在下面的程序中,我们用Area方法创建了Shape接口,用Volume方法创建了Object接口。因为结构类型Cube实现了这两个方法,所以它实现了这两个接口。因此,我们可以将结构类型Cube的值赋给类型为Shape或Object的变量。

typeIShapeinterface{Area()float64}typeObjectinterface{Volume()float64}typeCubestruct{sidefloat64}func(cCube)Area()float64{return6*c.side*c.side}func(cCube)Volume()float64{returnc.side*c.side*c.side}//mainc:=Cube{3}varsIShape=cvaroObject=cfmt.Println("areais",s.Area())fmt.Println("Volumeis",o.Volume())

这种调用是没有问题的,调用各自动态类型的方法。

那如果是这样呢?

fmt.Println("areaofsofinterfacetypeIShapeis",s.Volume())fmt.Println("volumeofoofinterfacetypeObjectis",o.Area())

输出:

s.Volumeundefined(typeShapehasnofieldormethodVolume)o.Areaundefined(typeObjecthasnofieldormethodArea)

这个程序无法编译,因为s的静态类型是IShape,而o的静态类型是Object。因为IShape没有定义Volume方法,Object也没有定义Area方法,所以我们得到了上面的错误。

要使其工作,我们需要以某种方式提取这些接口的动态值,这是一个立方体类型的结构体,立方体实现了这些方法。这可以使用类型断言来完成。

4.类型断言

我们可以通过i.(Type)确定接口i的底层动态值,Go将检查i的动态类型是否与type相同,并返回可能的动态值。

vars1IShape=Cube{3}c1:=s1.(Cube)fmt.Println("areaofsofinterfacetypeIShapeis",c1.Volume())fmt.Println("volumeofoofinterfacetypeObjectis",c1.Area())

这样便可以正常工作了。

如果IShape没有存储Cube类型,且Cube没有实现IShape,那么报错:

impossibletypeassertion:CubedoesnotimplementIShape(missingAreamethod)

如果IShape没有存储Cube类型,且Cube实现Shape,那么报错:

panic:interfaceconversion:inter.IShapeisnil,notinter.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("IShapes的动态类型值是:%v,该动态类型是否实现了Object接口:%v\n",vaule1,ok1)fmt.Printf("IShapes的动态类型值是:%v,该动态类型是否实现了Skin接口:%v\n",value2,ok2)

输出:

IShapes的动态类型值是:{3},该动态类型是否实现了Object接口:trueIShapes的动态类型值是:,该动态类型是否实现了Skin接口:false

类型断言不仅用于检查接口是否具有某个给定类型的具体值,而且还用于将接口类型的给定变量转换为不同的接口类型。

5.类型Switch

在前面的空接口中,我们知道将一个空接口作为函数参数,那么该函数可以接受任意类型,那如果我有一个需求是:当传递的数据类型是字符串时,要求全部变为大写,其他类型不进行操作?

针对这样的需求,我们可以采用Type Switch,即:i.(type)+switch。

funcswitchProcess(iinterface{}){switchi.(type){casestring:fmt.Println("processstring")caseint:fmt.Println("processint")default:fmt.Printf("typeis%T\n",i)}}

输出:

processintprocessstring6.嵌入接口

在Go中,一个接口不能实现或扩展其他接口,但我们可以通过合并两个或多个接口来创建一个新的接口。

例如:

这里使用Runner与Eater两个接口,组合成了一个新接口RunEater,该接口为Embedding interfaces。

typeRunnerinterface{run()string}typeEaterinterface{eat()string}typeRunEaterinterface{RunnerEater}typeDogstruct{ageint}func(dDog)run()string{return"run"}func(dDog)eat()string{return"eat"}//maind:=Dog{10}varreRunEater=dvarrRunner=dvareEater=dfmt.Printf("RunnEaterdynamictype:%T,value:%v\n",re,re)fmt.Printf("Runndynamictype:%T,value:%v\n",r,r)fmt.Printf("Eaterdynamictype:%T,value:%v\n",e,e)

输出:

RunnEaterdynamictype:inter.Dog,value:{10}Runndynamictype:inter.Dog,value:{10}Eaterdynamictype:inter.Dog,value:{10}7.接口比较

如果基础动态值为nil,则两个接口总是相等的,这意味着两个nil接口总是相等的,因此== operation返回true。

vara,binterface{}fmt.Println(a==b)//true

如果这些接口不是nil,那么它们的动态类型(具体值的类型)应该相同,具体值应该相等。

如果接口的动态类型不具有可比性,例如slice、map、function,或者接口的具体值是包含这些不可比较性值的复杂数据结构,如切片或数组,则==或!=操作将导致运行时panic。

感谢各位的阅读!关于“Go接口怎么用”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,让大家可以学到更多知识,如果觉得文章不错,可以把它分享出去让更多的人看到吧!