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

328 lines
11 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, 后端开发, 武科大, 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]]