数组与切片
数组和切片是Go中存储数据集合的方式。切片是Go中最常用的数据结构之一!说实话,我一开始对数组和切片的区别很困惑,经常搞混。后来才明白,在Go中,几乎99%的场景都用切片,数组很少用到。
数组
数组是固定长度的同类型元素集合。
声明和初始化
// 声明数组(零值初始化)
var arr1 [5]int // [0 0 0 0 0]
// 声明并初始化
var arr2 = [5]int{1, 2, 3, 4, 5}
// 简短声明
arr3 := [5]int{1, 2, 3, 4, 5}
// 自动推断长度
arr4 := [...]int{1, 2, 3, 4, 5} // 长度为5
// 指定索引初始化
arr5 := [5]int{0: 1, 4: 5} // [1 0 0 0 5]
基本操作
arr := [5]int{1, 2, 3, 4, 5}
// 访问元素
fmt.Println(arr[0]) // 1
fmt.Println(arr[4]) // 5
// 修改元素
arr[0] = 10
// 获取长度
fmt.Println(len(arr)) // 5
// 遍历
for i, v := range arr {
fmt.Printf("arr[%d] = %d\n", i, v)
}
数组是值类型!
这是Go数组的重要特点:
arr1 := [3]int{1, 2, 3}
arr2 := arr1 // 复制整个数组!
arr2[0] = 100
fmt.Println(arr1) // [1 2 3](原数组不变)
fmt.Println(arr2) // [100 2 3]
和Java/JavaScript的区别
// Java - 数组是引用类型
int[] arr1 = {1, 2, 3};
int[] arr2 = arr1; // 引用同一个数组
arr2[0] = 100; // arr1也变了!
Go的数组是值类型,赋值会复制整个数组。要传引用用切片!
切片(Slice)
切片是动态长度的数组,底层还是数组,但更灵活。
创建切片
// 从数组创建
arr := [5]int{1, 2, 3, 4, 5}
s1 := arr[1:4] // [2 3 4](左闭右开)
// 直接声明
var s2 []int // nil切片
// 字面量创建
s3 := []int{1, 2, 3, 4, 5}
// make创建(推荐!)
s4 := make([]int, 5) // 长度5,容量5
s5 := make([]int, 3, 10) // 长度3,容量10
切片操作
s := []int{1, 2, 3, 4, 5}
// 长度和容量
fmt.Println(len(s)) // 5(长度)
fmt.Println(cap(s)) // 5(容量)
// 切片语法 [start:end]
s[1:4] // [2 3 4]
s[:3] // [1 2 3](从头开始)
s[2:] // [3 4 5](到末尾)
s[:] // [1 2 3 4 5](全部)
append - 追加元素
s := []int{1, 2, 3}
// 追加单个元素
s = append(s, 4) // [1 2 3 4]
// 追加多个元素
s = append(s, 5, 6) // [1 2 3 4 5 6]
// 追加切片
s2 := []int{7, 8, 9}
s = append(s, s2...) // [1 2 3 4 5 6 7 8 9]
重要!
append 返回新切片,必须接收返回值!
s := []int{1, 2, 3}
append(s, 4) // ❌ 错误用法,丢弃了返回值
s = append(s, 4) // ✅ 正确
copy - 复制切片
src := []int{1, 2, 3, 4, 5}
dst := make([]int, 3)
n := copy(dst, src) // 复制3个元素
fmt.Println(dst) // [1 2 3]
fmt.Println(n) // 3(实际复制的数量)
切片是引用类型
arr := [5]int{1, 2, 3, 4, 5}
s1 := arr[1:4] // [2 3 4]
s2 := s1
s2[0] = 100
fmt.Println(arr) // [1 100 3 4 5](原数组也变了!)
fmt.Println(s1) // [100 3 4]
fmt.Println(s2) // [100 3 4]
切片的内部结构
切片本质上是一个结构体:
type slice struct {
ptr *array // 指向底层数组
len int // 长度
cap int // 容量
}
理解这个对于理解切片行为很重要!
常见操作
删除元素
s := []int{1, 2, 3, 4, 5}
// 删除索引为2的元素(值为3)
s = append(s[:2], s[3:]...) // [1 2 4 5]
在中间插入
s := []int{1, 2, 4, 5}
// 在索引2处插入3
s = append(s[:2], append([]int{3}, s[2:]...)...) // [1 2 3 4 5]
去重
func unique(s []int) []int {
seen := make(map[int]bool)
result := []int{}
for _, v := range s {
if !seen[v] {
seen[v] = true
result = append(result, v)
}
}
return result
}
s := []int{1, 2, 2, 3, 3, 3, 4}
fmt.Println(unique(s)) // [1 2 3 4]
反转
func reverse(s []int) {
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
}
s := []int{1, 2, 3, 4, 5}
reverse(s)
fmt.Println(s) // [5 4 3 2 1]
过滤
func filter(s []int, fn func(int) bool) []int {
result := []int{}
for _, v := range s {
if fn(v) {
result = append(result, v)
}
}
return result
}
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// 过滤出偶数
evens := filter(nums, func(n int) bool {
return n%2 == 0
})
fmt.Println(evens) // [2 4 6 8 10]
多维切片
// 二维切片
matrix := [][]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}
// 访问
fmt.Println(matrix[1][2]) // 6
// 遍历
for i, row := range matrix {
for j, val := range row {
fmt.Printf("matrix[%d][%d] = %d\n", i, j, val)
}
}
实战案例:学生成绩管理
package main
import (
"fmt"
"sort"
)
type Student struct {
Name string
Score float64
}
func main() {
// 学生列表
students := []Student{
{"张三", 85.5},
{"李四", 92.0},
{"王五", 78.5},
{"赵六", 92.0},
{"钱七", 65.0},
}
// 计算平均分
var total float64
for _, s := range students {
total += s.Score
}
avg := total / float64(len(students))
fmt.Printf("平均分: %.2f\n", avg)
// 按成绩排序(降序)
sort.Slice(students, func(i, j int) bool {
return students[i].Score > students[j].Score
})
// 打印排名
fmt.Println("\n成绩排名:")
for i, s := range students {
fmt.Printf("%d. %s: %.1f分\n", i+1, s.Name, s.Score)
}
// 筛选优秀学生(>=90分)
excellent := []Student{}
for _, s := range students {
if s.Score >= 90 {
excellent = append(excellent, s)
}
}
fmt.Printf("\n优秀学生(%d人):\n", len(excellent))
for _, s := range excellent {
fmt.Printf("- %s: %.1f分\n", s.Name, s.Score)
}
}
输出:
平均分: 82.60
成绩排名:
1. 李四: 92.0分
2. 赵六: 92.0分
3. 张三: 85.5分
4. 王五: 78.5分
5. 钱七: 65.0分
优秀学生(2人):
- 李四: 92.0分
- 赵六: 92.0分
切片 vs 数组:何时用哪个?
| 场景 | 推荐 | 原因 |
|---|---|---|
| 长度固定且已知 | 数组 | 性能略好 |
| 长度动态变化 | 切片 | 灵活 |
| 函数参数传递 | 切片 | 避免大量复制 |
| 99%的情况 | 切片 | 更常用、更灵活 |
练习
- 创建一个包含10个数字的切片,计算它们的和
- 实现一个函数,将切片中的所有负数变成0
- 实现一个函数,找出切片中的最大值和最小值
参考答案
package main
import (
"fmt"
"math"
)
// 1. 计算和
func sum(nums []int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
// 2. 负数变0
func zeroNegatives(nums []int) {
for i := range nums {
if nums[i] < 0 {
nums[i] = 0
}
}
}
// 3. 最大最小值
func minMax(nums []int) (min, max int) {
if len(nums) == 0 {
return 0, 0
}
min, max = math.MaxInt, math.MinInt
for _, n := range nums {
if n < min {
min = n
}
if n > max {
max = n
}
}
return
}
func main() {
// 1
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
fmt.Printf("和: %d\n", sum(nums)) // 55
// 2
nums2 := []int{-3, -1, 0, 1, 3, 5}
zeroNegatives(nums2)
fmt.Printf("负数变0: %v\n", nums2) // [0 0 0 1 3 5]
// 3
nums3 := []int{5, 2, 9, 1, 7, 3}
min, max := minMax(nums3)
fmt.Printf("最小: %d, 最大: %d\n", min, max) // 最小: 1, 最大: 9
}
切片是Go中最常用的数据结构,下一节学习另一个重要的数据结构 Map!
