155 lines
5.2 KiB
Markdown
155 lines
5.2 KiB
Markdown
---
|
||
tags: [go, golang, interview, interface-questions]
|
||
create time: 2026-06-07 14:30
|
||
---
|
||
|
||
# Interface 面试题 🔌
|
||
|
||
## 概述
|
||
|
||
本文件涵盖 Go Interface 的 5 道高频面试题,涉及底层原理(eface/iface)、类型断言、比较规则和实际应用。Interface 是 Go 多态和接口抽象的核心机制,理解其底层实现是区分中高级开发者的关键。
|
||
|
||
## 关联笔记
|
||
|
||
- [[hzh/GolangStar/Go语言基础/Go语言接口]] — Interface 基础用法
|
||
- [[hzh/GolangStar/Go语言进阶/反射]] — 反射与 Interface 的配合
|
||
- [[hzh/GolangStar/Go语言原理/interface原理]] — itab/witab 源码分析
|
||
- [[hzh/GolangStar/Go面试题库/基础面试题]] — nil interface 陷阱
|
||
|
||
## 正文
|
||
|
||
### Q1:Interface 的底层原理? 🟡中等
|
||
|
||
> [!question] ❓ 思考一下
|
||
> 为什么 `var i interface{} = 42` 能存储任意类型的值?它的内部是怎么做到这一点的?
|
||
|
||
## 参考答案
|
||
|
||
Go 的 interface 有两种底层结构:
|
||
|
||
#### eface(空接口 `interface{}`)
|
||
|
||
```go
|
||
type eface struct {
|
||
_type *_type // 指向类型信息
|
||
data unsafe.Pointer // 指向实际数据
|
||
}
|
||
```
|
||
|
||
空接口只有两个指针:**类型指针 + 数据指针**。这就是它能存储任意类型的原因。
|
||
|
||
#### iface(带方法的非空接口)
|
||
|
||
```go
|
||
type iface struct {
|
||
tab *itab // 接口类型描述 + 方法表
|
||
data unsafe.Pointer // 指向实际数据
|
||
}
|
||
|
||
type itab struct {
|
||
inter *interfacetype // 接口类型
|
||
_type *_type // 具体类型
|
||
hash uint32 // 用于类型 switch
|
||
fun [1]uintptr // 方法函数指针数组(变长)
|
||
}
|
||
```
|
||
|
||
iface 比 eface 多了 **itab(方法表)**,它记录了该类型实现的所有接口方法的地址。
|
||
|
||
> [!note] 📝 核心考点
|
||
> eface 只需要标识"是什么类型"和"值是什么";iface 还需要知道"有哪些方法可以调用"。方法表是 iface 的核心——它让 Go 在运行时通过函数指针间接调用方法,实现了动态分发。
|
||
|
||
> [!info] 🔗 延伸阅读
|
||
> - [[hzh/GolangStar/Go语言原理/interface原理]] — itab 的构建时机(编译期 vs 运行期)
|
||
|
||
---
|
||
|
||
### Q2:iface 和 eface 的区别? 🟢简单
|
||
|
||
## 参考答案
|
||
|
||
| | eface(空接口) | iface(非空接口) |
|
||
|--|---------------|-----------------|
|
||
| 定义 | `interface{}` | 包含至少一个方法的接口 |
|
||
| 字段 | `_type` + `data` | `itab` + `data` |
|
||
| 方法信息 | 无 | 有(itab 中的 fun 数组) |
|
||
| 内存占用 | 16 字节(两个指针) | 更大(itab 额外开销) |
|
||
|
||
> [!tip] 💡 面试技巧
|
||
> "空接口没有方法约束,所以不需要存方法表;非空接口需要方法表来实现动态分派。这是 iface 比 eface 复杂的原因。"
|
||
|
||
---
|
||
|
||
### Q3:类型转换和类型断言的区别? 🟡中等
|
||
|
||
## 参考答案
|
||
|
||
| 维度 | 类型转换 `T(value)` | 类型断言 `value.(T)` |
|
||
|------|-------------------|---------------------|
|
||
| 作用对象 | 普通变量 | **接口变量** |
|
||
| 检查时机 | **编译期** | **运行期** |
|
||
| 安全性 | 编译期保证 | 可能运行时失败 |
|
||
| 语法 | `T(value)` | `value.(T)` 或 `value, ok := x.(T)` |
|
||
|
||
```go
|
||
// 类型转换:编译期检查
|
||
var f float64 = 3.14
|
||
var i int = int(f)
|
||
|
||
// 类型断言:运行期检查
|
||
var i interface{} = "hello"
|
||
s, ok := i.(string) // s="hello", ok=true
|
||
n, ok := i.(int) // n=0, ok=false
|
||
```
|
||
|
||
> [!warning] ⚠️ 高频陷阱
|
||
> 类型断言在不匹配时会 panic!务必使用安全版本 `value, ok := x.(T)` 来避免。
|
||
|
||
---
|
||
|
||
### Q4:Interface 的应用场景? 🟢简单
|
||
|
||
## 参考答案
|
||
|
||
| 场景 | 说明 | 示例 |
|
||
|------|------|------|
|
||
| **依赖注入/解耦** | 高层模块不依赖具体实现 | `UserRepo` 接口 |
|
||
| **多态** | 统一处理不同类型的行为 | `Shape` 接口的 `Area()` |
|
||
| **标准库 API** | 提供统一操作接口 | `io.Reader` / `io.Writer` |
|
||
| **序列化/反序列化** | 配合反射处理任意类型 | `json.Marshal` |
|
||
| **插件化架构** | 第三方扩展遵循接口规范 | Web 中间件、DB 驱动 |
|
||
|
||
> [!tip] 💡 面试技巧
|
||
> 举例时可以用 `io.Reader` 来说明:"文件、网络连接、bytes.Buffer 都实现了 Reader 接口,所以可以用同一套代码读取它们的数据。这就是 Go 接口的优雅之处——隐式实现,无需显式声明 implements。"
|
||
|
||
---
|
||
|
||
### Q5:接口之间可以相互比较吗? 🟡中等
|
||
|
||
## 参考答案
|
||
|
||
**可以。** 两个接口值相等的条件:
|
||
|
||
1. 两者均为 nil(动态类型和动态值都为 nil)
|
||
2. 动态类型相同且动态值相等
|
||
|
||
**特殊情况:nil interface ≠ nil 值**
|
||
|
||
```go
|
||
var c Coder = (*Gopher)(nil) // 动态类型为 *Gopher,值为 nil
|
||
c == nil // false!因为动态类型不为 nil
|
||
```
|
||
|
||
> [!warning] ⚠️ 高频陷阱
|
||
> 这是 Go 面试中最经典的陷阱题之一。记住:**接口 == nil 的条件是动态类型 AND 动态值都为 nil**。当接口持有 nil 指针时,动态类型不为 nil,所以不等于 nil。
|
||
|
||
> [!info] 🔗 延伸阅读
|
||
> - [[hzh/GolangStar/Go面试题库/基础面试题]] — Q16 也是考察 nil 不相等问题
|
||
|
||
## 关联笔记
|
||
|
||
- [[hzh/GolangStar/Go语言基础/Go语言接口]]
|
||
- [[hzh/GolangStar/Go语言进阶/反射]]
|
||
- [[hzh/GolangStar/Go语言原理/interface原理]]
|
||
- [[hzh/GolangStar/Go面试题库/基础面试题]]
|