Go语言基础语法

1. 使用值为 nil 的 slice、map 会发生什么?

  • Slice:允许对值为 nil 的 slice 添加元素(append),不会报错,Go 会自动初始化。
  • Map禁止对值为 nil 的 map 添加元素,会造成运行时 panic。必须使用 make 初始化后才能写入。

2. 访问 map 中的 key,需要注意什么?

当访问 map 中不存在的 key 时,Go 会返回该类型的零值(如 0, “”, nil),而不是报错。因此,不能仅通过返回值是否为零值来判断 key 是否存在。
正确姿势:使用 value, ok := map[key],通过 ok(bool)来判断 key 是否存在。

3. string 类型的值可以修改吗?

不能。String 是只读的 byte slice。尝试通过索引(如 s[0] = 'a')修改会报错。
解决方法:将 string 转为 []byte[]rune,修改后再转回 string。

4. switch 中如何强制执行下一个 case 代码块?

Switch 默认自带 break。如果需要执行下一个 case,需显式使用 fallthrough 关键字。

5. 你是如何关闭 HTTP 的响应体的?

必须在处理 HTTP 请求的错误检查之后,手动调用 defer resp.Body.Close()。如果不关闭,可能会导致 socket 文件描述符泄漏。

6. 你是否主动关闭过 http 连接,为啥要这样做?

HTTP 标准库默认启用长连接(Keep-Alive)。如果不需要复用连接(如大量一次性请求),应主动关闭,避免耗尽 socket。
方法

  1. 设置 req.Close = true
  2. 设置 Header Connection: close

7. 解析 JSON 数据时,默认将数值当做哪种类型?

json.Unmarshal 解析到 interface{} 时,默认将数值按照 float64 处理。如果需要 int,需要手动转换。

8. 如何从 panic 中恢复?

defer 延迟执行的函数中调用 recover()。它能捕捉并中断 panic,防止程序崩溃。

9. 简短声明的变量(:=)需要注意什么?

  1. 只能在函数内部使用。
  2. 左侧至少要有一个新变量
  3. 不能用于 struct 的字段赋值。

10. range 迭代 map 是有序的吗?

无序的。Go 运行时为了防止开发者依赖遍历顺序,特意引入了随机化。

11. recover 的执行时机?

必须在 defer 函数中直接调用 recover() 才有效。

  • 不能在 defer 嵌套函数中调用。
  • 不能在主函数直接调用。

12. 闭包错误引用同一个变量问题怎么处理?

在 Go 1.22 之前,for 循环中的变量是复用的。如果在闭包(goroutine)中使用该变量,所有闭包可能拿到同一个值。
解决:在循环体内用新变量承接:v := v,或将变量作为参数传递给闭包。

13. 在循环内部执行 defer 语句会发生什么?

defer 是在函数退出时才执行,而不是循环结束时。如果在循环中(如处理文件)使用 defer,会导致资源一直无法释放,直到函数结束,可能导致内存泄漏或句柄耗尽。

14. 说出一个避免 Goroutine 泄露的措施

使用 context 包(ctx, cancel := context.WithCancel)。通过 select 监听 ctx.Done() 信号,在主程退出或超时的时候,通知子协程退出。

15. 如何跳出 for select 循环?

select 中使用 break 只能跳出 select,无法跳出外层 for
解决:使用 break label(标签)或 return

16. 如何在切片中查找?

如果切片是有序的,可使用 sort.Search(二分查找)。如果是无序的,通常只能手动遍历。

17. 如何初始化带嵌套结构的结构体?

必须明确指定内部结构体的字段名,或者使用结构体字面量进行逐层初始化。Go 推荐组合优于继承

18. 切片和数组的区别?

  • 数组:值类型,长度固定,是类型的一部分([3]int[4]int 是不同类型)。传递时会进行内存拷贝。
  • 切片:引用类型,长度可变,底层指向数组。传递时成本极低(只拷贝 SliceHeader)。

19. new 和 make 的区别?

  • new:分配内存,置零,返回指针。适用于任意类型(如 new(int))。
  • make:分配并初始化,返回引用。仅用于 slicemapchannel

20. Printf()、Sprintf()、Fprintf() 的区别?

  • Printf:输出到标准输出(屏幕)。
  • Sprintf:格式化并返回字符串
  • Fprintf:输出到文件或 io.Writer

21. 说说 go 语言中的 for 循环

Go 只有 for 关键字(没有 while)。支持传统 C 风格 for、类似 while 的条件 for 以及 for range。支持 continuebreak

22. Array 类型的值作为函数参数

Go 中数组是值传递。作为参数传递时,会完整拷贝整个数组。如果在函数内修改数组,外部原始数组不会改变。如果需要修改,请传切片或数组指针。

23. 说说 go 语言中的 switch 语句

默认不需要 break(匹配即结束)。
支持多条件匹配(case 1, 2:)。
支持类型断言 switch (switch v.(type))。

24. 说说 go 语言中有没有隐藏的 this 指针?

没有。Go 的方法接收者(Receiver)是显式声明的,可以是值或指针,名称由用户自己定义(通常用类型首字母),没有隐式的 thisself

25. go 语言中的引用类型包含哪些?

切片(Slice)、字典(Map)、通道(Channel)、接口(Interface)、函数(Function)。

26. go 语言中指针运算有哪些?

Go 的指针不支持算术运算(如 p++)。可以通过 unsafe.Pointer 进行特殊转换,但通常不推荐。

27. go 语言触发异常的场景有哪些?

  • 空指针引用。
  • 数组/切片下标越界。
  • 除数为 0。
  • 对 nil map 赋值。
  • 关闭已关闭的 channel。
  • 显式调用 panic()

28. 说说 go 语言的 beego 框架

一个全栈式的 Web 框架,集成了路由、ORM、日志、配置管理等,功能丰富,适合快速开发。

29. 说说 go 语言的 goconvey 框架

一个支持 BDD(行为驱动开发)风格的测试框架,拥有漂亮的 Web UI 界面,能实时监控代码变更并运行测试。

30. GoStub 的作用是什么?

用于单元测试中的打桩(Mock)。可以对全局变量、函数进行替换(Stub),以便隔离依赖进行测试。

31. go 语言编程的好处是什么?

  • 编译速度快,运行效率高。
  • 原生支持高并发(Goroutine)。
  • 语法简单,内置垃圾回收(GC)。
  • 强大的标准库和工具链。

32. 说说 go 语言的 select 机制

用于处理异步 IO 问题,专门用来监听多个 channel 的读写操作。
如果多个 case 同时满足,select随机选择一个执行。

33. 解释一下 go 语言中的静态类型声明

Go 是静态类型语言,变量在编译期必须确定类型。编译器会优化内存分配,但 Go 提供了类型推断(:=)让代码写起来像动态语言一样简洁。

34. go 的接口是什么?

接口定义了一组方法的集合。任何类型只要实现了这些方法,就被认为实现了该接口(隐式实现,Duck Typing)。接口是 Go 实现多态的关键。

35. Go 语言里面的类型断言是怎么回事?

用于检查接口变量底层存储的具体类型。
语法:value, ok := x.(T)。如果 x 不是类型 Tok 为 false,不会 panic。

36. go 语言中局部变量和全局变量的缺省值是什么?

缺省值是该类型的零值

  • 数值:0
  • 布尔:false
  • 字符串:“”
  • 指针/Slice/Map/Chan/Interface:nil

37. go 语言编程的好处是什么?(重复问题)

(同第 31 问)强调开发效率与运行效率的平衡,以及工程化友好的工具链。

38. 解释一下 go 语言中的静态类型声明(重复问题)

(同第 33 问)强调编译期检查能提前发现错误。

39. 模块化编程是怎么回事?

将大程序分解为小的、独立的包(Package)。Go 通过 packageimport 管理依赖,Go Modules 是目前的标准包管理工具。

40. Golang 的方法有什么特别之处?

方法是绑定在特定类型(接收者)上的函数。
接收者可以是值或指针。这使得 Go 的非结构体类型(如 type MyInt int)也能定义方法。

41. Golang 可变参数

函数最后一个参数可以是可变参数,格式为 ...Type。本质上,它在函数内部是一个切片。调用时可以传多个值,或传切片后跟 ...

42. Golang Slice 的底层实现

Slice 是一个结构体,包含三个字段:

  1. Pointer:指向底层数组的指针。
  2. Len:切片当前长度。
  3. Cap:切片当前容量。

43. Golang Slice 的扩容机制,有什么注意点?

  • Go 1.18 前:容量 < 1024 时翻倍;>= 1024 时增加 1.25 倍。
  • 注意:扩容可能会导致底层数组重新分配(地址改变)。如果切片引用了底层大数组的一小部分,可能会导致大数组无法被 GC 回收(内存泄漏)。

44. Golang Map 底层实现

Map 底层是 Hash Table
核心结构是 hmap,包含多个 bmap(bucket,桶)。每个 bucket 存储 8 个键值对。使用链地址法解决哈希冲突,当 bucket 满时会链接溢出桶(overflow bucket)。

45. JSON 标准库对 nil slice 和 空 slice 的处理是一致的吗?

不一致

  • nil slice 序列化为 null
  • empty slice (make([]int, 0)) 序列化为 []

46. Golang 的内存模型,为什么小对象多了会造成 GC 压力?

大量小对象意味着 GC 在标记阶段需要扫描更多的指针节点,增加了 CPU 消耗和 STW(Stop The World)的潜在时间。

47. Data Race 问题怎么解决?能不能不加锁解决这个问题?

  • 解决:使用 sync.Mutex 加锁,或者使用 Channel 通信。
  • 不加锁:可以使用 sync/atomic 包进行原子操作,或者通过 Channel 传递数据的所有权(避免共享)。

48. 在 range 迭代 slice 时,你怎么修改值的?

range 迭代出的 val 是值的副本,修改它不会影响原 slice。
正确做法:使用索引访问 slice[i] = newValue

49. nil interface 和 nil interface value 的区别

这是 Interface 最大的坑。
Interface 只有当 TypeValue 都为 nil 时,才等于 nil
如果将一个值为 nil 的具体类型指针赋值给 interface,该 interface 不等于 nil(因为 Type 有值)。

50. select 可以用于什么?

  • 多路复用:同时监听多个 channel 的读写。
  • 超时控制:配合 time.After 实现超时。
  • 非阻塞读写:配合 default 分支。
  • 完美退出:监听退出信号 channel。