一、数组(Array)— 定长
数组是固定长度的,一旦声明就不能改变长度。
// 声明:var 变量名 [长度]类型
var nums [3]int
nums[0] = 10
nums[1] = 20
nums[2] = 30
// 声明 + 初始化
nums := [3]int{10, 20, 30}
// 让 Go 自己数长度
nums := [...]int{10, 20, 30} // 自动推断长度为 3
// 数组长度是类型的一部分
nums1 := [3]int{1, 2, 3}
nums2 := [5]int{1, 2, 3, 4, 5}
// nums1 = nums2 ← ❌ 编译错误!长度不同,类型不同
数组的局限: 长度固定,不够灵活。实际开发中很少直接用数组,更常用的是切片。
二、切片(Slice)— 动态数组 ✅ 最常用
切片是 Go 最常用的数据结构,类似于 Python 的 list 或 Java 的 ArrayList。
2.1 创建切片
// 方式1:字面量创建
names := []string{"Go", "Python", "Rust"}
// 方式2:make 创建
nums := make([]int, 3) // [0, 0, 0],长度 = 3
nums := make([]int, 3, 5) // 长度 = 3,容量 = 5
// 方式3:从数组切取
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4] // [2, 3, 4] 左闭右开
2.2 切片的增删改查
// 追加元素
nums := []int{1, 2, 3}
nums = append(nums, 4) // [1, 2, 3, 4]
nums = append(nums, 5, 6) // [1, 2, 3, 4, 5, 6]
// 合并两个切片
nums = append(nums, []int{7, 8}...) // ... 把切片展开
// 获取长度和容量
fmt.Println(len(nums)) // 8,实际元素个数
fmt.Println(cap(nums)) // 底层数组容量(自动扩容)
// 遍历
for i, v := range nums {
fmt.Printf("索引 %d = %d\n", i, v)
}
2.3 长度 vs 容量
nums := make([]int, 3, 5) // 长度 3,容量 5
// nums = [0, 0, 0, _, _]
// ↑ 长度 ↑ 容量(底层数组总大小)
fmt.Println(len(nums)) // 3
fmt.Println(cap(nums)) // 5
nums = append(nums, 1) // 还有容量,不会重新分配
nums = append(nums, 2) // [0, 0, 0, 1, 2]
nums = append(nums, 3) // 容量不够了 → 自动扩容为 2 倍
💡
len表示能访问多少个元素,cap表示底层数组能放多少个。追加元素时,如果没超过 cap 就不会重新分配内存,效率更高。
三、Map — 键值对
类似于其他语言的字典 / HashMap。
// 创建 map
// map[键类型]值类型
scores := make(map[string]int)
scores["张三"] = 90
scores["李四"] = 85
// 创建 + 初始化
scores := map[string]int{
"张三": 90,
"李四": 85,
"王五": 92,
}
// 取值(安全方式:带 ok 判断是否存在)
score, ok := scores["赵六"]
if ok {
fmt.Println("分数:", score)
} else {
fmt.Println("此人不存在")
}
// 删除
delete(scores, "李四")
// 遍历(注意:map 遍历顺序不固定!)
for name, score := range scores {
fmt.Printf("%s: %d\n", name, score)
}
⚠️ 遍历 Map 时顺序不固定,每次运行可能都不一样。如果需要固定顺序,先把 key 取出来排序再遍历。
四、结构体(struct)— 自定义类型
4.1 定义和使用
// 定义结构体
type User struct {
Name string
Age int
Email string
}
// 使用
u1 := User{"pc", 25, "pc@example.com"} // 按字段顺序(不推荐)
u2 := User{Name: "pc", Age: 25, Email: "pc@example.com"} // 指定字段名(推荐 ✅)
u3 := User{Name: "pc"} // 只赋值部分字段,其余为零值
// 访问字段
fmt.Println(u2.Name) // "pc"
u2.Age = 26 // 修改字段
4.2 结构体方法
Go 没有 class,但可以给 struct 绑定方法:
type User struct {
Name string
Age int
}
// 值接收者(不修改原始数据)
func (u User) SayHello() {
fmt.Printf("你好,我是 %s,今年 %d 岁\n", u.Name, u.Age)
}
// 指针接收者(可以修改原始数据)
func (u *User) Birthday() {
u.Age++
}
// 使用
u := User{Name: "pc", Age: 25}
u.SayHello() // 你好,我是 pc,今年 25 岁
u.Birthday() // Age 变成 26
u.SayHello() // 你好,我是 pc,今年 26 岁
💡 什么时候用值接收者 vs 指针接收者?
- 只读不修改 → 值接收者
- 需要修改字段 → 指针接收者
- 结构体很大 → 指针接收者(避免复制开销)
五、指针 — 简单理解
Go 的指针没有 C 的指针运算,比 C 安全得多。
// & 取地址 * 取值
x := 10
p := &x // p 是一个指向 int 的指针
fmt.Println(p) // 0xc0000...(内存地址)
fmt.Println(*p) // 10(取指针指向的值)
*p = 20 // 通过指针修改 x
fmt.Println(x) // 20
为什么需要指针?
// 没有指针:值传递,原来的值不变
func double(x int) {
x = x * 2
}
// 有指针:可以修改原来的值
func double(x *int) {
*x = *x * 2
}
n := 5
double(&n)
fmt.Println(n) // 10
💡 新手阶段掌握
&和*就够了。在 Go 里,大部分时间用的是指针接收者(struct 方法)和切片(内部就是指针),不需要像 C 那样手动操作指针。
六、完整示例
type Student struct {
Name string
Score float64
}
func (s Student) Info() string {
status := "不及格"
if s.Score >= 60 {
status = "及格"
}
return fmt.Sprintf("%s: %.1f分 - %s", s.Name, s.Score, status)
}
func main() {
// 用结构体 + 切片组织数据
students := []Student{
{"张三", 85},
{"李四", 42},
{"王五", 93},
}
// 用 map 索引
index := make(map[string]float64)
for _, s := range students {
index[s.Name] = s.Score
}
// 查询
for _, name := range []string{"张三", "赵六"} {
if score, ok := index[name]; ok {
fmt.Printf("%s 的分数: %.1f\n", name, score)
} else {
fmt.Printf("%s 不存在\n", name)
}
}
// 遍历
for _, s := range students {
fmt.Println(s.Info())
}
}
七、小结
| 类型 | 特点 | 常用程度 |
|---|---|---|
数组 [n]T | 定长,用的少 | ⭐⭐ |
切片 []T | 动态长度,最常用 | ⭐⭐⭐⭐⭐ |
Map map[K]V | 键值对,快速查询 | ⭐⭐⭐⭐ |
结构体 struct | 组织多个字段,代替 class | ⭐⭐⭐⭐⭐ |
指针 *T | 引用传值,注意 & 和 * | ⭐⭐⭐ |
下一课预告:错误处理与包管理——学会用好 error 和 go mod。