for _,v:=range a {
fmt.Print(v," ")
}
fmt.Println()
return
}
func main(){
a:=[]int{2,1,3,5,4}
echoArray(a)
}
以上代码将会报错,因为对于echoArray()而言,a是interface{}类型,而不是[]int类型
所以前面代码中,将echoArray()做如下修改即可:
func echoArray(a interface{}){
b,_:=a.([]int)//通过断言实现类型转换
for _,v:=range b{
fmt.Print(v," ")
}
fmt.Println()
return
}
注意:在使用断言时最好用 (安全类型断言)
b,ok:=a.([]int)
if ok{
...
}
或
b,ok:=a.([]int) if ok{ //断言成功处理
...
}
的形式,这样能根据ok的值判断断言是否成功。
因为断言失败在编译阶段不会报错,所以很可能出现断言失败导致运行错误,而你却迟迟找不到原因(亲身经历)。
总结:“空接口类型”是动态类型的,即空接口类型的变量是任意类型的变量
通过上面的例子,我们对“空接口类型”有了了解,下面我们就对“接口之间的转换” 和 “接口和普通类型直接的转换”作讲解,来理解和接口之间的转换到底是“类型转换”还是“类型断言”
接口之间转换
Go语言中接口的类型转换有很多奇怪的特性: 有时候是隐式转换, 有时候需要类型断言。
如:2个接口类型:
type IA interface {} //空接口类型
type IB interface {Foo()} //非空接口类型
1. 空接口类型转非空接口类型
IA
要向IB
转换如何操作呢?
这个操作无法在编译期确定, 因此必然不是类型转换.。由于2者都是接口类型, 因此肯定是类型断言:
为什么说“在编译期确定”呢?通过上面的知识,我们知道 IA 是空接口类型,既然是空接口类型,说明IA对应的变量可以是任意类型,所以类型就无法确定(动态类型),所以在编译的时候,都无法确定
当然因为上面的代码中的a
是nil,
会导致a.(B)
错误。但是请注意: 这只是运行错误, 并不是编译错误!
结论:空接口类型转非空接口类型时,是"类型断言"转换
2. 非空接口类型转空接口类型
IB
要向IA
转换如何操作呢? 因为IB是确定的类型,所以这个操作可以在编译期确定(类型), 因此必然是类型转换.
前面我们说过, Go语言的接口是隐式转换的, 因此还可以省略强制转换的语句:
接口和类型之间的转换(T(x))
虽然前面看到接口之间偶尔也会有类似普通类型之间的强制强制转换语法,
但从本意上来说接口是一个特殊的类型(和普通的类型区别).
我们先定义2个和前面的IA
/IB
匹配的普通类型(底层类型一样):
type IA interface {} //空接口类型
type IB interface {Foo()} //非空接口类型
type TA int
type TB int
func (TB) Foo(
) {}
如果是TA
和TB
之间的转换, 可以参考前面的类型之间转换的例子.
我们这里重点关注 TA
/TB
和 IA
/IB
之间的转换.
普通类型向接口类型转换是隐式的(可以编译期确定, 接口的隐式转换特权):
var ta TA
var ia = ta
var tb TB
var ib = tb
接口类型向普通类型是类型断言(运行期确定):
var ia IA
var ta = ia.(TA)
var ib IB
var tb = ib.(TB)
类型断言在编译期是没有任何保障的, 错误的代码也可以编译通过:
var ta = ib.(TA)
var tb = ia.(TB)
总结:
x.(T)
检查x的动态类型是否是T,其中x必须是接口值。
- 如果T是具体类型,类型断言检查x的动态类型是否等于具体类型T。如果检查成功,类型断言返回的结果是x的动态值,其类型是T。换句话说,对接口值x断言其动态类型是具体类型T,若成功则提取出x的具体值。如果检查失败则panic。
- 如果T是接口类型,类型断言检查x的动态类型是否满足T。如果检查成功,x的动态值不会被提取,返回值是一个类型为T的接口值。换句话说,到接口类型的类型断言,改变了表达式的类型,改变了(通常是扩大了)可以访问的方法,且保护了接口值内部的动态类型和值。
- 无论T是什么类型,如果x是nil接口值,则类型断言失败。
- 类型断言到一个较少限制(较少方法)的接口类型基本是不需要的,因为这个行为和赋值一样(除了nil的情况)
- 如果我们想知道类型断言是否失败,而不是失败时触发panic,可以使用返回两个值的版本:
y, ok := x.(T)