Skip to content

附录D:面试题精选

附录D精选Go语言常见面试题:基础语法、并发编程、内存管理、面试技巧。用途:准备Go岗位面试,检验学习成果。

使用方法:先自己回答,再看答案;标注不熟悉的内容重点复习


答:

  • 整数:int, int8, int16, int32, int64, uint8, uint16, uint32, uint64
  • 浮点:float32, float64
  • 复数:complex64, complex128
  • 布尔:bool
  • 字符串:string
  • 字节:byte(uint8别名)
  • 字符:rune(int32别名)

答:

  • new(T):为类型T分配零值内存,返回*T指针。用于任意类型的内存分配。
  • make(T, ...):为切片、map、channel分配初始化内存,返回T本身。不是指针。
p := new(int) // *int,值为0
s := make([]int, 10) // []int,长度10,容量10
ch := make(chan int, 5) // chan int

Q3: Go的接口是什么?什么时候使用空接口?

Section titled “Q3: Go的接口是什么?什么时候使用空接口?”

答: 接口定义了一组方法签名。Go通过隐式实现,任何类型只要实现了接口的方法,就实现了该接口。

type Writer interface {
Write([]byte) (int, error)
}

空接口interface{}(别名any)表示任何类型:

var i interface{} = 10
var 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")


答:

特性线程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 <- 2
ch <- 3 // 缓冲区满,阻塞

答: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("无数据")
}

答:Go通过返回值传递错误,典型模式:

result, err := doSomething()
if err != nil {
return fmt.Errorf("操作失败: %w", err)
}

使用errors包进行错误检查:

errors.Is(err, target) // 判断错误链中是否有target
errors.As(err, &target) // 从错误链中提取特定类型

答:

  • panic:触发运行时错误,用于真正不应该发生的情况
  • recover:捕获panic,防止程序崩溃
func safeCall() {
defer func() {
if r := recover(); r != nil {
fmt.Println("恢复:", r)
}
}()
panic("oops")
}

答:

  • 自动垃圾回收(GC)
  • 三色标记并发GC
  • 分代回收(简单)
  • 逃逸分析:编译器决定变量在栈还是堆分配

答:编译器分析变量的生命周期,决定其在栈还是堆分配。

func foo() int {
x := 10 // 分配在栈上
return x
}
func bar() *int {
x := 10 // 逃逸,分配在堆上
return &x // 返回指针,x生命周期超出函数
}

答:

  • 小切片(cap小於1024):约1.5倍增长
  • 大切片(cap大於等於1024):约1.25倍增长
  • 新容量 = 旧容量 * 2(小于1024)或 旧容量 * 1.25(大于等于1024)

答:

var s1 []int = nil // nil切片
s2 := []int{} // 空切片
s3 := make([]int, 0) // 空切片
// len和cap相同
// 但s1 == nil为true,s2 == nil为false

答:不是。需要使用sync.RWMutex或sync.Map。

var mu sync.RWMutex
var m = make(map[string]int)
mu.RLock() // 读
v := m["key"]
mu.RUnlock()
mu.Lock() // 写
m["key"] = v
mu.Unlock()

答:

  • 命名:驼峰式,局部变量短命名
  • 包名:小写,简短
  • 格式化:使用gofmt
  • 错误处理:不要忽略错误
  • 注释:导出函数要文档注释
  • 测试:文件名以_test.go结尾

答:

  1. 避免全局变量累积
  2. 及时释放资源(defer close)
  3. 避免goroutine泄漏(使用context取消)
  4. 减少大对象引用
  5. 及时清理map中的无用数据