在这篇文章中,我将继续讨论接口类型,特别是空接口,如何处理nil接口,以及类型断言和类型开关。
接口 值里有什么?
一个接口值的内部定义是由2个值组成的,一个元组。
- 一个值和。
- 一个具体的类型。
例如,下面的代码。
type Interface interface {
Method()
}
type String struct {
Value string
}
func (s *String) Method() {}
type Integer int
func (i Integer) Method() {}
定义了3种类型。
Interface这是一个接口类型,和- 2个类型,
String和Integer,它们实现了接口类型Interface,因为它们都定义了一个叫做Method的方法。
当我们声明一个类型为Interface 的变量时,该值的内部表示将根据分配给它的内容而改变。
var iface Interface
iface = &String{"hello world"}
fmt.Printf("Value: %v, Type: %T\n", iface, iface)
iface = Integer(100)
fmt.Printf("Value: %v, Type: %T\n", iface, iface)
代码将打印出以下内容,表明内部存储在变量iface 的值。
Value: &{hello world}, Type: *main.String
Value: 100, Type: main.Integer
在我们需要将这些变量与nil 进行比较的情况下,了解Interface Values 的工作原理是很重要的。当与nil 进行比较时,值和具体类型都必须是nil ,才能认为是真的。
这在官方的 "常见问题 "中有所涉及,可以用下面的片段来解释。
func main() {
iface := AlwaysNonNil()
fmt.Printf("Value: %v, Type: %T, Is nil? %t\n", iface, iface, iface == nil)
}
func AlwaysNonNil() Interface {
var ret *String
return ret
}
它打印出以下内容。
Value: <nil>, Type: *main.String, Is nil? false
这是因为AlwaysNonNil 中的返回值被分配给了一个具体的类型,在这种情况下是*String ,但是返回的类型使用的是接口类型,因此返回值中的那个字段已经被分配了,这导致了它很难与nil 进行比较。
这个问题的一个更现实的例子是FAQ中提到的返回错误时的例子。
func returnsError() error {
var err *MyError
// some other code
return ret
}
什么是空接口(interface{})?
空接口,写成interface{} ,是一个接口类型。
- 它指定的方法为零。
- 它可以容纳任何类型的任何值,并且。
- 它被处理未知类型的代码所使用。
标准库中使用interface{} 的一些例子是。
fmt包中的函数,如。
func Println(a ...interface{}) (n int, err error)
那就是在a 中接收interface{} 类型的参数的变量列表。
- 另一个常见的例子是
encoding/json:
func Marshal(v interface{}) ([]byte, error)
- 也是
database/sql:
type Scanner interface {
Scan(src interface{}) error
}
使用类型断言和类型转换
当使用空接口时,有些情况下我们需要考虑具体类型来执行一些逻辑,在这些情况下,有两种方法可以将接口类型转换为具体类型,根据我们的需要,我们可能需要使用其中之一。
让我们考虑以下情况。
type Interface interface {
Method()
}
type Integer int
func (i Integer) Method() {}
为了将类型断言从一个变成另一个,我们使用语法(variable name).(type to assert to) 。
var iface interface{} = Integer(100)
t, ok := iface.(Integer)
fmt.Printf("OK? %t, Value %v, Type %T\n", ok, t, t)
iface = "hello"
t, ok = iface.(Integer)
fmt.Printf("OK? %t, Value %v, Type %T\n", ok, t, t)
其中打印出来的。
OK? true, Value 100, Type main.Integer
OK? false, Value 0, Type main.Integer
第一行表示预期的断言,而第二行表示不起作用的断言。这是因为在第一个类型断言中,分配给变量iface 的值与类型Integer 。断言语句返回断言的类型以及一个变量,表明它是否工作,在上面的片段中被命名为ok ,重要的是总是以这种方式断言类型,否则断言失败可能导致我们的程序恐慌。
var iface interface{} = Integer(100)
t := iface.(Integer)
fmt.Printf("Value %v, Type %T\n", t, t)
iface = "hello"
t = iface.(Integer) // XXX: Panic
fmt.Printf("Value %v, Type %T\n", t, t)
输出将类似于。
Value 100, Type main.Integer
panic: interface conversion: interface {} is string, not main.Integer
goroutine 1 [running]:
main.main()
/tmp/sandbox3926933851/prog.go:21 +0xa8
另一种方法是通过使用类型转换(Type Switches)来实现我们上面所做的,这个想法与普通的switch ,但这次case 是用来断言定义的类型。
func describe(i interface{}) {
switch v := i.(type) {
case Integer:
fmt.Printf("int %d\n", v)
case string:
fmt.Printf("string %s\n", v)
default:
fmt.Printf("unknown %T - %v\n", v, v)
}
}
如果我们在main 中调用该函数。
describe("hello")
describe(Integer(100))
describe(10)
我们会得到。
string hello
int 100
unknown int - 10
这在多种类型实现一个接口类型的情况下很有用,我们需要以集中的方式定义类似的逻辑,最常见的例子是当使用Error 接口时,我们可能根据错误类型向客户返回不同的信息。
总结
通过这篇文章,我们完成了与Go中的接口类型有关的所有内容,请参考下面的推荐阅读链接,以了解更多关于Go的有趣文章。