一、切片的定义
在Go语言中,切片是一个引用类型,用于封装了一个动态数组的视图。切片提供了一种方便、灵活的方式来操作数组。
切片的定义
在Go语言中,切片的定义使用以下语法:
var sliceName []Type
其中,sliceName
是切片的名称,Type
是切片存储的元素类型。
示例
:
package main
import "fmt"
func main() {
// 定义一个切片
var numbers []int
fmt.Println(numbers) // 输出: []
}
二、切片的初始化方式
可以通过切片字面量、make函数、或者从已有数组/切片中切出子切片的方式进行切片的赋值。
1、使用切片字面量
package main
import "fmt"
func main() {
// 使用切片字面量创建切片
numbers := []int{1, 2, 3, 4, 5}
fmt.Println(numbers) // 输出: [1 2 3 4 5]
}
2、使用make函数
package main
import "fmt"
func main() {
// 使用make函数创建切片,参数为类型、长度、容量
numbers := make([]int, 5, 10)
fmt.Println(numbers) // 输出: [0 0 0 0 0]
}
3、从已有数组/切片中切出子切片
package main
import "fmt"
func main() {
// 从已有数组中切出子切片
array := [5]int{1, 2, 3, 4, 5}
slice := array[1:4]
fmt.Println(slice) // 输出: [2 3 4]
}
解释
:
这里,array[1:4]
表示从数组的索引1开始(包含),到索引4结束(不包含)的切片。
三、切片的数据访问
在Go语言中,切片的数据访问可以通过索引来实现。切片是一个动态数组的视图,因此可以使用索引来访问切片中的元素。
示例
:
package main
import "fmt"
func main() {
// 创建一个切片
numbers := []int{1, 2, 3, 4, 5}
// 使用索引访问切片中的元素
fmt.Println("Element at index 2:", numbers[2]) // 输出: Element at index 2: 3
// 修改切片中的元素
numbers[2] = 10
fmt.Println("Modified slice:", numbers) // 输出: Modified slice: [1 2 10 4 5]
// 切片的长度和容量
fmt.Println("Length:", len(numbers)) // 输出: Length: 5
fmt.Println("Capacity:", cap(numbers)) // 输出: Capacity: 5
// 遍历切片中的元素
fmt.Println("Elements:")
for i, value := range numbers {
fmt.Printf("Index: %d, Value: %d\n", i, value)
}
/*
输出:
Elements:
Index: 0, Value: 1
Index: 1, Value: 2
Index: 2, Value: 10
Index: 3, Value: 4
Index: 4, Value: 5
*/
// 切片的切割
slice1 := numbers[1:4]
fmt.Println("Slice1:", slice1) // 输出: Slice1: [2 10 4]
// 切片的追加元素
numbers = append(numbers, 6, 7, 8)
fmt.Println("After append:", numbers) // 输出: After append: [1 2 10 4 5 6 7 8]
}
解释
:
上述例子中,首先创建了一个切片 numbers
,然后通过索引访问切片中的元素,修改了切片中的某个元素的值。接着,使用 len
函数获取切片的长度,使用 cap
函数获取切片的容量。通过 for
循环遍历切片中的元素,最后演示了切片的切割和追加元素的操作。
注意
:
切片是引用类型,对切片的修改会影响原始切片及其底层数组。
四、通过省略号添加多个元素到切片
在Go语言中,可以使用省略号(...
)语法将一个切片的元素追加到另一个切片中。这种方式可以方便地将一个切片的所有元素追加到另一个切片中,使代码更简洁。
示例
:
package main
import "fmt"
func main() {
// 创建两个切片
slice1 := []int{1, 2, 3}
slice2 := []int{4, 5, 6}
// 使用省略号将 slice2 的所有元素追加到 slice1 中
slice1 = append(slice1, slice2...)
fmt.Println("Combined slice:", slice1) // 输出: Combined slice: [1 2 3 4 5 6]
// 创建一个切片
numbers := []int{7, 8, 9}
// 使用省略号将多个元素追加到切片中
slice1 = append(slice1, 7, 8, 9)
fmt.Println("Updated slice:", slice1) // 输出: Updated slice: [1 2 3 4 5 6 7 8 9]
}
解释
:
在上面的例子中,首先创建了两个切片 slice1
和 slice2
,然后使用 append
函数和省略号语法将 slice2
的所有元素追加到 slice1
中,形成一个新的切片 Combined slice
。接着,演示了在切片末尾使用省略号直接追加多个元素到切片中的操作,形成一个更新后的切片 Updated slice
。
作用
:
这种省略号语法的使用使得切片的追加操作更加灵活和简洁。
五、切片的元素删除与拷贝
在Go语言中,切片的元素删除可以通过切片的操作和append
函数实现。另外,切片的元素拷贝可以使用copy
函数。以下是详细的例子和结果:
1、元素删除
package main
import "fmt"
func main() {
// 创建一个切片
numbers := []int{1, 2, 3, 4, 5}
// 删除切片中的第三个元素(索引为2)
indexToRemove := 2
numbers = append(numbers[:indexToRemove], numbers[indexToRemove+1:]...)
fmt.Println("After deletion:", numbers) // 输出: After deletion: [1 2 4 5]
// 或者使用切片操作
numbers = append(numbers[:2], numbers[3:]...)
fmt.Println("After deletion using slicing:", numbers) // 输出: After deletion using slicing: [1 2 5]
}
解释
:
在上述例子中,通过append
函数和切片操作实现了切片中元素的删除。第一个例子中,通过切片操作删除了索引为2的元素,第二个例子中使用了append
函数和切片操作一起删除了索引为2的元素。
2、元素拷贝
package main
import "fmt"
func main() {
// 创建两个切片
source := []int{1, 2, 3, 4, 5}
destination := make([]int, len(source))
// 使用copy函数将source切片的元素拷贝到destination切片中
copy(destination, source)
fmt.Println("Copied slice:", destination) // 输出: Copied slice: [1 2 3 4 5]
// 修改source切片,不影响destination切片
source[0] = 10
fmt.Println("Source after modification:", source) // 输出: Source after modification: [10 2 3 4 5]
fmt.Println("Destination after source modification:", destination) // 输出: Destination after source modification: [1 2 3 4 5]
}
解释
:
在这个例子中,使用copy
函数将source
切片的元素拷贝到destination
切片中,这样就创建了一个新的切片,对原切片的修改不会影响到拷贝后的切片。
注意
:
copy
函数的目标切片必须有足够的容量来容纳源切片的元素。如果目标切片的长度小于源切片的长度,只会拷贝目标切片长度的元素。
3、元素删除与拷贝的总结与区别
切片的元素删除和拷贝是两个不同的操作,它们在实现和目的上有一些明显的区别。
3.1、切片元素删除
在Go语言中,切片的元素删除通常通过以下两种方式实现:
-
使用
append
和切片操作:numbers = append(numbers[:indexToRemove], numbers[indexToRemove+1:]...)
这种方式利用
append
函数和切片操作,将要删除的元素前半部分和后半部分重新拼接起来,从而实现元素的删除。这样的操作会创建一个新的切片,原始切片的底层数组不受影响。 -
使用切片操作:
numbers = append(numbers[:2], numbers[3:]...)
这种方式直接使用切片操作,将要删除的元素前半部分和后半部分拼接起来,也能实现元素的删除。与第一种方式相比,这种方式更简洁,但原理相同。
3.2、切片元素拷贝
切片的元素拷贝主要通过copy
函数来实现:
copy(destination, source)
这里,source
是要拷贝的源切片,destination
是目标切片。copy
函数会将source
切片的元素拷贝到destination
切片中。需要注意的是,destination
切片的长度必须足够大,以容纳source
切片的所有元素。
3.3、区别总结
- 元素删除 主要通过
append
函数和切片操作实现,产生一个新的切片。 - 元素拷贝 主要通过
copy
函数实现,创建一个新的切片,原切片的修改不会影响到拷贝后的切片。
总体而言,切片的删除操作涉及切片的重新构建,而切片的拷贝操作则涉及将一个切片的元素复制到另一个切片。
六、切片的基本原理
Go语言中的切片(Slice)是一种动态数组的抽象。切片本质上是对底层数组的封装,提供了一种方便、灵活且高效的方式来操作数组的片段。
1、切片的结构
切片的结构包含三个字段:
-
指向底层数组的指针(Pointer):
- 指示切片开始的位置。
-
切片的长度(Length):
- 切片包含的元素个数。
-
切片的容量(Capacity):
- 切片可以容纳的元素个数,从切片的开始位置到底层数组的末尾。
切片是一个动态数组的引用,其结构如下:
type slice struct {
ptr *ElementType // 指向底层数组的指针
len int // 切片的长度
cap int // 切片的容量
}
- ptr: 指向底层数组的指针,指示切片的起始位置。
- len: 切片的长度,表示切片包含的元素个数。
- cap: 切片的容量,表示切片可以容纳的最大元素个数。
2、切片的创建
切片可以通过以下方式创建:
// 通过切片字面量创建切片
slice1 := []int{1, 2, 3, 4, 5}
// 通过make函数创建切片,make([]T, length, capacity)
slice2 := make([]int, 3, 5)
3、切片的底层数组
切片不拥有数据,它只是底层数组的一个视图。当我们创建切片时,Go语言会在底层数组上创建一个新的切片结构,包含指向底层数组的指针、切片的长度和切片的容量。多个切片可以共享同一个底层数组。
array := [...]int{1, 2, 3, 4, 5}
slice1 := array[1:4] // 切片1:[2 3 4]
slice2 := array[0:2] // 切片2:[1 2]
在上述例子中,slice1
和 slice2
共享同一个底层数组 array
。
4、切片的扩容
当切片的容量不足以容纳新的元素时,切片就会被扩容。Go语言的切片扩容策略是以2的幂次方进行扩容,确保切片的容量始终是2的幂次方。
slice := make([]int, 3, 5)
newSlice := append(slice, 4, 5)
在上述例子中,如果 slice
的容量不足以容纳两个新的元素,就会创建一个新的底层数组,将原有元素拷贝到新数组中,并返回新数组的切片。这确保了在大多数情况下,切片的操作都是高效的。
5、切片的传递
切片是引用类型,当切片作为参数传递给函数时,函数接收到的是切片的拷贝,但拷贝的是切片结构,不包括底层数组。因此,在函数内部对切片的修改会影响到原始切片。
func modifySlice(s []int) {
s[0] = 99
}
originalSlice := []int{1, 2, 3}
modifySlice(originalSlice)
fmt.Println(originalSlice) // 输出: [99 2 3]
6、切片的引用与共享
由于切片只是对底层数组的引用,所以多个切片可以引用同一个底层数组的不同部分。修改一个切片的元素会影响到其他引用同一底层数组的切片。
slice1[0] = 99
fmt.Println(array) // 输出: [1 99 3 4 5]
在上述例子中,修改 slice1 的第一个元素也修改了底层数组 array,因此 array 的第二个元素变为了 99。
7、切片的零值
切片的零值是 nil
。一个值为 nil
的切片没有底层数组,长度为0,容量为0。
var nilSlice []int
fmt.Println(nilSlice == nil) // 输出: true
七、切片底层存储原理通俗易懂版
切片就像是一把智能剪刀,有三个要害部分——指针、长度和容量。指针指向一串连续的元素,长度表示切片当前拥有的元素个数,而容量则是切片能够继续增加元素的上限。
当你创建一个切片时,实际上是在内存中找了一片空地,这片地方就是一个数组,切片的指针指向这个数组的开始。如果你对切片进行修改,其实是直接在这个数组上操作。更有趣的是,如果你再创建一个切片,它也可以指向同一个数组的不同部分,这样就实现了切片之间的共享。因为切片只是对数组的一种引用,所以修改一个切片会影响到其他引用同一数组的切片。
当切片的元素个数不够用时,切片就会像智能剪刀一样自动找一片更大的空地,搬迁原来的元素,并把指针、长度和容量都更新,保证切片继续灵活使用。这种动态的、自动管理内存的特性使得切片成为Go语言中强大而灵活的数据结构。