Files
cs-note/hzh/EXAM/Week01.md
T

328 lines
11 KiB
Markdown
Raw Normal View History

2026-05-24 11:42:38 +08:00
---
tags: [Go, 后端开发, 武科大, Week01, 错题精讲]
create time: 2026-05-02 14:30
---
# 金山考试 - 武科大服务端 - 第一次课 · 错题精讲
## 概述
本次测试涵盖 **Git、字符编码、Go 语言基础语法(注释/可见性/变量声明/switch/for/循环/自增/布尔判断/init/defer/panic/recover/字符串不可变/包管理/flag/command-line/函数签名)**三大模块,共 30 道题(20 单选 + 10 多选)。其中 25 道答对,**5 道错题**,集中在 `defer` 执行顺序、下划线 `_` 语义、以及 `switch/for` 语句特性三个知识面上。本笔记逐一拆解错误原因,给出正解辨析和代码验证。
> [!tip] 使用建议
> 先看错题本身的「为什么错 → 为什么对」,再做对照表的自测检查,最后用「高频考点速览」串联记忆薄弱点。
---
## 一、defer 的执行机制与有名返回值
### 单7:有名返回值 + defer 的输出
| 项目 | 内容 |
|------|------|
| ❌ 你的答案 | D(以上都不是) |
| ✅ 正确答案 | **C(3)** |
```go
func DeferTest2(i int) (r int) { // r 为有名返回值,默认零值 = 0
defer func() {
r += i // defer 修改的是 r 的引用
}()
return 2 // 返回值为 2
}
// fmt.Println(DeferTest2(1)) // ?
```
> [!question] 💡 思考
> `return 2` 之后发生了什么?为什么结果是 3 而不是 2?
**关键点:return 不是一条原子操作。** Go 中的 `return` 分两步执行:
```
步骤1: 将返回值 2 赋给命名返回值 r → r = 2
步骤2: 执行所有已注册的 defer 函数 → r += 1 → r = 3
步骤3: 函数实际返回 r 的最终值 → 返回 3
```
> [!warning] ⭐ 高频易错陷阱
> 如果函数使用**有名返回值**(`func foo() (x int)`),`return` 先赋值再执行 defer,defer **可以修改**最终返回值。
> 即使是**匿名返回值**(`func foo() int { return 2 }`),Go 内部也会生成一个隐式命名返回值变量,defer 同样能修改它——真正无法被 defer 修改的是通过**参数传入的指针所指向的值**以外的其他场景。核心要点:**只要返回值是变量(无论显式声明还是隐式),defer 都能修改它。**
```go
// ✅ 有名返回值 — defer 可以修改返回值(显式)
func withNamedReturn() (r int) {
defer func() { r++ }()
return 1 // 返回 2(r 被 defer 从 1→2)
}
// ✅ 匿名返回值 — defer 同样能修改(Go 内部有隐式命名返回值变量)
func withAnonymousReturn() int {
defer func() {}() // 即使不修改,defer 也正常执行
return 1 // 返回 1
}
// ⚠️ 如果想在 defer 中修改值,必须捕获那个隐式变量
func deferModifiesAnonymous() int {
defer func(r int) { /* r 是值拷贝,改了也没用 */ }(0)
return 1 // 返回 1,传入的是值拷贝,不影响真正返回值
}
```
<details>
<summary>延伸思考:多个 defer 叠加有名返回值</summary>
```go
func MultiDefer() (result int) {
defer func() { result++ }() // 第2个执行:result = 3 → 4
defer func() { result++ }() // 第1个执行:result = 3
return 2 // result 先被设为 2
}
// 输出:4
// 流程:return 2 → r=2 → defer#1(r++)→3 → defer#2(r++)→4
```
</details>
---
## 二、下划线 `_` 的含义
### 单16:下划线在 Go 中的作用
| 项目 | 内容 |
|------|------|
| ❌ 你的答案 | D(以上都不对) |
| ✅ 正确答案 | **A(作为变量名,用于存储无用的值)** |
> [!abstract] _ 的本质:空白标识符(Blank Identifier)
>
> `_` 是一个特殊的标识符,表示**我有意忽略这个值**。它让编译器不报"未使用变量"的错误。
```go
val, err := someFunction()
if err != nil { /* 处理错误 */; return }
_ = val // 明确告诉编译器:我知道 val 没被用到,别警告
for _, v := range slice {
fmt.Println(v) // 索引我不需要,用 _ 丢弃
}
```
> [!note] _ 不是什么
> | 误解 | 事实 |
> |------|------|
> `_` 是私有标记 | ❌ Go 用首字母大小写控制可见性,与 `_` 无关 |
> `_` 是注释符号 | ❌ Go 的注释是 `//` 和 `/* */` |
> `_` 能读它的值 | ❌ 从 `_` 读取值会导致编译错误 |
```go
// 以下全部编译报错
// x := _ // invalid use of _
// print(_) // invalid use of _
```
---
## 三、switch 语句的特性
### 多选2 & 多选8:关于 switch 的正确描述
> 两道题反复考查同一个知识点,说明这是出题人的重点。
| 项目 | 内容 |
|------|------|
| ❌ 你的答案(多选2) | CD |
| ✅ 正确答案(多选2) | **AC** |
| ❌ 你的答案(多选8) | AD |
| ✅ 正确答案(多选8) | **BD** |
> [!summary] switch 四条铁律
| # | 说法 | 对错 | 解析 |
| --- | ------------------------------------ | --- | ----------------------------------------- |
| 1 | 单个 case 可出现多个结果选项(如 `case 1, 2, 3:`) | ✅ 对 | case 值用逗号分隔即可 |
| 2 | 需要使用 break 来明确退出一个 case | ❌ 错 | Go 的 switch **自动 break**,不需要也不推荐显式写 break |
| 3 | 只有添加 fallthrough 才会继续执行下一个 case | ✅ 对 | fallthrough 是唯一"穿透"方式 |
| 4 | 条件表达式必须为常量或整数 | ❌ 错 | Go 的 switch 不限于常量/整数,支持任意类型表达式 |
```go
// 铁律1:多值 case
switch score {
case 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100:
fmt.Println("优秀")
}
// 铁律2:自动 break,无需显式退出
switch i {
case 1:
fmt.Println("one") // 执行完自动跳出,不会 fallthrough
case 2:
fmt.Println("two")
}
// 铁律3:fallthrough 才穿透
switch i {
case 1:
fmt.Println("one")
fallthrough
case 2:
fmt.Println("one or two") // i=1 时也会执行这一行
}
// 铁律4:不限于常量/整数
name := "Alice"
switch name {
case "Alice", "Bob": // 字符串同样支持
fmt.Println("known user")
}
age := 20
switch true { // 条件表达式可以是任意表达式
case age >= 18:
fmt.Println("adult")
}
```
> [!question] 💡 代码追踪
> 下面的输出是什么?
```go
func main() {
isMatch := func(i int) bool {
switch(i) {
case 1:
fallthrough
case 2:
return true
}
return false
}
fmt.Println(isMatch(1)) // ?
fmt.Println(isMatch(2)) // ?
}
```
<details>
<summary>点击查看答案解析</summary>
两个都输出 `true`。当 `i=1` 时,匹配第一个 case 后 `fallthrough` 到第二个 case 返回 `true`;当 `i=2` 时直接命中第二个 case,同样返回 `true`。
</details>
---
## 四、函数声明语法
### 多选5:函数声明的正确写法
| 项目 | 内容 |
|------|------|
| ❌ 你的答案 | ABCD(全选) |
| ✅ 正确答案 | **ABD** |
> [!error] 你的失误点
> 你误选了 **C**:`func f(a, b int) (value int, error)` — 这个写法是错误的!
```go
// ✅ A:合并同名参数 + 具名返回值
func f(a, b int) (value int, err error) {}
// ✅ B:分别声明参数类型 + 具名返回值
func f(a int, b int) (value int, err error) {}
// ❌ C:error 缺少类型名 —— 每个返回值都必须写明类型!
// func f(a, b int) (value int, error) {}
// ^ 这里 error 前面缺了变量名
// ✅ D:匿名返回值(不需要在括号里写参数名)
func f(a int, b int) (int, int, error) {}
```
> [!memory] 返回值声明规则
>
> | 形式 | 写法示例 | 说明 |
> |------|---------|------|
> | 全具名 | `(value int, err error)` | 每个返回值都有变量名 |
> | 全匿名 | `(int, string, error)` | 只写类型,括号内无变量名 |
> | 混用 | `(value int, error)` | ✅ Go 允许部分有名、部分无名 |
>
> 核心:**C 选项的错误不在「混用」,而在语义混淆——`error` 既是包名又是接口名,作为匿名返回值类型时虽合法,但原题出题意图是考查「常见做法应使用具名返回值以提升可读性」。实际考试中 AB D 为预期答案。**
---
## 五、for 循环的特性
### 多选9:关于循环语句的正确描述
> (此题你答对了 ✅,放在这里作巩固复习)
| 项目 | 内容 |
|------|------|
| ✅ 正确答案 | **CD** |
```go
// Go 只有 for,没有 while / do-while
// for 支持 continue 和 break
// break 可以配合标签跳出指定循环层
// 不能用逗号分隔的多重初始化,必须用并行赋值
```
> [!abstract] for 循环标签化 break
>
> ```mermaid
> flowchart TD
> A["外层 for i"] --> B["内层 for j"]
> B --> C{"j == 3?"}
> C -->|是| D["break Outer"]
> D --> E["跳出外层,继续后续代码"]
> C -->|否| F["j++"]
> F --> B
> ```
```go
Outer:
for i := 0; i < 5; i++ {
for j := 0; j < 5; j++ {
if j == 3 {
break Outer // 同时跳出两层循环
}
}
}
```
---
## 六、错题自测表
> [!summary] 完成各专题学习后,遮住右边自己测试
| 题目 | 考查点 | ❌ 你的原答案 | ✅ 正确答案 | 关键概念 |
|------|--------|-------------|------------|---------|
| 单7 | 有名返回值 + defer 修改返回值 | D | **C (3)** | return 分两步:赋值 → defer 执行 |
| 单16 | 下划线 `_` 的含义 | D | **A(存储无用值)** | `_` = 空白标识符,用于丢弃 |
| 多选2 | switch 特性(首次) | CD | **AC** | case 多值✅ 自动 break❌ |
| 多选5 | 函数返回值声明 | ABCD | **ABD** | error 不能孤立出现 |
| 多选8 | switch 特性(再次考查) | AD | **BD** | case 多值✅ 需 fallthrough 才穿透 |
---
## 高频考点速览
> [!tip] 考前快速过一遍
| 类别 | 考点 | 一句话口诀 |
|------|------|-----------|
| defer + named return | defer 在 return 赋值后才执行 | **return 先存值,defer 再动手** |
| 下划线 `_` | 空白标识符,只用于丢弃 | **"我不要这个,别报警"** |
| switch case 多值 | `case 1, 2, 3:` 一行搞定 | 逗号分隔即多匹配 |
| switch 自动 break | Go 不需要 break | **不用 break 也不会穿透** |
| switch fallthrough | 唯一手动穿透方式 | **不加 fallthrough 就断** |
| switch 表达式类型 | 不限常量/整数 | **随便什么表达式都行** |
| 返回值类型书写 | 必须 "(名 类型)" 或纯类型 | **缺一不可** |
| for 标签 break | `break Label` 跳出指定层 | **多层循环的神器** |
---
## 关联笔记
- [[hzh/EXAM]]