Files
cs-note/hhs/EXAM/Week02.md
T
2026-05-24 11:42:38 +08:00

540 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [Go, 考试, Week02, 武科大服务端]
create time: 2026-05-14 12:00
---
# 金山考试 - 武科大服务端-第二次课
## 概述
本次考试覆盖 Go 语言进阶核心知识点,包括切片(slice)、数组与切片的底层关系、Map 操作、结构体与方法、接口实现、标准库(JSON / 文件 / 时间)、指针与内存分配、并发编程(channel / select / sync)等。适合用于检验 Go 入门阶段学习成果。
**知识点地图:**
```mermaid
graph LR
A[Go 进阶基础] --> B[切片 slice]
A --> C[映射 map]
A --> D[结构体 struct]
A --> E[方法 method]
A --> F[接口 interface]
A --> G[标准库 JSON/文件/时间]
A --> H[指针 new &]
A --> I[并发 channel select sync]
B --> B1[底层引用共享]
B --> B2[make vs new]
D --> D1[字段访问 .]
E --> E1[值接收者 vs 指针接收者]
I --> I1[无缓冲 Channel 同步语义]
I --> I2[select 多路复用]
I --> I3[WaitGroup / Mutex / RWMutex]
```
## 正文
### 一、单选题
#### 1. 错误的切片声明方式
【单选题】下列关于 Go 语言的切片声明,哪个是错误的?(5分)
A. `var slice1 []int`
B. `slice2 := []int{}`
C. `slice3 := make([]int, 0)`
D. `slice4 := new([]int)`
**正确答案:**D
**作答答案:**D
> [!warning] new vs make
> - `make(T, args)`:仅用于 **slice、map、channel**,返回类型 T 本身并初始化
> - `new(T)`:返回指向类型 T 的零值指针 `*T`,不初始化内部结构
> - `new([]int)` 返回的是 `*[]int`,虽然能编译但不能直接当作切片使用
---
#### 2. 创建长度 0 容量 10 的切片
【单选题】要创建一个初始长度为 0,容量为 10 的整数切片,应该使用(5分)
A. `new([]int)`
B. `make([]int, 0, 10)`
C. `new([10]int)`
D. `make([10]int)`
**正确答案:**B
**作答答案:**B
> [!info] make 的参数含义
> `make([]int, len, cap)` — len 和 cap 均可选:
> - `make([]int, 0, 10)` → 长度 0、容量 10
> - `make([]int, 10)` → 长度 10、容量 10(cap 省略时等于 len)
> - 数组用 `[N]T` 语法声明,不需要 make
---
#### 3. 数组 vs 切片的长度特性
【单选题】以下关于数组和切片的说法,正确的是(5分)
A. 数组的长度可以动态改变,切片不行
B. 切片的长度可以动态改变,数组不行
C. 数组和切片的长度都可以动态改变
D. 数组和切片的长度都不能动态改变
**正确答案:**B
**作答答案:**B
> [!tip] 核心区别一句话
> 数组长度是**类型的一部分**(`[5]int` 和 `[10]int` 是不同的类型),不可变;切片是对数组的**轻量描述符**(ptr + len + cap),长度可以通过 append/slice 操作动态变化。
---
#### 4. 切片修改对底层数组的影响
【单选题】给定以下 Go 代码,运行后输出结果是什么?(5分)
```go
package main
import "fmt"
func main() {
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4] // 引用 arr[1]=2, arr[2]=3, arr[3]=4
slice[0] = 10 // 等价于 arr[1] = 10
fmt.Println(arr)
}
```
A. `[1 2 3 4 5]`
B. `[1 10 3 4 5]`
C. `[10 2 3 4 5]`
D. 编译错误
**正确答案:**B
**作答答案:**B
> [!important] 切片共享底层数组
> 切片是对底层数组的**引用**。`arr[1:4]` 创建的切片其索引 0 对应 `arr[1]`,因此 `slice[0] = 10` 会直接修改 `arr[1]`。这是理解 Go 切片性能优势的关键——零拷贝传递数据。
```mermaid
sequenceDiagram
participant Arr as arr = [1,2,3,4,5]
participant Slc as slice = arr[1:4]
participant Set as slice[0] = 10
Arr->>Slc: 创建引用(ptr→arr[1], len=3, cap=4)
Slc->>Set: 写 slice[0](即 arr[1])
Set-->>Arr: arr[1] 被改为 10
Arr-->>Arr: arr = [1,10,3,4,5]
```
---
#### 5. Map 的 delete 操作
【单选题】观察下面的 Go 代码,判断运行结果是什么?(5分)
```go
package main
import "fmt"
func main() {
m := map[string]int{"a": 1, "b": 2, "c": 3}
delete(m, "b")
fmt.Println(m)
}
```
A. `map[a:1 b:2 c:3]`
B. `map[a:1 b:0 c:3]`
C. `map[a:1 c:3]`
D. `map[a:1]`
**正确答案:**C
**作答答案:**C
> [!info] delete 的语义
> `delete(map, key)` 直接删除键值对,key 不存在时也是安全的(noop,不 panic)。打印时只保留剩余元素。
---
#### 6. map 的 json.Unmarshal
【单选题】关于 map,下面说法正确的是?(5分)
A. `json.Unmarshal()` 的入参必须为 map 的地址
B. 在函数调用中传递 map,则子函数中对 map 元素的增加不会导致父函数中 map 的修改
C. 不能使用内置函数 `delete()` 删除 map 的元素
D. 以上都不对
**正确答案:**A
**作答答案:**A
> [!info] 为什么必须是地址?
> `json.Unmarshal(data, &m)` 需要指针才能修改原 map。而 `json.Marshal(data, m)` 不需要地址(值即可读取)。
>
> **补充:map 作为参数传递**——Go 中 map 是引用类型(内部是指针结构体),所以子函数通过同一底层数据结构增删元素,父函数可见。选项 B 说反了。
---
#### 7. 结构体字段访问
【单选题】在 Go 语言中,如果你有一个结构体实例 `myStruct`,你如何访问其字段 `field1` 的值?(5分)
A. `myStruct[field1]`
B. `myStruct->field1`
C. `myStruct.field1`
D. `*myStruct.field1`
**正确答案:**C
**作答答案:**C
> [!note] Go 没有箭头运算符
> Go 不使用 `->`(C/C++ 风格),也不使用 `[key]`(像 map 那样)。统一用点号 `.` 访问字段。当持有指针 `p` 时,也只需 `p.Field`,编译器自动解引用。
---
#### 8. 结构体方法的错误认知
【单选题】以下关于结构体方法的说法,错误的是(5分)
A. 值接收者方法不能修改结构体的字段值
B. 指针接收者方法可以修改结构体的字段值
C. 值接收者方法在传递参数时效率更高
D. 指针接收者方法在结构体较大时更节省内存
**正确答案:**C
**作答答案:**C
> [!warning] 值接收者的陷阱
> - A ✅:值接收者是副本,修改不影响原对象
> - B ✅:指针接收者直接操作原对象
> - C ❌:**这是本题答案**。值接收者在传参时需要复制整个结构体,当结构体较大时反而更慢
> - D ✅:指针只传 8 字节(ptr),避免完整复制
---
#### 9. 接口实现的完整性
【单选题】Go 中如何正确实现一个接口?(5分)
A. 只需要声明接口中的所有方法
B. 只需要部分实现接口中的方法
C. 需要声明并实现接口中的所有方法
D. 接口不能被实现,只能被继承
**正确答案:**C
**作答答案:**C
> [!info] Go 接口是隐式实现
> Go 没有 `implements` 关键字。只要一个类型的**所有方法签名**与接口一致,就自动实现了该接口。缺少任一方法都会编译报错。
---
#### 10. JSON 处理的标准库
【单选题】在 Go 中处理 JSON 数据的推荐方法是?(5分)
A. 使用 `encoding/json` 标准库
B. 使用外部包如 `jsoniter`
C. 手动解析字符串
D. 使用 `bufio` 读取器
**正确答案:**A
**作答答案:**A
> [!info] encoding/json
> Go 标准库 `encoding/json` 提供 `Marshal` / `Unmarshal` 两个核心函数,通过结构体 tag(`` `json:"name"` ``)控制序列化行为,满足绝大多数场景需求。
---
#### 11. 格式化输出的函数选择
【单选题】在 Go 中,如何通过 fmt 包输出格式化的字符串?(5分)
A. `fmt.Print()`
B. `fmt.Println()`
C. `fmt.Printf()`
D. `fmt.Format()`
**正确答案:**C
**作答答案:**C
> [!tip] fmt 包常用函数速记
> - `Print`:无换行,无格式
> - `Println`:自动追加换行
> - `Printf`:**支持占位符格式化**,如 `%d`、`%s`、`%v`
---
#### 12. 读取文件的便捷方式
【单选题】选择 Go 语言中正确的方式来读取文件。(5分)
A. `os.ReadFile()`
B. `os.OpenFile()`
C. `ioutil.ReadAll()`
D. `file.Read()`
**正确答案:**A
**作答答案:**A
> [!note] ioutil 已废弃
> `ioutil.ReadAll` 和 `ioutil.ReadFile` 在 Go 1.16+ 中被迁移到 `io` 和 `os` 包中。推荐写法:`os.ReadFile("path")` 一次性读取全部文件内容到 `[]byte`。
---
#### 13. 时间日期的标准库
【单选题】在 Go 中,用于处理时间和日期的标准库是哪一个?(5分)
A. `time`
B. `date`
C. `timer`
D. `clock`
**正确答案:**A
**作答答案:**A
> [!info] time 包核心用法
> ```go
> now := time.Now() // 当前时间
> time.Sleep(1 * time.Second) // 休眠
> t.Format("2006-01-02 15:04:05") // 格式化(记忆:参考时间 2006-01-02)
> ```
---
#### 14. new 的正确使用场景
【单选题】以下哪种情况应该使用 `new` 来分配内存?(5分)
A. 创建一个切片
B. 创建一个映射
C. 创建一个结构体指针
D. 扩展一个切片的容量
**正确答案:**C
**作答答案:**C
> [!info] new vs make 对比
> | 用途 | new(T) | make(T, ...)|
> |------|--------|-------------|
> | 返回类型 | `*T`(指针) | T 本身 |
> | 适用于 | 所有类型 | slice, map, channel |
> | 初始化 | 零值 | 完整初始化 |
> | 典型场景 | `new(Person)` | `make([]int, 10)` |
---
#### 15. 无缓冲 channel 的同步语义
【单选题】无缓冲 channel 的特点是什么?(5分)
A. 发送操作永远不会阻塞
B. 接收操作永远不会阻塞
C. 发送和接收操作必须同时发生
D. 可以存储无限数量的值
**正确答案:**C
**作答答案:**C
> [!important] 无缓冲 Channel = 同步通道
> - 容量为 0,send 和 receive **必须同时就绪**才能完成
> - 生产者 send 后阻塞直到有消费者 receive
> - 消费者 receive 后阻塞直到有生产者 send
> - 这是 goroutine 间最原子的同步机制
---
#### 16. Select 语句的作用
【单选题】Go 语言中的 `select` 语句用于什么目的?(5分)
A. 声明变量
B. 捕获异常
C. 监听多个 Channel 操作
D. 同步 Goroutine
**正确答案:**C
**作答答案:**C
> [!info] select 的行为规则
> - 每个 case 必须是 channel 的 send 或 receive 操作
> - 所有 case 求值后,随机选择一个可用的执行
> - 若无可用 case 且有 `default`,立即执行 default
> - 若无可用 case 且无 default,阻塞直到某个 case 就绪
---
#### 17. Goroutine 同步的原语选择
【单选题】在 Go 语言中,哪个 sync 原语可以用于确保多个 Goroutine 的同步执行?(5分)
A. `WaitGroup`
B. `Mutex`
C. `Atomic`
D. `Context`
**正确答案:**A
**作答答案:**A
> [!info] 各 sync 原语分工
> - `sync.WaitGroup`:**等待一组 goroutine 完成**(Add / Done / Wait)
> - `sync.Mutex`:互斥锁,保护临界区
> - `sync/atomic`:原子操作,无锁并发
> - `context.Context`:取消信号与超时控制
---
#### 18. 取地址运算符
【单选题】在 Go 中获取变量的内存地址应使用什么运算符?(5分)
A. `*`
B. `&`
C. `#`
D. `@`
**正确答案:**B
**作答答案:**B
> [!tip] 指针运算口诀
> - `&x`:取 x 的地址
> - `*p`:解除 p 指向,访问目标值
> - 二者互为逆运算:`*&x == x`,`&*p == p`
---
#### 19. RWMutex vs Mutex
【单选题】在 Go 中,`sync.RWMutex` 与 `sync.Mutex` 的主要区别是?(5分)
A. Mutex 只支持读操作
B. RWMutex 允许多个读锁或一个写锁,Mutex 仅支持互斥访问
C. 两者没有区别
D. RWMutex 不能与 channel 同时使用
**正确答案:**B
**作答答案:**B
> [!info] 读写锁的性能意义
> 当读多写少时,`RWMutex` 显著优于 `Mutex`:多个 goroutine 可同时持有多把读锁 (`RLock`),只有写操作 (`Lock`) 时才互斥。这是典型的「读并发、写串行」优化手段。
---
#### 20. 使用指针接收者的原因
【单选题】以下结构体方法定义中,使用指针接收者的原因通常是(5分)
A. 方法内部需要修改结构体的字段值
B. 为了提高性能(避免大结构体拷贝)
C. 为了遵循代码规范
D. 以上都是
**正确答案:**D
**作答答案:**D
> [!info] 何时用指针接收者?
> 1. 需要**修改**原对象状态
> 2. 结构体**较大**,减少拷贝开销
> 3. 需要保证**一致性**(同一个对象的方法要么全用指针,要么全用值接收者)
---
#### 21. 值接收者方法不会修改原对象
【单选题】对于以下结构体和方法定义,调用 ChangeName 方法后,原始的 Person 结构体实例的 Name 字段会改变吗?(5分)
```language
type Person struct {
Name string
Age int
}
func (p Person) ChangeName(newName string) {
p.Name = newName
}
```
A. 会
B. 不会
C. 不确定
D. 以上都不对
**正确答案:**B
**作答答案:**B
> [!warning] 经典面试题
> `ChangeName` 使用**值接收者**,这意味着 `p` 是原对象的**副本**。方法内修改 `p.Name` 只会影响副本,原对象不受影响。若希望修改生效,应将接收者改为指针 `(p *Person)`。
---
### 二、多选题
#### 1. 指针访问成员变量的方式
【多选题】通过指针变量 p 访问其成员变量 name,有哪几种方式?(5分)
A. `p.name`
B. `(&p).name`
C. `(*p).name`
D. `p->name`
**正确答案:**AC
**作答答案:**AC
> [!info] 指针访问字段两种方式
> - `p.name` ✅:Go 语法糖,编译器自动解引用,推荐写法
> - `(*p).name` ✅:先解引用再 `.name`,等价但稍显繁琐
> - `(&p).name` ❌:`&p` 是指针的地址(双重指针 `**P`),不符合题意
> - `p->name` ❌:Go 没有 `->` 运算符
---
#### 2. panic 和 recover 的使用
【多选题】关于 Go 语言中 panic 和 recover 的使用,以下正确的是?(5分)
A. panic 可以在任何地方引发,但 recover 只有在 defer 调用的函数中有效
B. recover 用于终止 panic 的异常传递过程
C. panic 无法被捕获,会直接导致程序崩溃
D. 使用 panic 和 recover 可以替代传统的错误处理方式
**正确答案:**AB
**作答答案:**ABC
> [!warning] 易错辨析
> - A ✅:recover 放在 defer 中能捕获当前 goroutine 的 panic,阻止程序崩溃
> - B ✅:recover 被调用后,panic 链中断,程序恢复正常运行
> - C ❌:panic **可以**被 recover 捕获,并非一定会导致程序崩溃
> - D ❌:Go 设计哲学中,panic 用于**真正的异常情况**(如编程错误、初始化失败),业务错误应用 error 返回
---
#### 3. 数组和切片的综合理解
【多选题】在 Go 语言中,关于数组(array)和切片(slice),以下说法正确的是?(5分)
A. 数组定义时必须指定长度,且长度是数组类型的一部分
B. 切片定义时不需要指定长度,其长度可以在运行时动态变化
C. 数组作为函数参数传递时,传递的是数组的副本,函数内对数组的修改不会影响原始数组
D. 切片作为函数参数传递时,传递的是切片结构体的副本,函数内对切片的修改会反映到原始切片上
**正确答案:**ABCD
**作答答案:**ABCD
> [!important] 四个选项全对的原因
> - A ✅:`[3]int` 和 `[5]int` 是不同长度决定类型
> - B ✅:slice 本质是「指针+长度+容量」描述符
> - C ✅:数组传值是**深拷贝**整个数组内容
> - D ✅:slice 内部含底层数组指针,虽然 slice 自身是副本(ptr/len/cap 三件套被拷贝),但对 `slice[i] = x` 等元素级修改通过 ptr 反映到底层数组,因此源数据可见
## 关联笔记
- [[hhs/EXAM/Week01]]
- [[hhs/EXAM/Week05]]
- [[hhs/EXAM/Week06]]