4、Go语言基础回顾(下):数组与切片、map与结构体、指针、错误处理机制(error与panic/recover)
本章继续回顾Go语言的核心基础,聚焦于复合数据类型与运行时控制。数组与切片是Go中最常用的序列容器;map与结构体分别对应键值映射与数据聚合;指针提供了间接引用能力;而错误处理机制(error接口与panic/recover)则是构建健壮服务的关键。掌握这些基础,是后续开发任务调度平台OpenAPI服务的必要条件。
4.1 数组与切片:定长与动态序列
4.1.1 数组(Array)
数组是固定长度的同类型元素序列。长度是类型的一部分,[5]int 与 [10]int 是不同类型。数组在Go中是值类型,赋值或传参会复制整个数组。
// 声明与初始化
var arr1 [3]int // [0 0 0]
arr2 := [3]int{1, 2, 3} // [1 2 3]
arr3 := [...]int{10, 20, 30, 40} // 编译器推导长度: [4]int
// 访问与修改
arr2[0] = 100 // [100 2 3]
// 遍历
for i, v := range arr2 {
fmt.Printf("索引:%d 值:%d\n", i, v)
}
4.1.2 切片(Slice)
切片是对数组的动态视图,长度可变。切片本身是引用类型,包含指向底层数组的指针、长度(len)和容量(cap)。切片是Go中最常用的序列抽象。
| 操作 | 语法 | 说明 |
|---|---|---|
| 声明 | var s []int | nil切片,无底层数组 |
| 字面量 | s := []int{1,2,3} | 创建切片并初始化 |
| make | s := make([]int, 5, 10) | 长度5,容量10 |
| 追加 | s = append(s, 4, 5) | 可能触发扩容 |
| 截取 | sub := s[1:3] | 左闭右开,共享底层数组 |
| 复制 | copy(dst, src) | 复制元素,取较小长度 |
copy 或 append 在扩容时产生新底层数组可避免副作用。
// 切片扩容机制示例
s := make([]int, 0, 2)
fmt.Println(cap(s)) // 2
s = append(s, 1, 2)
fmt.Println(cap(s)) // 2
s = append(s, 3) // 触发扩容
fmt.Println(cap(s)) // 4 (通常翻倍)
4.2 map与结构体:键值映射与数据聚合
4.2.1 map(映射)
map是无序键值对集合,引用类型。键必须支持 == 比较(如string、int,不可用slice/map作为键)。使用 make 或字面量初始化,读写时需注意零值行为。
// 创建与操作
m := make(map[string]int)
m["cpu"] = 8
m["memory"] = 16384
// 安全读取
if val, ok := m["disk"]; ok {
fmt.Println("disk:", val)
} else {
fmt.Println("disk not found") // 输出
}
// 删除
delete(m, "cpu")
// 遍历(无序)
for k, v := range m {
fmt.Printf("%s: %d\n", k, v)
}
4.2.2 结构体(Struct)
结构体是聚合数据类型,将多个字段组合成一个整体。结构体是值类型,支持嵌套、方法绑定和标签(Tag)。
| 特性 | 说明 | 示例 |
|---|---|---|
| 定义 | 使用 type 关键字 | type Task struct { ID int; Name string } |
| 初始化 | 字面量或按字段赋值 | t := Task{ID: 1, Name: "build"} |
| 方法 | 值接收者/指针接收者 | func (t *Task) Run() { ... } |
| 标签 | 反射时使用,如JSON序列化 | Name string `json:"name"` |
| 嵌入 | 实现类似继承的效果 | type Job struct { Task; Timeout int } |
// 结构体定义与方法
type Task struct {
ID int `json:"id"`
Name string `json:"name"`
Status int `json:"status"`
}
// 指针接收者方法可修改原结构体
func (t *Task) Complete() {
t.Status = 2
}
// 使用
task := Task{ID: 1, Name: "deploy"}
task.Complete()
fmt.Printf("%+v\n", task) // {ID:1 Name:deploy Status:2}
4.3 指针:间接引用与高效传递
Go指针保存变量的内存地址,使用 & 取地址,* 解引用。与C不同,Go指针不支持算术运算,更安全。指针常用于:
- 修改函数外变量:通过指针参数实现“引用传递”效果。
- 避免大结构体复制:传递指针(8字节)比复制整个结构体更高效。
- 方法接收者:指针接收者允许方法修改接收者状态。
- nil指针检查:调用方法前需判断指针是否为nil。
// 指针基础
x := 42
p := &x // p是指向int的指针
fmt.Println(*p) // 42
*p = 100 // 通过指针修改x
fmt.Println(x) // 100
// 函数参数:指针传递
func increment(val *int) {
*val++
}
increment(&x)
fmt.Println(x) // 101
4.4 错误处理机制:error与panic/recover
4.4.1 error接口:显式错误处理
Go推崇显式错误处理,通过返回 error 接口值表示异常。error是内置接口:
type error interface {
Error() string
}
函数通常返回 (value, error) 元组,调用方必须检查error。标准库提供 errors.New 和 fmt.Errorf 创建错误。
// 自定义错误
type ValidationError struct {
Field string
Msg string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("字段 %s 验证失败: %s", e.Field, e.Msg)
}
// 使用
func validateTask(name string) error {
if name == "" {
return &ValidationError{Field: "Name", Msg: "不能为空"}
}
return nil
}
if err := validateTask(""); err != nil {
fmt.Println(err) // 字段 Name 验证失败: 不能为空
}
4.4.2 panic与recover:异常中断与恢复
panic 用于不可恢复的错误(如数组越界、空指针解引用),会立即中止当前函数并逐层向上传递,直到程序崩溃或被 recover 捕获。recover 仅在 defer 中有效,用于恢复程序执行。
| 机制 | 适用场景 | 注意事项 |
|---|---|---|
| error | 可预期的业务异常(如输入校验、文件不存在) | 调用方必须处理;支持错误链(Go 1.13+) |
| panic | 编程错误、不可恢复状态(如初始化失败、断言失败) | 不应作为常规错误处理;会导致程序退出 |
| recover | 在defer中捕获panic,防止服务完全崩溃 | 通常用于HTTP中间件、goroutine保护 |
// panic/recover 示例:保护goroutine
func safeRun(fn func()) {
defer func() {
if r := recover(); r != nil {
fmt.Printf("捕获panic: %v\n", r)
// 记录日志、清理资源,但不重新panic
}
}()
fn()
}
// 使用
safeRun(func() {
panic("模拟严重错误")
})
fmt.Println("服务继续运行") // 这行会执行
- 业务校验、数据库查询失败等可预期错误:返回error,由调用层转为HTTP错误码(如400/404/500)。
- 空指针、索引越界等编程错误:使用panic,在HTTP中间件中通过recover捕获,返回500并记录详细日志,防止服务完全终止。
- goroutine内的panic:必须在goroutine入口处使用defer+recover保护,避免整个进程崩溃。
4.5 本章小结
本章回顾了Go语言的五个核心基础:
- 数组与切片:数组定长值语义,切片动态引用语义,注意扩容与共享底层数组的副作用。
- map与结构体:map用于键值查询,结构体用于数据聚合,两者结合可构建复杂业务模型。
- 指针:提供间接引用,用于修改外部变量和避免大对象复制,但不可进行算术运算。
- 错误处理:error接口实现显式错误传播;panic/recover用于不可恢复异常的保护性恢复。
这些基础是后续开发任务调度平台OpenAPI服务的基石,尤其在定义请求/响应结构体、处理任务状态、保护服务稳定性方面将反复使用。