🐹
Go 面试题
7 道题 · 2 个模块
简单 3中等 3困难 1
基础语法
4 题Q1.Go 中 defer 的执行顺序和用途?简单
Q1.Go 中 defer 的执行顺序和用途?
参考答案
`defer` 延迟执行——在函数 return 前按**后进先出(LIFO)**顺序执行。
go
func main() {
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
// 输出: 3, 2, 1
}
用途:关闭文件/连接、释放锁、recover panic。Q2.Go 中数组和切片(slice)的区别?简单
Q2.Go 中数组和切片(slice)的区别?
参考答案
**数组**:长度固定,是值类型——赋值会复制整个数组。`[3]int{1,2,3}`
**切片**:动态长度,是引用类型——指向底层数组的视图。`[]int{1,2,3}`
go
arr := [3]int{1, 2, 3} // 数组
s := []int{1, 2, 3} // 切片
s = append(s, 4) // 切片可追加
// len=长度, cap=容量
s2 := make([]int, 0, 10) // len=0, cap=10
日常开发中几乎只用切片。Q3.Go 中的指针和取地址?有指针运算吗?简单
Q3.Go 中的指针和取地址?有指针运算吗?
参考答案
Go 有指针但没有指针运算(更安全)。`*T` 是指针类型,`&` 取地址,`*` 解引用。
go
x := 42
p := &x // *int
fmt.Println(*p) // 42(解引用)
*p = 100 // 修改 x 的值
// ❌ 没有指针运算
// p++ // 编译错误
Go 的指针比 C 安全——不能做算术运算,有自动逃逸分析。Q4.Go 的接口(interface)是如何实现的?空接口有什么用?中等
Q4.Go 的接口(interface)是如何实现的?空接口有什么用?
参考答案
Go 接口是**隐式实现**——不需要显式声明 `implements`。接口底层是 `(type, value)` 对。
go
type Writer interface {
Write([]byte) (int, error)
}
// 任何有 Write 方法的类型自动实现 Writer
// 空接口 interface{} = any(Go 1.18+)
var x any = "hello"
x = 42
x = []int{1,2,3}
空接口可以接收任意类型——类似 Java Object,但需要类型断言才能使用。并发编程
3 题Q1.goroutine 和线程的区别?中等
Q1.goroutine 和线程的区别?
参考答案
| | goroutine | 线程 |
|---|----------|------|
| 内存 | ~2KB 栈(可动态增长) | ~1MB 固定 |
| 调度 | Go 运行时(用户态) | OS 内核 |
| 切换成本 | 极低(~200ns) | 较高(~1-10μs) |
| 数量 | 可轻松创建数十万 | 数千 |
| 通信 | channel 优先 | 共享内存+锁 |
goroutine 是 Go 并发的核心——轻量、多路复用在线程上。Q2.channel 的缓冲和非缓冲的区别?中等
Q2.channel 的缓冲和非缓冲的区别?
参考答案
**非缓冲** `make(chan int)`:发送方阻塞直到接收方准备好——同步通信。
**缓冲** `make(chan int, 10)`:缓冲满之前不阻塞——异步通信。
go
ch := make(chan int) // 非缓冲
ch := make(chan int, 10) // 缓冲 10
// select 多路复用
select {
case msg := <-ch1:
fmt.Println(msg)
case ch2 <- val:
fmt.Println("sent")
default:
fmt.Println("no activity")
}
Q3.Go 中如何避免 goroutine 泄漏?困难
Q3.Go 中如何避免 goroutine 泄漏?
参考答案
goroutine 泄漏 = goroutine 永远阻塞,无法退出。
**常见原因与解决方案**:
1. **channel 永远无人接收** → 用 `context` 或 `done` channel 通知退出
2. **无限循环无退出条件** → 总是有 `ctx.Done()` 分支
go
func worker(ctx context.Context, ch <-chan int) {
for {
select {
case <-ctx.Done():
return // 正常退出
case val := <-ch:
process(val)
}
}
}
用 `runtime.NumGoroutine()` 监控协程数量。