深入学习golang(5)—接口
本文内容纲要:
-接口
-概述
-接口赋值
-接口嵌套
-空接口(emptyinterface)
-类型转换(Conversions)
-Typeswitch与Typeassertions
接口
概述
如果说goroutine和channel是Go并发的两大基石,那么接口是Go语言编程中数据类型的关键。在Go语言的实际编程中,几乎所有的数据结构都围绕接口展开,接口是Go语言中所有数据结构的核心。
Go语言中的接口是一些方法的集合(methodset),它指定了对象的行为:如果它(任何数据类型)可以做这些事情,那么它就可以在这里使用。
typeReaderinterface{
Read(p[]byte)(nint,errerror)
}
typeWriterinterface{
Write(p[]byte)(nint,errerror)
}
typeCloserinterface{
Close()error
}
typeSeekerinterface{
Seek(offsetint64,whenceint)(int64,error)
}
上面的代码定义了4个接口。
假设我们在另一个地方中定义了下面这个结构体:
typeFilestruct{//...
}
func(f*File)Read(buf[]byte)(nint,errerror)
func(f*File)Write(buf[]byte)(nint,errerror)
func(f*File)Seek(offint64,whenceint)(posint64,errerror)func(f*File)Close()error
我们在实现File的时候,可能并不知道上面4个接口的存在,但不管怎样,File实现了上面所有的4个接口。我们可以将File对象赋值给上面任何一个接口。
varfile1Reader=new(File)
varfile2Writer=new(File)
varfile3Closer=new(File)
varfile4Seeker=new(File)
因为File可以做这些事情,所以,File就可以在这里使用。File在实现的时候,并不需要指定实现了哪个接口,它甚至根本不知道这4个接口的存在。这种“松耦合”的做法完全不同于传统的面向对象编程。
实际上,上面4个接口来自标准库中的iopackage。
接口赋值
我们可以将一个实现接口的对象实例赋值给接口,也可以将另外一个接口赋值给接口。
(1)通过对象实例赋值
将一个对象实例赋值给一个接口之前,要保证该对象实现了接口的所有方法。考虑如下示例:
typeIntegerint
func(aInteger)Less(bInteger)bool{
returna<b
}
func(a*Integer)Add(bInteger){
*a+=b
}
typeLessAdderinterface{
Less(bInteger)bool
Add(bInteger)
}
varaInteger=1
varb1LessAdder=&a//OK
varb2LessAdder=a//notOK
b2的赋值会报编译错误,为什么呢?还记得<类型方法>一章中讨论的Go语言规范的规定吗?
ThemethodsetofanyothernamedtypeTconsistsofallmethodswithreceivertypeT.Themethodsetofthecorrespondingpointertype*TisthesetofallmethodswithreceiverTorT(thatis,italsocontainsthemethodsetofT).
也就是说Integer实现了接口LessAdder的所有方法,而Integer只实现了Less方法,所以不能赋值。
(2)通过接口赋值
varrio.Reader=new(os.File)
varrwio.ReadWriter=r//notok
varrw2io.ReadWriter=new(os.File)
varr2io.Reader=rw2//ok
因为r没有Write方法,所以不能赋值给rw。
接口嵌套
我们来看看iopackage中的另外一个接口:
//ReadWriteristheinterfacethatgroupsthebasicReadandWritemethods.
typeReadWriterinterface{
Reader
Writer
}
该接口嵌套了io.Reader和io.Writer两个接口,实际上,它等同于下面的写法:
typeReadWriterinterface{
Read(p[]byte)(nint,errerror)
Write(p[]byte)(nint,errerror)
}
注意,Go语言中的接口不能递归嵌套,
//illegal:Badcannotembeditself
typeBadinterface{
Bad
}
//illegal:Bad1cannotembeditselfusingBad2
typeBad1interface{
Bad2
}
typeBad2interface{
Bad1
}
空接口(emptyinterface)
空接口比较特殊,它不包含任何方法:
interface{}
在Go语言中,所有其它数据类型都实现了空接口。
varv1interface{}=1
varv2interface{}="abc"
varv3interface{}=struct{Xint}{1}
如果函数打算接收任何数据类型,则可以将参考声明为interface{}。最典型的例子就是标准库fmt包中的Print和Fprint系列的函数:
funcFprint(wio.Writer,a...interface{})(nint,errerror)
funcFprintf(wio.Writer,formatstring,a...interface{})
funcFprintln(wio.Writer,a...interface{})
funcPrint(a...interface{})(nint,errerror)
funcPrintf(formatstring,a...interface{})
funcPrintln(a...interface{})(nint,errerror)
注意,[]T不能直接赋值给[]interface{}
t:=[]int{1,2,3,4}
vars[]interface{}=t
编译时会输出下面的错误:
cannotuset(type[]int)astype[]interface{}inassignment
我们必须通过下面这种方式:
t:=[]int{1,2,3,4}
s:=make([]interface{},len(t))
fori,v:=ranget{
s[i]=v
}
类型转换(Conversions)
类型转换的语法:
Conversion=Type"("Expression[","]")".
当以运算符*或者<-开始,有必要加上括号避免模糊:
*Point(p)//sameas*(Point(p))
(*Point)(p)//pisconvertedto*Point
<-chanint(c)//sameas<-(chanint(c))
(<-chanint)(c)//cisconvertedto<-chanint
func()(x)//functionsignaturefunc()x
(func())(x)//xisconvertedtofunc()
(func()int)(x)//xisconvertedtofunc()int
func()int(x)//xisconvertedtofunc()int(unambiguous)
Typeswitch与Typeassertions
在Go语言中,我们可以使用typeswitch语句查询接口变量的真实数据类型,语法如下:
switchx.(type){
//cases
}
x必须是接口类型。
来看一个详细的示例:
typeStringerinterface{
String()string
}
varvalueinterface{}//Valueprovidedbycaller.
switchstr:=value.(type){
casestring:
returnstr//typeofstrisstring
caseStringer://typeofstrisStringer
returnstr.String()
}
语句switch中的value必须是接口类型,变量str的类型为转换后的类型。
Iftheswitchdeclaresavariableintheexpression,thevariablewillhavethecorrespondingtypeineachclause.It'salsoidiomatictoreusethenameinsuchcases,ineffectdeclaringanewvariablewiththesamenamebutadifferenttypeineachcase.
如果我们只关心一种类型该如何做?如果我们知道值为一个string,只是想将它抽取出来该如何做?只有一个case的类型switch是可以的,不过也可以用类型断言(typeassertions)。类型断言接受一个接口值,从中抽取出显式指定类型的值。其语法借鉴了类型switch子句,不过是使用了显式的类型,而不是type关键字,如下:
x.(T)
同样,x必须是接口类型。
str:=value.(string)
上面的转换有一个问题,如果该值不包含一个字符串,则程序会产生一个运行时错误。为了避免这个问题,可以使用“comma,ok”的习惯用法来安全地测试值是否为一个字符串:
str,ok:=value.(string)
ifok{
fmt.Printf("stringvalueis:%q\n",str)
}else{
fmt.Printf("valueisnotastring\n")
}
如果类型断言失败,则str将依然存在,并且类型为字符串,不过其为零值,即一个空字符串。
我们可以使用类型断言来实现typeswitch的中例子:
ifstr,ok:=value.(string);ok{
returnstr
}elseifstr,ok:=value.(Stringer);ok{
returnstr.String()
}
这种做法没有多大实用价值。
本文内容总结:接口,概述,接口赋值,接口嵌套,空接口(emptyinterface),类型转换(Conversions),Typeswitch与Typeassertions,
原文链接:https://www.cnblogs.com/hustcat/p/4007126.html