数组和切片
数组和切片都是用来存放一组同类型数据的结构,写法也很像,所以放在一起讲。先记住一个关键区别:数组长度固定,切片长度可变。
数组
数组的长度在声明时就定死了,之后不能改。元素类型随意,但同一数组里必须是同一种类型。
语法:
go
var 数组名 = [长度]类型{值1, 值2, 值3, ...}
数组名 := [长度]类型{值1, 值2, 值3, ...}
例子:
go
package main
import "fmt"
func main() {
var arr = [5]int{1, 2, 3, 4, 5}
arr2 := [5]int{6, 7, 8, 9, 10}
fmt.Println(arr, arr2)
}
两种写法都能声明数组:var 完整声明,或 := 短声明。[5]int 表示「长度为 5、元素类型为 int」------长度是类型的一部分 ,[5]int 和 [4]int 是两种不同类型。
读写元素
通过下标访问和修改元素。下标从 0 开始:
go
package main
import "fmt"
func main() {
arr := [4]string{"唱", "跳", "rap", "篮球"}
// 下标: 0 1 2 3
fmt.Println(arr[0]) // 唱
arr[0] = "篮球"
fmt.Println(arr[0]) // 篮球
}
获取长度
用内置函数 len 获取数组长度:
go
package main
import "fmt"
func main() {
arr := [4]string{"唱", "跳", "rap", "篮球"}
fmt.Println(len(arr)) // 4
}
切片
切片可以看作「可变长度的数组」。日常开发里用得比数组多得多。
语法上,把数组的长度去掉就行:
go
var 切片名 = []类型{值1, 值2, 值3, ...}
切片名 := []类型{值1, 值2, 值3, ...}
例子:
go
package main
import "fmt"
func main() {
var s1 = []int{1, 2, 3}
s2 := []string{"唱", "跳", "rap"}
fmt.Println(s1, s2)
}
[]int 没有写死长度,所以这是切片,不是数组。
读写元素
读写方式和数组一样,也是按下标操作:
go
package main
import "fmt"
func main() {
s := []string{"唱", "跳", "rap", "篮球"}
fmt.Println(s[1]) // 跳
s[1] = "打球"
fmt.Println(s) // [唱 打球 rap 篮球]
}
长度与容量
切片有两个常用属性:
len:当前元素个数cap:底层数组的容量(还能装多少,不用立刻扩容)
go
package main
import "fmt"
func main() {
s := []int{1, 2, 3}
fmt.Println(len(s)) // 3
fmt.Println(cap(s)) // 3
}
切片截取
用 切片[低:高] 可以从已有切片(或数组)上「切」出一段,得到一个新切片。区间是左闭右开 :包含下标 低,不包含下标 高。
go
package main
import "fmt"
func main() {
s := []int{10, 20, 30, 40, 50}
// 下标: 0 1 2 3 4
fmt.Println(s[1:4]) // [20 30 40] 取下标 1、2、3
fmt.Println(s[:3]) // [10 20 30] 从头切到下标 3(不含 3)
fmt.Println(s[2:]) // [30 40 50] 从下标 2 切到末尾
fmt.Println(s[:]) // [10 20 30 40 50] 整段复制一份切片头
}
几种常见写法:
| 写法 | 含义 |
|---|---|
s[低:高] |
从 低 到 高(不含 高) |
s[:高] |
从开头到 高(不含 高) |
s[低:] |
从 低 到末尾 |
s[:] |
整段截取 |
也可以对数组做同样的事,结果是一个切片:
go
package main
import "fmt"
func main() {
arr := [5]int{10, 20, 30, 40, 50}
s := arr[1:4]
fmt.Println(s) // [20 30 40]
}
截取出来的切片和原数据共享底层数组:改一边,另一边也会变。后面做前置追加、中间插入时,会经常用到 s[:idx]、s[idx:] 这种写法。
追加元素
切片长度可变,最常见的操作就是往里面加元素。Go 只内置了往尾部 追加的 append;往前面插、往中间插,都要靠 append 和切片截取自己拼。
尾部追加
append 接收原切片和要追加的元素,返回一个新切片。容量不够时会自动扩容:
go
package main
import "fmt"
func main() {
s := []int{1, 2, 3}
s = append(s, 4, 5)
fmt.Println(s) // [1 2 3 4 5]
}
一次可以追加多个值。记得把返回值接回来(s = append(...)),否则原切片看不到新增内容。
前置追加
Go 没有「往头部插入」的内置函数,思路是:先建一个装着新元素的切片,再用 append 把原切片接在后面。
这里会用到 ...(展开操作符):s... 表示把切片 s 里的元素一个个拆开,当作多个参数传给 append。
go
package main
import "fmt"
func main() {
s := []int{1, 2, 3, 4, 5}
s = append([]int{-1, 0}, s...)
// 等同于:append([]int{-1, 0}, 1, 2, 3, 4, 5)
fmt.Println(s) // [-1 0 1 2 3 4 5]
}
拆开看就两步:
[]int{-1, 0}:要插到前面的新元素s...:把原来的1, 2, 3, 4, 5展开,接到后面
中间插入
中间插入同样没有内置方法,本质是把切片拆成「前半段 + 新元素 + 后半段」,再拼回去。
假设切片是 [-1 0 1 2 3 4 5],想在 2 后面(下标 4 的位置)插入 100, 200:
go
package main
import "fmt"
func main() {
s := []int{-1, 0, 1, 2, 3, 4, 5}
// 下标: 0 1 2 3 4 5 6
idx := 4 // 从下标 4 开始插入,也就是 2 的后面
s = append(s[:idx], append([]int{100, 200}, s[idx:]...)...)
fmt.Println(s) // [-1 0 1 2 100 200 3 4 5]
}
这行可以拆成三块理解:
| 部分 | 含义 | 结果 |
|---|---|---|
s[:idx] |
插入点之前 | [-1 0 1 2] |
[]int{100, 200} |
要插入的内容 | [100 200] |
s[idx:] |
插入点及之后 | [3 4 5] |
先用 append([]int{100, 200}, s[idx:]...) 得到 [100 200 3 4 5],再接到 s[:idx] 后面,就拼成了最终结果。
写法有点绕,但记口诀就行:前半段 + 新元素 + 后半段。
数组 vs 切片
| 数组 | 切片 | |
|---|---|---|
| 长度 | 固定,声明时确定 | 可变,可用 append 增减 |
| 类型写法 | [5]int |
[]int |
| 传递方式 | 值拷贝(复制整个数组) | 引用底层数组(更轻量) |
| 使用场景 | 长度已知且不变 | 日常开发更常用 |
入门阶段可以先记住:不确定长度、或需要增删,就用切片。
作业
- 数组基础 :声明一个长度为
5的int数组,依次赋值为1~5,打印第3个元素(注意下标),再用len打印数组长度。 - 切片声明与追加 :声明切片
[]string{"唱", "跳", "rap"},用append在尾部追加"篮球",打印切片内容、len和cap。 - 切片截取 :给定
s := []int{10, 20, 30, 40, 50},分别打印s[1:4]、s[:3]、s[2:]、s[:],对照文中表格核对结果。 - 前置与中间插入 :以
[]int{1, 2, 3, 4, 5}为起点,先把-1, 0插到最前面;再在元素3后面插入100, 200,最终打印完整切片。 - 综合练习 :用切片存储若干门课程分数(如
[]int{95, 58, 76, 42, 88})。遍历切片:低于 60 的改为0(表示重考),然后截取前3门课的分数打印,并统计及格科目数量。