<7>Golang基础进阶——切片
Golang基础进阶——切片
切片( slice )——动态分配大小的连续空间
Go 语言切片的内部结构包含地址、大小和容量,切片是真正意义上的动态数组,而且是一个引用类型,切片指向一个底层数组。
切片的声明方式
var name []T
声明但未使用的切片的默认值是:nil。示例:
func main() {
var strList []string
fmt.Println(strList == nil)
}
从指定范围中生成切片
func main() {
var h [30]int
for i := 0; i < 30; i++ {
h[i] = i + 1
}
fmt.Println(h[10:15])
fmt.Println(h[20:])
fmt.Println(h[:2])
}
使用 make() 函数构造切片,格式如下:
make([]T, size, cap)
- T: 切片类型
- size:为这个类型分配多少元素
- cap:容量,这个值的设定不影响size,只是提前分配空间,降低多次分配空间造成的性能问题。
示例:
func main() {
a := make([]int, 2)
b := make([]int, 2, 10)
fmt.Println(a, b)
fmt.Println(len(a), len(b))
}
提示:使用 make() 函数生成的切片一定发生了内存分配操作。但给定开始与结束位置的切片只是将新的切片结构指向已经分配好的内存区域,设定开始与结束为止,不会发生内存分配操作。
从数组/切片中切出来的切片容量
示例1:
func main() {
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
s1 := arr[2:]
fmt.Println(s1) // [2, 3, 4, 5, 6, 7]
s1[0] = 100
fmt.Println(s1) // [100, 3, 4, 5, 6, 7]
fmt.Println(arr) // [0, 1, 100, 3, 4, 5, 6, 7]
s2 := arr[2:6]
fmt.Println(s2) // [100, 3, 4, 5,]
s3 := s2[3:6]
fmt.Println(s3) //[5,6,7]
s4 := s3[1:]
fmt.Println(s4)
fmt.Printf("len(s2)=%d, cap(s2)=%d\n", len(s2), cap(s2))
}
示例2:
func main() {
var h [30]int
for i := 0; i < 30; i++ {
h[i] = i + 1
}
fmt.Println(h)
a := h[1:10] // [2 3 4 5 6 7 8 9 10]
fmt.Println(a)
b := a[3:5] // [5 6]
fmt.Println(b)
c := b[2:4]
fmt.Println(c)
fmt.Printf("c -- cap=%d\n", cap(c)) // c -- cap=24
}
使用 append() 函数为切片添加元素
简单示例:
向 slice 尾部添加数据,返回新的 slice 对象
func main() {
var s1 []int
s1 = append(s1, 1)
s1 = append(s1, 2, 3)
s1 = append(s1, 4, 5, 6)
fmt.Println(s1)
s2 := make([]int, 5)
s2 = append(s2, 6)
fmt.Println(s2)
s3 := []int{1, 2, 3}
s3 = append(s3, 5)
fmt.Println(s3)
}
切片append之后对原数组的影响
func main() {
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
s1 := arr[2:6]
fmt.Println(s1) // [2, 3, 4, 5]
s2 := s1[3:5]
fmt.Println(s2) // [5, 6]
s3 := append(s2, 10)
fmt.Println(s3) // [5, 6, 10]
fmt.Println(arr) // [0, 1, 2, 3, 4, 5, 6, 10]
s4 := append(s3, 11)
fmt.Println(s4) // [5, 6, 10, 11]
fmt.Println(arr) // [0, 1, 2, 3, 4, 5, 6, 10]
s5 := append(s4, 12)
fmt.Println(s5) // [5, 6, 10, 11, 12]
fmt.Println(arr) // [0, 1, 2, 3, 4, 5, 6, 10]
}
append导致切片扩容
append()可以为切片动态添加元素,每个切片会指向一片内存空间,这片内存空间能容纳一定数量的元素。当空间不足时就会进行扩容。“扩容”操作往往发生在 append() 函数调用时。切片扩容时,容量的扩展规律按照容量的2倍数扩充,例如1、2、4、8、16 ... ...。
示例:
func main() {
var numbers []int
for i := 0; i < 10; i++ {
numbers = append(numbers, i)
fmt.Printf("len:%d cap:%d pointer:%p\n", len(numbers), cap(numbers), numbers)
}
}
// len:1 cap:1 pointer:0xc000054080
// len:2 cap:2 pointer:0xc0000540c0
// len:3 cap:4 pointer:0xc000052160
// len:4 cap:4 pointer:0xc000052160
// len:5 cap:8 pointer:0xc00006c140
// len:6 cap:8 pointer:0xc00006c140
// len:7 cap:8 pointer:0xc00006c140
// len:8 cap:8 pointer:0xc00006c140
// len:9 cap:16 pointer:0xc00008a000
// len:10 cap:16 pointer:0xc00008a000
append添加切片
func main() {
var a = []int{1, 2, 3}
var b = []int{4, 5, 6}
a = append(a, b...)
fmt.Println(a)
}
copy() 函数
格式:
copy(destSlice, srcSlice []T) int
- srcSlice 为数据来源切片。
- destSlice 为复制的目标。目标切片必须分配过空间且足够承载复制的元素个数。来源和目标的类型一致,copy 的返回值表示实际发生复制的元素个数。
示例:
func main() {
const elementCount = 1000
srcData := make([]int, elementCount)
for i := 0; i < elementCount; i++ {
srcData[i] = i
}
refDate := srcData
fmt.Println(refDate) // 0-999
copyData := make([]int, elementCount)
copy(copyData, srcData)
srcData[0] = 999
fmt.Println(refDate[0]) // 999
fmt.Printf("%p, %p\n", srcData, refDate) // 地址相同
fmt.Println(copyData[0], copyData[elementCount-1]) // 0, 999
copy(copyData, srcData[4:6])
for i := 0;i < 5;i++{
fmt.Printf("%d\n", copyData[i]) //4 5 2 3 4
}
}
切片删除操作
Go 中切片删除元素的本质是:以被删除元素为分界点,将前后两个部分的内存重新连接起来。
func main() {
seq := []string{"a", "b", "c", "d", "e"}
// 指定删除位置
index := 2
// 查看删除位置之前的元素和之后的元素
fmt.Println(seq[:index], seq[index+1:])
// 将删除点前后的元素连接起来
seq = append(seq[:index], seq[index+1:]...)
fmt.Println(seq)
}
注意:Go 语言中切片元素的删除过程并没有提供任何的语法糖或者方法封装,无论是初学者学习,还是实际使用都是极为麻烦的。连续容器的元素删除无论是在任何语言中,都要将删除点前后的元素移动到新的位直。随着元素的增加,这个过程将会变得极为耗时 因此,当业务需要大量、频繁地从一个切片中删除元素时,如果对性能要求较高,就需要反思是否需要更换其他的容器(如双链表等能快速从删除点删除元素)。
加油,你们是最棒的!