附录D:面试题精选
附录D精选Go语言常见面试题:基础语法、并发编程、内存管理、面试技巧。用途:准备Go岗位面试,检验学习成果。
使用方法:先自己回答,再看答案;标注不熟悉的内容重点复习
Q1: Go的基本数据类型有哪些?
Section titled “Q1: Go的基本数据类型有哪些?”答:
- 整数:int, int8, int16, int32, int64, uint8, uint16, uint32, uint64
- 浮点:float32, float64
- 复数:complex64, complex128
- 布尔:bool
- 字符串:string
- 字节:byte(uint8别名)
- 字符:rune(int32别名)
Q2: make和new的区别?
Section titled “Q2: make和new的区别?”答:
new(T):为类型T分配零值内存,返回*T指针。用于任意类型的内存分配。make(T, ...):为切片、map、channel分配初始化内存,返回T本身。不是指针。
p := new(int) // *int,值为0s := make([]int, 10) // []int,长度10,容量10ch := make(chan int, 5) // chan intQ3: Go的接口是什么?什么时候使用空接口?
Section titled “Q3: Go的接口是什么?什么时候使用空接口?”答: 接口定义了一组方法签名。Go通过隐式实现,任何类型只要实现了接口的方法,就实现了该接口。
type Writer interface { Write([]byte) (int, error)}空接口interface{}(别名any)表示任何类型:
var i interface{} = 10var s interface{} = "hello"适用于需要存储任意类型的场景,如fmt.Println。
Q4: 值接收者和指针接收者的区别?
Section titled “Q4: 值接收者和指针接收者的区别?”答:
- 值接收者:方法操作副本,无法修改原对象
- 指针接收者:方法操作原对象,可以修改
type User struct { Name string}
func (u User) SetName1(name string) { // 值接收 u.Name = name // 不影响原对象}
func (u *User) SetName2(name string) { // 指针接收 u.Name = name // 影响原对象}
user := User{Name: "Alice"}user.SetName1("Bob")fmt.Println(user.Name) // Alice(未变)
user.SetName2("Carol")fmt.Println(user.Name) // Carol(已变)Go会自动转换:user.SetName2("Carol") 等效于 (&user).SetName2("Carol")
Q5: goroutine和thread的区别?
Section titled “Q5: goroutine和thread的区别?”答:
| 特性 | 线程 | Goroutine |
|---|---|---|
| 栈大小 | ~1MB | ~2KB(可增长) |
| 创建成本 | 高 | 低 |
| 调度 | OS内核 | Go运行时(GMP模型) |
| 数量 | 有限 | 可轻松创建百万 |
Q6: channel有缓冲和无缓冲的区别?
Section titled “Q6: channel有缓冲和无缓冲的区别?”答:
- 无缓冲channel:发送和接收必须同时准备,否则阻塞
- 有缓冲channel:发送不阻塞直到缓冲区满,接收不阻塞直到缓冲区空
// 无缓冲ch := make(chan int)ch <- 1 // 阻塞直到其他goroutine接收
// 有缓冲ch := make(chan int, 3)ch <- 1 // 不阻塞ch <- 2ch <- 3 // 缓冲区满,阻塞Q7: select的作用?
Section titled “Q7: select的作用?”答:select监听多个channel操作,类似于switch,但用于channel。
select {case v := <-ch1: fmt.Println("从ch1收到:", v)case v := <-ch2: fmt.Println("从ch2收到:", v)case <-time.After(time.Second): fmt.Println("超时")default: fmt.Println("无数据")}Q8: Go的错误处理模式?
Section titled “Q8: Go的错误处理模式?”答:Go通过返回值传递错误,典型模式:
result, err := doSomething()if err != nil { return fmt.Errorf("操作失败: %w", err)}使用errors包进行错误检查:
errors.Is(err, target) // 判断错误链中是否有targeterrors.As(err, &target) // 从错误链中提取特定类型Q9: panic和recover的用法?
Section titled “Q9: panic和recover的用法?”答:
- panic:触发运行时错误,用于真正不应该发生的情况
- recover:捕获panic,防止程序崩溃
func safeCall() { defer func() { if r := recover(); r != nil { fmt.Println("恢复:", r) } }() panic("oops")}Q10: Go的内存管理机制?
Section titled “Q10: Go的内存管理机制?”答:
- 自动垃圾回收(GC)
- 三色标记并发GC
- 分代回收(简单)
- 逃逸分析:编译器决定变量在栈还是堆分配
Q11: 什么是逃逸分析?
Section titled “Q11: 什么是逃逸分析?”答:编译器分析变量的生命周期,决定其在栈还是堆分配。
func foo() int { x := 10 // 分配在栈上 return x}
func bar() *int { x := 10 // 逃逸,分配在堆上 return &x // 返回指针,x生命周期超出函数}切片与Map
Section titled “切片与Map”Q12: 切片扩容机制?
Section titled “Q12: 切片扩容机制?”答:
- 小切片(cap小於1024):约1.5倍增长
- 大切片(cap大於等於1024):约1.25倍增长
- 新容量 = 旧容量 * 2(小于1024)或 旧容量 * 1.25(大于等于1024)
Q13: nil切片和空切片区别?
Section titled “Q13: nil切片和空切片区别?”答:
var s1 []int = nil // nil切片s2 := []int{} // 空切片s3 := make([]int, 0) // 空切片
// len和cap相同// 但s1 == nil为true,s2 == nil为falseQ14: Map是线程安全的吗?
Section titled “Q14: Map是线程安全的吗?”答:不是。需要使用sync.RWMutex或sync.Map。
var mu sync.RWMutexvar m = make(map[string]int)
mu.RLock() // 读v := m["key"]mu.RUnlock()
mu.Lock() // 写m["key"] = vmu.Unlock()Q15: Go的编码规范?
Section titled “Q15: Go的编码规范?”答:
- 命名:驼峰式,局部变量短命名
- 包名:小写,简短
- 格式化:使用gofmt
- 错误处理:不要忽略错误
- 注释:导出函数要文档注释
- 测试:文件名以
_test.go结尾
Q16: 如何避免内存泄漏?
Section titled “Q16: 如何避免内存泄漏?”答:
- 避免全局变量累积
- 及时释放资源(defer close)
- 避免goroutine泄漏(使用context取消)
- 减少大对象引用
- 及时清理map中的无用数据