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。
方法:
- 设置
req.Close = true。 - 设置 Header
Connection: close。
7. 解析 JSON 数据时,默认将数值当做哪种类型?
json.Unmarshal 解析到 interface{} 时,默认将数值按照 float64 处理。如果需要 int,需要手动转换。
8. 如何从 panic 中恢复?
在 defer 延迟执行的函数中调用 recover()。它能捕捉并中断 panic,防止程序崩溃。
9. 简短声明的变量(:=)需要注意什么?
- 只能在函数内部使用。
- 左侧至少要有一个新变量。
- 不能用于 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:分配并初始化,返回引用。仅用于
slice、map、channel。
20. Printf()、Sprintf()、Fprintf() 的区别?
Printf:输出到标准输出(屏幕)。Sprintf:格式化并返回字符串。Fprintf:输出到文件或io.Writer。
21. 说说 go 语言中的 for 循环
Go 只有 for 关键字(没有 while)。支持传统 C 风格 for、类似 while 的条件 for 以及 for range。支持 continue 和 break。
22. Array 类型的值作为函数参数
Go 中数组是值传递。作为参数传递时,会完整拷贝整个数组。如果在函数内修改数组,外部原始数组不会改变。如果需要修改,请传切片或数组指针。
23. 说说 go 语言中的 switch 语句
默认不需要 break(匹配即结束)。
支持多条件匹配(case 1, 2:)。
支持类型断言 switch (switch v.(type))。
24. 说说 go 语言中有没有隐藏的 this 指针?
没有。Go 的方法接收者(Receiver)是显式声明的,可以是值或指针,名称由用户自己定义(通常用类型首字母),没有隐式的 this 或 self。
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 不是类型 T,ok 为 false,不会 panic。
36. go 语言中局部变量和全局变量的缺省值是什么?
缺省值是该类型的零值:
- 数值:0
- 布尔:false
- 字符串:“”
- 指针/Slice/Map/Chan/Interface:nil
37. go 语言编程的好处是什么?(重复问题)
(同第 31 问)强调开发效率与运行效率的平衡,以及工程化友好的工具链。
38. 解释一下 go 语言中的静态类型声明(重复问题)
(同第 33 问)强调编译期检查能提前发现错误。
39. 模块化编程是怎么回事?
将大程序分解为小的、独立的包(Package)。Go 通过 package 和 import 管理依赖,Go Modules 是目前的标准包管理工具。
40. Golang 的方法有什么特别之处?
方法是绑定在特定类型(接收者)上的函数。
接收者可以是值或指针。这使得 Go 的非结构体类型(如 type MyInt int)也能定义方法。
41. Golang 可变参数
函数最后一个参数可以是可变参数,格式为 ...Type。本质上,它在函数内部是一个切片。调用时可以传多个值,或传切片后跟 ...。
42. Golang Slice 的底层实现
Slice 是一个结构体,包含三个字段:
- Pointer:指向底层数组的指针。
- Len:切片当前长度。
- 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 只有当 Type 和 Value 都为 nil 时,才等于 nil。
如果将一个值为 nil 的具体类型指针赋值给 interface,该 interface 不等于 nil(因为 Type 有值)。
50. select 可以用于什么?
- 多路复用:同时监听多个 channel 的读写。
- 超时控制:配合
time.After实现超时。 - 非阻塞读写:配合
default分支。 - 完美退出:监听退出信号 channel。