3. Interface
Q: 接口的底层实现?
接口在底层有两种结构:
空接口 (eface)
空 interface{} 的实现,只包含两个指针:
type eface struct {
_type *_type // 指向类型信息
data unsafe.Pointer // 指向实际数据
}非空接口 (iface)
带方法的 interface 实现,包含 itab 和 data:
type iface struct {
tab *itab // 接口表,包含类型信息和方法表
data unsafe.Pointer // 指向数据的指针
}
type itab struct {
inter *interfacetype // 接口类型
_type *_type // 具体类型
hash uint32 // 类型哈希
_ [4]byte
fun [1]uintptr // 方法表,函数指针数组
}核心机制:
_type指向类型信息,这就是为什么空接口能存储任意类型值的原因itab中的方法表是个函数指针数组,保存了该类型实现的所有接口方法的地址
Q: nil 接口和接口的 nil 值有什么区别?
var p *int = nil
var i interface{} = p
fmt.Println(p == nil) // true
fmt.Println(i == nil) // false ← 注意!接口包含类型信息,不为nil面试高频陷阱:当将一个 nil 指针赋值给接口时,接口本身不为 nil,因为它包含了类型信息。
Q: 类型转换和类型断言的区别?
本质:都是把一个类型转换成另外一个类型。
类型转换
- 编译期确定:类型转换是在编译期确定的强制转换,转换前后的两个类型需要相互兼容
- 安全性:类型转换在编译期可以保证安全性
- 使用场景:主要解决数值类型、字符串、切片之间的转换
var a int = 10
var b float64 = float64(a) // 类型转换类型断言
- 运行期检查:运行期的动态检查,专门从接口类型中提取具体类型
value.(T) - 安全性:类型断言在运行时可能失败,所以一般使用
value, ok := x.(string),通过 ok 判断是否成功 - 使用场景:主要用于接口编程,拿到一个
interface{}需要还原成具体类型时使用
var i interface{} = "hello"
s, ok := i.(string) // 类型断言
if ok {
fmt.Println(s)
}Q: 接口的使用场景有哪些?
多态实现:定义一个 Shape 接口包含 Area() 方法,不同的图形结构体实现这个接口,就能用统一的方式处理各种图形,让代码更加灵活和可扩展
类型断言和反射配合:比如 JSON 解析、ORM 映射等场景,先用
interface{}接收任意类型,再通过类型断言或反射处理具体逻辑
Q: 接口之间可以比较吗?
接口值比较:接口值之间可以使用
==和!=进行比较。接口值的零值是指动态类型和动态值都为 nil。当且仅当这两部分的值都为 nil 时,这个接口值才会被认为接口值 == nil比较规则:如果两个接口值的动态类型相同,但这个动态类型是不可比较的(比如切片),将它们进行比较就会失败并且 panic
与非接口值比较:接口值在与非接口值比较时,Go会先将非接口值尝试转换为接口值,再比较
特殊性:接口值很特别,其它类型要么是可比较类型(如基本类型和指针)要么是不可比较类型(如切片、映射类型和函数),但接口值视具体的类型和值,可能会报出潜在的 panic