【Golang】切片 slice
切片(slice)是go语言提供的动态数组,本质上是对底层动态数组的引用。
slice
数组
(array)的大小都是固定的。
切片
(slice)为数组元素提供了动态大小的、灵活的视角。在实践中,切片比数组更常用。
切片类似数组的引用
切片的底层是动态数组。
切片是对数组的引用,它不存储任何数据,只是描述了底层数组中的一段。
更改切片的元素会修改其底层数组中对应的元素。
和它共享底层数组的切片都会观测到这些修改。但触发扩容时,发生扩容的切片会成为新数组的引用。
通过数组创建的切片:
- 切片与数组共享内存空间。
- 在切片进行
append
操作时,若数组空间有空闲,会将数组下一个位置设置为给定值。 - 在切片进行
append
操作时,若数组空间不够用,则会分配一个更大的底层数组,该切片成为新数组的引用,和原数组不再关联,它们互相独立。
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main
import "fmt"
func main() {
langs := [5]string{"C", "C++", "Golang", "Python", "Cangjie"}
slice1 := langs[0:3] // []string{"C", "C++", "Golang"}
slice2 := langs[1:3] // []string{"C++", "Golang"}
fmt.Printf("%T, %v\n", langs, langs) // [5]string{"C", "C++", "Golang", "Python", "Cangjie"}
fmt.Printf("%T, %v\n", slice1, slice1) // []string{"C", "C++", "Golang"}
fmt.Printf("%T, %v\n", slice2, slice2) // []string{"C++", "Golang"}
fmt.Println("------------------------")
slice2[1] = "XXX"
fmt.Printf("%T, %v\n", langs, langs) // [5]string{"C", "C++", "XXX", "Python", "Cangjie"}
fmt.Printf("%T, %v\n", slice1, slice1) // []string{"C", "C++", "XXX"}
fmt.Printf("%T, %v\n", slice2, slice2) // []string{"C++", "XXX"}
fmt.Println("------------------------")
slice1 = append(slice1, "Java") // 数组空间有空闲、够用
fmt.Printf("%T, %v\n", langs, langs) // [5]string{"C", "C++", "XXX", "Java", "Cangjie"}
fmt.Printf("%T, %v\n", slice1, slice1) // []string{"C", "C++", "XXX", "Java"}
fmt.Printf("%T, %v\n", slice2, slice2) // []string{"C++", "XXX"}
fmt.Println("------------------------")
slice3 := langs[:]
slice3 = append(slice3, "PHP") // 数组没有多余空间
fmt.Printf("%T, %v\n", langs, langs) // [5]string{"C", "C++", "XXX", "Java", "Cangjie"}
fmt.Printf("%T, %v\n", slice1, slice1) // []string{"C", "C++", "XXX", "Java"}
fmt.Printf("%T, %v\n", slice2, slice2) // []string{"C++", "XXX"}
fmt.Printf("%T, %v\n", slice3, slice3) // [5]string{"C", "C++", "XXX", "Java", "Cangjie", "PHP"}
fmt.Println("------------------------")
}
输出结果如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[5]string, [C C++ Golang Python Cangjie]
[]string, [C C++ Golang]
[]string, [C++ Golang]
------------------------
[5]string, [C C++ XXX Python Cangjie]
[]string, [C C++ XXX]
[]string, [C++ XXX]
------------------------
[5]string, [C C++ XXX Java Cangjie]
[]string, [C C++ XXX Java]
[]string, [C++ XXX]
------------------------
[5]string, [C C++ XXX Java Cangjie]
[]string, [C C++ XXX Java]
[]string, [C++ XXX]
[]string, [C C++ XXX Java Cangjie PHP]
------------------------
通过切片创建的切片,示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package main
import "fmt"
func main() {
var names = []string{"蒙奇·D·路飞", "罗罗诺亚·索隆", "木下秀吉", "利姆鲁·特恩佩斯特"}
n1 := names[0:3]
n2 := names[2:4]
fmt.Printf("%T, %v\n", names, names)
fmt.Printf("%T, %v\n", n1, n1)
fmt.Printf("%T, %v\n", n2, n2)
fmt.Println("-----------------------------------")
n1[2] = "2233"
fmt.Printf("%T, %v\n", names, names)
fmt.Printf("%T, %v\n", n1, n1)
fmt.Printf("%T, %v\n", n2, n2)
fmt.Println("-----------------------------------")
n1 = append(n1, "二刺猿")
fmt.Printf("%T, %v\n", names, names)
fmt.Printf("%T, %v\n", n1, n1)
fmt.Printf("%T, %v\n", n2, n2)
fmt.Println("-----------------------------------")
n1 = append(n1, "😄") // 触发扩容,只有 n1 发生变化
// n1 成为新数组的引用,n2 和 names 仍然是同一个动态数组的引用
fmt.Printf("%T, %v\n", names, names)
fmt.Printf("%T, %v\n", n1, n1)
fmt.Printf("%T, %v\n", n2, n2)
fmt.Println("-----------------------------------")
n1[0] = "🎇" // 只有 n1 发生变化
fmt.Printf("%T, %v\n", names, names)
fmt.Printf("%T, %v\n", n1, n1)
fmt.Printf("%T, %v\n", n2, n2)
fmt.Println("-----------------------------------")
}
输出如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[]string, [蒙奇·D·路飞 罗罗诺亚·索隆 木下秀吉 利姆鲁·特恩佩斯特]
[]string, [蒙奇·D·路飞 罗罗诺亚·索隆 木下秀吉]
[]string, [木下秀吉 利姆鲁·特恩佩斯特]
-----------------------------------
[]string, [蒙奇·D·路飞 罗罗诺亚·索隆 2233 利姆鲁·特恩佩斯特]
[]string, [蒙奇·D·路飞 罗罗诺亚·索隆 2233]
[]string, [2233 利姆鲁·特恩佩斯特]
-----------------------------------
[]string, [蒙奇·D·路飞 罗罗诺亚·索隆 2233 二刺猿]
[]string, [蒙奇·D·路飞 罗罗诺亚·索隆 2233 二刺猿]
[]string, [2233 二刺猿]
-----------------------------------
[]string, [蒙奇·D·路飞 罗罗诺亚·索隆 2233 二刺猿]
[]string, [蒙奇·D·路飞 罗罗诺亚·索隆 2233 二刺猿 😄]
[]string, [2233 二刺猿]
-----------------------------------
[]string, [蒙奇·D·路飞 罗罗诺亚·索隆 2233 二刺猿]
[]string, [🎇 罗罗诺亚·索隆 2233 二刺猿 😄]
[]string, [2233 二刺猿]
-----------------------------------
切片长度与容量
切片的长度
(length)就是它所包含的元素个数,对应C++ vector的size。
切片的容量
(capacity)是从它的第一个元素开始数,到其底层数组元素末尾的个数, 即当前最多可以保存的元素数量,超过这个数量就会触发扩容,对应C++ vector的capacity。
切片 s
的长度和容量可通过表达式 len(s)
和 cap(s)
来获取。
nil 切片
切片的零值
是 nil
。
值为零值nil的切片称为nil 切片
。
nil 切片的长度
和容量
为 0 且没有
底层数组。
代码验证:
1
2
3
4
5
6
7
8
9
10
11
package main
import "fmt"
func main() {
var s []int
fmt.Println(s, len(s), cap(s))
if s == nil {
fmt.Println("nil!")
}
}
删除切片元素
使用append函数
append 函数用于将元素添加到现有切片。 通过将新切片与现有切片中的元素相结合,您可以有效地从现有切片中删除元素。
以 []int 类型切片为例:
1
2
3
func DelElement(slice []int, index int) []int {
return append(slice[:index], slice[index+1:]...)
}
使用copy函数
copy 函数用于复制切片中的元素。通过将切片元素复制到一个较小的切片中,您可以有效地从原始切片中移除元素。
以 []int 类型切片为例:
1
2
3
4
func DelElem(slice []int, index int) []int {
copy(slice[index:], slice[index+1:])
return slice[:len(slice)-1]
}
使用slice创建新切片
您可以使用切片语法 [start:end]
来创建一个新的切片,其中包含原始切片的一部分。 这使您可以有效地跳过要删除的元素。
1
2
3
4
5
6
7
8
// 创建一个整数切片
slice := []int{1, 2, 3, 4, 5}
// 从切片中删除索引为 2 的元素
slice = slice[:2]
// 打印修改后的切片
fmt.Println(slice) // 输出:[1 2]
使用for循环
虽然其他方法效率更高,但您也可以使用 for 循环从切片中删除元素。 此方法涉及遍历切片元素并逐个删除它们。
以 []int 类型切片为例:
1
2
3
4
5
6
7
func DeleteElem(slice []int, index int) []int {
for i := index; i<len(slice)-1; i++ {
slice[i] = slice[i+1]
}
slice = slice[:len(slice)-1]
return slice
}