4. 复合数据类型:数组、切片与结构体

在 Go 语言中,复合数据类型由基本类型组合而成,用于表达更复杂的业务结构。本章聚焦于 数组、切片(slice)、映射(map) 和 结构体(struct),这些类型是构建报表接口编排中数据模型、缓存队列和请求参数的核心载体。

4.1 数组:固定长度的连续内存块

数组是长度固定的、元素类型相同的数据序列。在 Go 中,数组的长度是类型的一部分,因此 [3]int 和 [5]int 被视为不同类型。

特性说明示例
声明var arr [5]int默认元素为零值(0)
初始化arr := [3]int{1,2,3}长度由编译器推导
访问arr[0]索引从 0 开始
长度len(arr)编译期常量
传递值传递函数内修改不影响原数组
避坑提示: 数组作为函数参数时,整个数组会被复制。对于大数组,应使用切片或指针避免性能开销。
// 数组声明与遍历
var days [7]string = [7]string{"Mon","Tue","Wed","Thu","Fri","Sat","Sun"}
for i, v := range days {
    fmt.Printf("索引 %d: %s\n", i, v)
}

4.2 切片(slice):动态数组的底层原理与操作

切片是对数组的抽象,提供动态长度的序列视图。其底层结构包含三个字段:指针(ptr)、长度(len) 和 容量(cap)。

4.2.1 底层结构

切片本身是一个描述符,指向底层数组的某个起始位置。当切片长度超过容量时,Go 运行时会分配一个新的底层数组并将原数据复制过去。

// 切片底层结构示意(伪代码)
type slice struct {
    ptr unsafe.Pointer // 指向底层数组的指针
    len int            // 当前元素个数
    cap int            // 底层数组总容量
}
关键理解: 多个切片可以共享同一个底层数组,修改一个切片中的元素会影响其他共享该数组的切片。

4.2.2 切片操作

操作语法说明
声明var s []intnil 切片,无底层数组
字面量s := []int{1,2,3}自动创建底层数组
makes := make([]int, 5, 10)len=5, cap=10
追加s = append(s, 4)可能触发扩容
截取sub := s[1:3]左闭右开,共享底层数组
复制copy(dst, src)按最小长度复制
// 切片扩容示例
s := make([]int, 0, 2)
fmt.Printf("初始: len=%d cap=%d\n", len(s), cap(s))
for i := 0; i < 5; i++ {
    s = append(s, i)
    fmt.Printf("追加 %d: len=%d cap=%d\n", i, len(s), cap(s))
}
// 输出显示 cap 在超过 2 后翻倍为 4,再翻倍为 8

4.2.3 切片与数组的关系

切片与底层数组关系图 底层数组 [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] 切片 A: ptr→[2], len=4, cap=8 指向索引 2 切片 B: ptr→[6], len=2, cap=4 指向索引 6 共享底层数组的注意事项 • 切片 A 修改 arr[2] 会影响切片 B 吗?不会,因为 B 不包含该索引。 • 切片 A 扩容后若分配新数组,则与 B 脱离共享关系。

4.3 映射(map):键值对的无序集合

map 是 Go 内置的哈希表实现,提供 O(1) 平均复杂度的键值查找。声明时必须指定键类型和值类型,键类型必须可比较(支持 == 和 !=)。

4.3.1 声明与初始化

// 方式一:make 函数
var m1 map[string]int
m1 = make(map[string]int)

// 方式二:字面量
m2 := map[string]int{
    "alice": 90,
    "bob":   85,
}

// 方式三:nil map(不能直接写入)
var m3 map[string]int
// m3["key"] = 1  // panic: assignment to entry in nil map

4.3.2 常用操作

操作代码说明
插入/更新m["key"] = value键不存在则插入,存在则覆盖
读取v := m["key"]键不存在返回零值
安全读取v, ok := m["key"]ok 为 bool,表示键是否存在
删除delete(m, "key")键不存在则无操作
长度len(m)返回键值对数量
遍历for k, v := range m {}遍历顺序随机,不保证有序
并发安全警告: map 不是并发安全的。在多个 goroutine 同时读写同一个 map 时,必须使用 sync.Mutex 或 sync.Map。
// 安全读取与删除
score := map[string]int{"zhang":95, "li":88}
if v, ok := score["wang"]; ok {
    fmt.Println("wang 的分数:", v)
} else {
    fmt.Println("wang 不存在")
}
delete(score, "li")

4.4 结构体(struct):自定义复合类型

结构体将多个字段组合成一个逻辑单元,是 Go 面向对象编程的基础。结构体可以定义方法,但 Go 没有类继承,而是通过组合实现复用。

4.4.1 定义与实例化

// 定义结构体
type ReportTask struct {
    ID        int
    Name      string
    Endpoints []string
    Timeout   time.Duration
}

// 实例化方式
t1 := ReportTask{ID: 1, Name: "日报"}
t2 := ReportTask{2, "周报", nil, 30 * time.Second} // 按字段顺序
var t3 ReportTask
t3.ID = 3

4.4.2 结构体方法

方法是一种特殊的函数,拥有接收者(receiver)。接收者可以是值类型或指针类型。

接收者类型特点适用场景
值接收者方法内修改不影响原实例只读操作、小型结构体
指针接收者方法内修改影响原实例修改字段、避免复制大结构体
// 值接收者方法
func (t ReportTask) Duration() time.Duration {
    return t.Timeout
}

// 指针接收者方法
func (t *ReportTask) SetTimeout(d time.Duration) {
    t.Timeout = d
}

// 使用
task := ReportTask{Timeout: 10 * time.Second}
fmt.Println(task.Duration()) // 10s
task.SetTimeout(20 * time.Second)
fmt.Println(task.Duration()) // 20s

4.4.3 结构体嵌套与组合

Go 通过结构体嵌套实现组合,被嵌套的字段可以提升到外层直接访问。

type BaseConfig struct {
    Host string
    Port int
}

type ReportService struct {
    BaseConfig               // 字段提升
    ServiceName string
}

svc := ReportService{
    BaseConfig: BaseConfig{Host: "10.0.0.1", Port: 8080},
    ServiceName: "daily-report",
}
fmt.Println(svc.Host) // 直接访问提升字段: 10.0.0.1

4.5 复合数据类型在报表编排中的应用

在接口编排场景中,复合数据类型用于构建请求参数、缓存中间结果和定义编排节点。

// 编排节点定义
type OrchestrationNode struct {
    ID          string
    DependsOn   []string          // 依赖的上游节点 ID
    Request     map[string]interface{} // 请求参数
    RetryPolicy struct {
        MaxRetries int
        Backoff    time.Duration
    }
}

// 报表上下文
type ReportContext struct {
    TaskID    string
    Input     map[string]interface{}
    Cache     map[string][]byte   // 中间结果缓存
    Nodes     []OrchestrationNode
    StartTime time.Time
}
最佳实践: 在编排引擎中,使用 map[string]interface{} 作为通用参数容器,配合结构体方法实现参数校验和默认值填充,可大幅提升代码可维护性。

4.6 本章小结

  • 数组:固定长度,值传递,适合已知大小的常量集合。
  • 切片:动态长度,引用底层数组,注意扩容机制和共享陷阱。
  • map:无序键值对,安全读取使用 v, ok 模式,非并发安全。
  • 结构体:自定义类型,支持值/指针接收者方法,通过嵌套实现组合。