This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
2026-05-15 16:26:14 +08:00

15 KiB
Raw Permalink Blame History

tags, create time
tags create time
Go
考试
Week02
武科大服务端
2026-05-14 12:00

金山考试 - 武科大服务端-第二次课

概述

本次考试覆盖 Go 语言进阶核心知识点,包括切片(slice)、数组与切片的底层关系、Map 操作、结构体与方法、接口实现、标准库(JSON / 文件 / 时间)、指针与内存分配、并发编程(channel / select / sync)等。适合用于检验 Go 入门阶段学习成果。

知识点地图:

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分)

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 切片性能优势的关键——零拷贝传递数据。

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分)

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 包核心用法

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分)

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 反映到底层数组,因此源数据可见

关联笔记