数组、切片、Map、结构体——Go 的复合数据结构
一、数组(Array)
Go 的数组是固定长度、同类型的值类型。
1.1 声明与初始化
// 声明:长度是类型的一部分
var arr [5]int // [0 0 0 0 0]
arr := [5]int{1, 2, 3, 4, 5}
arr := [...]int{1, 2, 3} // 编译器自动推断长度 = 3
// 指定索引初始化
arr := [5]int{1: 10, 3: 30} // [0 10 0 30 0]
1.2 数组特点
// 长度是类型的一部分
var a [3]int = [3]int{1, 2, 3}
var b [3]int = [3]int{1, 2, 3}
// a == b ✅ 同类型、同长度、同元素 → 相等
var c [4]int = [4]int{1, 2, 3, 4}
// a == c ❌ 编译错误:类型不同([3]int != [4]int)
// 值类型:赋值和传参会复制整个数组
arr1 := [3]int{1, 2, 3}
arr2 := arr1 // arr2 是 arr1 的完整副本
arr2[0] = 100 // 不影响 arr1
fmt.Println(arr1) // [1 2 3]
// ⚠️ 实际开发中很少直接用数组,切片(slice)更常用
1.3 多维数组
matrix := [2][3]int{
{1, 2, 3},
{4, 5, 6},
}
fmt.Println(matrix[1][2]) // 6
二、切片(Slice)— 最核心的数据结构
切片是 Go 中最常用的复合类型。它是一个对底层数组的引用。
2.1 切片的内部结构
slice = { ptr, len, cap }
│ │ │
│ │ └─ 底层数组总容量
│ └─ 当前元素个数
└─ 指向底层数组的指针
// 用 reflect 包可以查看 slice 的内部结构
s := []int{1, 2, 3}
header := (*reflect.SliceHeader)(unsafe.Pointer(&s))
fmt.Printf("addr=%x len=%d cap=%d\n", header.Data, header.Len, header.Cap)
2.2 创建切片
// 1. 字面量(最常用)
s := []int{1, 2, 3, 4, 5}
// 2. make 创建指定长度/容量的切片
s := make([]int, 5) // len=5, cap=5, 全零
s := make([]int, 0, 10) // len=0, cap=10,预分配容量
// 3. 从数组/切片派生
arr := [5]int{10, 20, 30, 40, 50}
s1 := arr[1:3] // [20 30],从索引1到索引3(不含)
s2 := arr[:3] // [10 20 30]
s3 := arr[2:] // [30 40 50]
s4 := arr[:] // [10 20 30 40 50],整个数组
// 4. 从已有切片创建子切片
sub := s[1:3] // 共享底层数组!
sub[0] = 99 // 原切片 s 也会被修改
2.3 append — 动态增长
s := []int{1, 2, 3}
fmt.Println(len(s), cap(s)) // 3 3
// 追加单个元素
s = append(s, 4)
fmt.Println(len(s), cap(s)) // 4 6(容量翻倍增长)
// 追加多个元素
s = append(s, 5, 6, 7)
// 合并两个切片
s2 := []int{8, 9}
s = append(s, s2...)
// ⚠️ append 可能触发扩容,返回新切片!必须用返回值
// s = append(s, 10) // ✅ 正确
// append(s, 10) // ⚠️ 返回值被丢弃,原 s 不变
2.4 copy — 安全复制
src := []int{1, 2, 3, 4, 5}
dst := make([]int, len(src))
n := copy(dst, src) // n = 5,返回复制的元素个数
// dst 是 src 的独立副本,修改互不影响
// 部分复制
dst2 := make([]int, 3)
copy(dst2, src[1:]) // [2 3 4]
2.5 删除元素
// 删除索引 i 的元素(不保留顺序,O(1))
s := []int{1, 2, 3, 4, 5}
i := 2 // 删除元素 3
s = append(s[:i], s[i+1:]...) // [1 2 4 5]
// 删除索引 i 的元素(保留顺序,O(n))
s = append(s[:i], s[i+1:]...)
// ⚠️ 从切片中间删除不会释放内存,底层数组不变
// 如果需要释放,复制到新切片
s = s[:len(s)-1] // 缩小 len,但 cap 不变
2.6 切片陷阱
// 陷阱1:子切片共享底层数组
origin := []int{1, 2, 3, 4, 5}
sub := origin[:3]
sub = append(sub, 99) // origin 也变了![1 2 3 99 5]
// 陷阱2:make 时 len > 0 的影响
s1 := make([]int, 0, 5) // len=0, cap=5 → append 正常
s2 := make([]int, 5, 5) // len=5, cap=5 → append 从末尾追加
// 还是复制底层数组的内容,而不是共享
s3 := make([]int, 3, 5)
s4 := s3[:2]
s4 = append(s4, 100) // 修改了 s3 的第3个元素!
// 解决方案:三下标切片限制长度
s5 := s3[:2:2] // [low:high:max] → len=2, cap=2
s5 = append(s5, 100) // 安全,会触发扩容
三、Map — 哈希表
3.1 声明与初始化
// 声明(nil map,不能直接写入!)
var m map[string]int // nil map
// make 初始化
m = make(map[string]int)
m["age"] = 25 // ✅
// 字面量初始化
m := map[string]int{
"age": 25,
"score": 100,
}
// 创建空 map(推荐写法)
m := make(map[string]int) // 等价于 map[string]int{}
m := map[string]int{} // 等价于上面
3.2 基本操作
m := map[string]int{"Go": 1, "Java": 2}
// 读取
v := m["Go"] // 1
v, ok := m["Python"] // 0, false(ok=false 表示不存在)
// 写入/更新
m["Python"] = 3 // 不存在则新增,存在则覆盖
// 删除
delete(m, "Java") // 删除键 "Java"
// 长度
len(m) // 2
// 遍历
for key, value := range m {
fmt.Printf("%s: %d\n", key, value)
}
遍历顺序是随机的。 Go 故意打乱 map 遍历顺序,防止开发者依赖顺序。
3.3 Map 注意事项
// 1. nil map 读取返回零值,写入 panic
var m map[string]int
v := m["key"] // 0(零值,不 panic)
// m["key"] = 1 // ❌ panic: assignment to entry in nil map
// 2. map 是引用类型
m1 := map[string]int{"a": 1}
m2 := m1 // m2 和 m1 指向同一个 map
m2["a"] = 99 // m1["a"] 也变成 99
// 3. map 不支持取地址
// &m["key"] // ❌ 编译错误
// 4. 并发不安全
// 多个 goroutine 同时读写同一个 map 会导致竞态
// 解决方案:sync.Mutex 或 sync.Map(并发专题详述)
四、结构体(Struct)— Go 的"类"
Go 没有 class,struct + 方法 实现了面向对象的核心能力。
4.1 定义与使用
// 定义结构体
type Person struct {
Name string
Age int
Email string
}
// 创建实例
p1 := Person{Name: "Alice", Age: 30, Email: "alice@example.com"}
p2 := Person{"Bob", 25, "bob@example.com"} // 按字段顺序赋值
p3 := new(Person) // 返回 *Person(指针),字段零值
// 访问和修改字段
fmt.Println(p1.Name) // Alice
p1.Age = 31
4.2 值类型 vs 指针
// 结构体是值类型,赋值和传参会复制
func modify(p Person) {
p.Name = "Modified" // 修改的是副本
}
func modifyPtr(p *Person) {
p.Name = "Modified" // 修改的是原始值
}
p := Person{Name: "Alice"}
modify(p)
fmt.Println(p.Name) // Alice(未变)
modifyPtr(&p)
fmt.Println(p.Name) // Modified(已变)
4.3 结构体标签(Tag)
结构体字段可以附加元数据标签,常用于 JSON 序列化、ORM 映射等。
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Password string `json:"-"` // `-` 表示忽略该字段
Email string `json:"email,omitempty"` // 空值时省略
CreatedAt string `json:"created_at" db:"created_at"`
}
// JSON 序列化
u := User{ID: 1, Name: "Alice", Password: "secret"}
data, _ := json.Marshal(u)
fmt.Println(string(data))
// {"id":1,"name":"Alice"} (Password 被忽略)
4.4 匿名字段(嵌入)
// 匿名字段实现"组合"(Go 用组合代替继承)
type Address struct {
City string
Country string
}
type Employee struct {
Person // 匿名嵌入(没有字段名)
Address // 可以嵌入多个
Dept string
}
e := Employee{
Person: Person{Name: "Alice", Age: 30},
Address: Address{City: "Beijing", Country: "China"},
Dept: "Engineering",
}
// 可以直接访问嵌入字段(提升)
fmt.Println(e.Name) // Alice(来自 Person)
fmt.Println(e.City) // Beijing(来自 Address)
// 同名字段需要显式指定
fmt.Println(e.Person.Name) // 显式通过类型访问
这就是 Go 的组合优于继承——没有
extends,用嵌入实现代码复用。
五、指针
5.1 基本用法
x := 42
p := &x // p 的类型是 *int,指向 x
fmt.Println(*p) // 42(解引用)
*p = 100 // 通过指针修改 x
fmt.Println(x) // 100
5.2 new 与 make
| 函数 | 用途 | 返回值 |
|---|---|---|
new(T) | 分配内存,返回 *T(零值初始化) | 指针 |
make(T, len, cap) | 初始化 slice/map/channel,返回 T | 值(非指针) |
// new:适用于结构体等
p := new(Person) // *Person,字段零值
// p := &Person{} // 等价写法,更常用
// make:仅适用于 slice/map/channel
s := make([]int, 10)
m := make(map[string]int)
ch := make(chan int)
// ⚠️ make 不能用于结构体,new 不能用于 slice/map/channel 的初始化
5.3 指针的优势
// 1. 避免复制大结构体
func processUser(user *User) { // 只传指针,不复制整个 User
user.Name = "updated"
}
// 2. 函数可以修改外部变量
func increment(n *int) {
*n++
}
// 3. nil 指针可以表示"空"
var p *int // nil,可以用于判断
六、本节要点速查
| 概念 | 要点 |
|---|---|
| 数组 | 固定长度、值类型,实际很少直接使用 |
| 切片 | ptr/len/cap 三元组,动态数组的引用 |
append | 可能触发扩容,必须 s = append(s, x) |
| 切片陷阱 | 子切片共享底层数组,修改会互相影响 |
| Map | m[key] 读取,delete(m, key) 删除,range 遍历顺序随机 |
| nil Map | 可读不可写,写入会 panic |
| Struct | 值类型,赋值/传参复制,方法用指针接收者修改 |
| Struct Tag | json:"name" 等元数据,用于序列化/ORM |
| 匿名嵌入 | Go 的组合机制,替代继承 |
new(T) | 返回 *T(零值指针) |
make(T) | 初始化 slice/map/channel |
豫公网安备41108202000982号