切片是Go语言中的一种数据结构,提供了比数组更灵活的功能。切片是对数组的一个连续片段的抽象,具有动态大小和自动扩容的特性。
切片是引用类型,底层由一个数组支持。切片包含三个部分:指向底层数组的指针、切片的长度和切片的容量。
切片在传递时是按值传递的,但由于切片包含一个指向底层数组的指针,所以切片的副本仍然引用同一个底层数组。这意味着对切片的修改可能会影响到其他引用同一底层数组的切片。
一、切片的定义
方式1:通过切片表达式从一个数组或另一个切片创建
var arr [长度]类型
var slice []类型 = arr[起始索引:结束索引]
-
切片的长度为结束索引减去起始索引,容量为底层数组从起始索引到数组末尾的长度。
-
切片的起始索引默认为0,结束索引默认为底层数组的长度。
-
切片的定义方式有两种:一种是通过切片表达式从一个数组或另一个切片创建,另一种是直接定义一个切片并初始化。
-
切片表达式的语法为:slice := arr[low:high],其中low是起始索引,high是结束索引。切片包含从low到high-1的元素。
-
切片表达式还可以省略low或high,默认为0或底层数组的长度。例如,arr[:3]表示从索引0到索引2的元素,arr[2:]表示从索引2到数组末尾的元素。
-
切片表达式还可以同时省略low和high,表示引用整个底层数组。例如,arr[:]表示引用整个数组。
package main
import "fmt"
func main() {
// 切片来源于一个指定的数组(这个数组是可见的),切片的长度和容量由切片的定义方式决定
// slice1的长度为3,包含元素2、3、4,底层数组为arr
var arr [5]int = [5]int{1, 2, 3, 4, 5} // 定义一个长度为5的数组
var slice1 []int = arr[1:4] // 定义一个切片,引用数组arr的第2到第4个元素
// 省略起始索引: slice2的长度为4,包含元素1、2、3、4,底层数组为arr
var slice2 []int = arr[:4] // 定义一个切片,引用数组arr的前4个元素
// 省略结束索引: slice3的长度为5,包含元素1、2、3、4、5,底层数组为arr
var slice3 []int = arr[0:5] // 定义一个切片,引用整个数组
// 同时省略起始和结束索引: slice4的长度为5,包含元素1、2、3、4、5,底层数组为arr
var slice4 []int = arr[:] // 定义一个切片,引用整个数组
fmt.Println(arr, slice1, slice2, slice3, slice4)
}
方式2:直接定义一个切片并初始化
var slice []类型 // 定义一个切片,底层数组由编译器自动创建,元素类型为类型,切片的长度和容量都为0。元素的默认值为类型的零值。
var slice []类型 = []类型{元素1, 元素2, ...}
package main
import "fmt"
func main() {
// 直接定义一个切片(切片来源的这个数组不可见)
var slice1 []int = []int{1, 2, 3} // 定义一个切片,并初始化
fmt.Println(slice1)
// 定义一个切片,但不初始化,切片的长度和容量都为0,元素的默认值为int类型的零值0
var slice2 []string
fmt.Println(slice2) // 输出:[],切片的长度和容量都为0,元素的默认值为string类型的零值""
}
方式3:切片来源于另一个切片
var slice2 []类型 = slice1[起始索引:结束索引]
-
切片也可以通过切片表达式从另一个切片创建。例如,slice2 := slice1[1:3]表示从slice1的第2到第3个元素创建一个新的切片slice2。
-
这种方式创建的切片slice2与slice1共享同一个底层数组,因此对slice2的修改可能会影响到slice1,反之亦然。
package main
import "fmt"
func main() {
// 切片来源于另一个切片,slice2引用slice1的第2到第3个元素
var arr [5]int = [5]int{1, 2, 3, 4, 5} // 定义一个长度为5的数组
var slice1 []int = arr[1:4] // 定义一个切片,引用数组arr的第2到第4个元素
var slice2 []int = slice1[1:3] // 定义一个切片,引用slice1的第2到第3个元素
fmt.Println(arr, slice1, slice2)
}
方式4:使用内置函数make创建切片
var slice []类型 = make([]类型, 长度, 容量)
var slice []类型 = make([]类型, 长度)
-
make函数用于创建切片,语法为:slice := make([]Type, length, capacity)。其中length是切片的长度,capacity是切片的容量。如果省略capacity,则capacity默认为length。
-
使用make函数创建的切片底层会自动分配一个数组来支持切片的存储。切片的长度和容量由make函数的参数决定。
-
容量是可选的,如果不指定容量,切片的容量将与长度相同。这意味着切片在添加元素时可能会触发扩容。
-
如果只声明slice,而没有初始化元素的值,切片中的元素将被初始化为类型的零值。例如,int float类型的切片元素将被初始化为0,string类型的切片元素将被初始化为""。
package main
import "fmt"
func main() {
// 使用make函数创建一个长度为3,容量为5的切片
var slice []int = make([]int, 3, 5) // 定义一个切片,长度为3,容量为5
fmt.Println(slice)
// 使用make函数创建一个长度为3,容量不确定的切片
var slice2 []int = make([]int, 3) // 定义一个切片,长度为3,容量不确定的切片
fmt.Println(slice2)
}
二、切片的组成
切片由三部分组成:指向底层数组的指针、切片的长度和切片的容量。
-
指针:指向切片所引用的底层数组的起始位置。
-
长度:切片中元素的数量。
-
容量:从切片的起始位置到底层数组末尾的元素数量。
三、切片的动态特性
-
go的切片是一个动态数组,提供了比数组更灵活的功能。
-
切片由三部分组成:指向底层数组的指针、切片的长度和切片的容量。使用len()函数可以获取切片的长度,使用cap()函数可以获取切片的容量。
-
切片可以动态增长和缩小,且在需要时会自动扩容。
-
切片的长度是切片中元素的数量,而容量是从切片的起始位置到底层数组末尾的元素数量。
-
切片的长度不能超过其容量。当切片的长度增加超过容量时,Go会自动分配一个更大的底层数组,并将原有元素复制到新的数组中。
-
切片在传递时是按值传递的,但由于切片包含一个指向底层数组的指针,所以切片的副本仍然引用同一个底层数组。这意味着对切片的修改可能会影响到其他引用同一底层数组的切片。
-
切片的使用和数组类似,可以使用索引访问元素,可以使用 for-range 循环遍历切片,也可以使用内置函数 len() 获取切片的长度,cap() 获取切片的容量。
-
可以使用内置的append函数向切片添加元素。
四、切片的使用
package main
import "fmt"
func main() {
// 定义切片
var arr [5]int = [5]int{1, 2, 3, 4, 5}
var slice1 []int = arr[1:4] // 定义一个切片,引用数组arr的第2到第4个元素
var slice2 []int = slice1[1:3] // 定义一个切片,引用slice1的第2到第3个元素
var slice3 []int = make([]int, 3, 5) // 定义一个切片,长度为3,容量为5
var slice5 []int = make([]int, 3) // 定义一个切片,长度为3,容量不确定的切片
var slice4 []int = []int{10, 20, 30} // 定义一个切片,并初始化
// 切片的内容、长度、容量、地址
fmt.Println(slice1) // 输出:[2 3 4],切片sliced1的内容
fmt.Println(len(slice1)) // 输出:3,切片slice1包含3个元素
fmt.Println(cap(slice1)) // 输出:4,切片slice1的容量为4
fmt.Printf("%p\n", &slice1) // 输出切片slice1的地址
fmt.Printf("%p\n", &slice5) // 输出切片slice5的地址,使用make创建的切片地址不同于通过数组创建的切片
// 访问切片元素
fmt.Println(slice1[0]) // 输出:2,访问切片中的元素
// fmt.Println(slice1[10]) // 输出:panic: runtime error: index out of range [10] with length 3,访问切片中不存在的元素会导致运行时错误
// 修改切片数据
// slice[10] = 100 // 修改切片中的元素,可能会导致运行时错误,因为切片的长度为3,访问索引10超出范围
slice1[0] = 10 // 修改切片中的元素
fmt.Println(arr, slice1) // 输出:[1 10 3 4 5] [10 3 4],切片修改了底层数组的数据
slice2[0] = 20 // 修改切片中的元素
fmt.Println(arr, slice1, slice2) // 输出:[1 10 20 4 5] [10 20 4] [20 4],切片修改了底层数组的数据,影响了其他引用同一底层数组的切片
slice3[0] = 30 // 修改切片中的元素
fmt.Println(slice3) // 输出:[30 0 0],切片slice3有自己的底层数组,与slice1和slice2不共享数据
slice4[0] = 40 // 修改切片中的元素
fmt.Println(slice4) // 输出:[40 20 30],切片slice4有自己的底层数组,与slice1和slice2不共享数据
// 切片扩容
slice1 = append(slice1, 6) // 向切片添加一个元素,切片长度增加,可能会触发扩容
fmt.Println(arr, slice1) // 输出:[1 10 3 4 5] [10 3 4 6],切片扩容后,底层数组可能发生变化
slice2 = append(slice2, 7) // 向切片添加一个元素,切片长度增加,可能会触发扩容
fmt.Println(arr, slice1, slice2) // 输出:[1 10 3 4 5] [10 3 4 6] [20 4 7],切片扩容后,底层数组可能发生变化,影响了其他引用同一底层数组的切片
slice3 = append(slice3, 8) // 向切片添加一个元素,切片长度增加,可能会触发扩容
fmt.Println(slice3) // 输出:[30 0 0 8],切片扩容后,底层数组可能发生变化
slice4 = append(slice4, 9) // 向切片添加一个元素,切片长度增加,可能会触发扩容
fmt.Println(slice4) // 输出:[40 20 30 9],切片扩容后,底层数组可能发生变化
// 遍历切片
for i, v := range slice1 {
fmt.Printf("slice1[%d] = %d\n", i, v) // 输出切片中的元素
}
for i := 0; i < len(slice2); i++ {
fmt.Printf("slice2[%d] = %d\n", i, slice2[i]) // 输出切片中的元素
}
// 切片传递给数组
changeSlice(slice4)
fmt.Println(slice4) // 输出:[100 20 30 9],切片修改了底层数组的数据
}
func changeSlice(slice []int) {
slice[0] = 100 // 修改切片中的元素
fmt.Println(slice) // 输出:[100 20 30 9],切片修改了底层数组的数据
}
五、切片在内存中的表现
六、切片与数组的区别
-
数组是值类型,切片是引用类型。
-
数组的长度是固定的,切片的长度是动态的。
-
数组在定义时需要指定长度,切片不需要指定长度。
七、切片 append 的使用和扩容机制
-
append函数用于向切片添加元素,语法为:slice = append(slice, elem1, elem2, ...)。当切片的长度增加超过容量时,Go会自动分配一个更大的底层数组,并将原有元素复制到新的数组中。
-
切片的扩容机制通常会将容量增加到原来的两倍,以减少频繁的扩容操作,提高性能。
八、切片的copy函数的使用
-
copy函数用于将一个切片的元素复制到另一个切片,语法为:copy(dest, src)。
-
copy函数会将src切片中的元素复制到dest切片中,复制的元素数量为src和dest切片长度的较小值。
-
copy函数返回复制的元素数量。
-
copy函数不会改变目标切片的长度和容量,但会修改目标切片底层数组中的元素值。
-
copy函数可以用于切片之间的数据复制,也可以用于切片与数组之间的数据复制。
-
copy函数在复制数据时,如果目标切片的容量不足以容纳源切片的所有元素,copy函数会复制尽可能多的元素,并返回实际复制的元素数量。
-
copy函数在复制数据时,如果目标切片的长度不足以容纳源切片的所有元素,copy函数会复制尽可能多的元素,并返回实际复制的元素数量。
-
copy函数在复制数据时,如果目标切片和源切片引用同一个底层数组,并且目标切片的起始位置在源切片的起始位置之前,copy函数会正确处理重叠区域,确保数据正确复制。
-
copy函数在复制数据时,如果目标切片和源切片引用同一个底层数组,并且目标切片的起始位置在源切片的起始位置之后,copy函数会正确处理重叠区域,确保数据正确复制。
-
copy函数在复制数据时,如果目标切片和源切片引用同一个底层数组,并且目标切片和源切片完全重叠,copy函数会正确处理重叠区域,确保数据正确复制。
-
copy函数在复制数据时,如果目标切片和源切片引用同一个底层数组,并且目标切片和源切片没有重叠,copy函数会直接复制数据,不需要处理重叠区域。
-
copy函数在复制数据时,如果目标切片和源切片引用不同的底层数组,copy函数会直接复制数据,不需要处理重叠区域。
-
copy函数在复制数据时,如果目标切片和源切片引用同一个底层数组,并且目标切片的起始位置在源切片的起始位置之前,copy函数会从前向后复制数据,以避免覆盖源切片中的数据。
-
copy函数在复制数据时,如果目标切片和源切片引用同一个底层数组,并且目标切片的起始位置在源切片的起始位置之后,copy函数会从后向前复制数据,以避免覆盖源切片中的数据。
七、切片的使用细节和注意事项
-
切片的长度不能超过其容量。当切片的长度增加超过容量时,Go会自动分配一个更大的底层数组,并将原有元素复制到新的数组中。
-
切片在传递时是按值传递的,但由于切片包含一个指向底层数组的指针,所以切片的副本仍然引用同一个底层数组。这意味着对切片的修改可能会影响到其他引用同一底层数组的切片。
-
slice := append(slice, elem) // 向切片添加元素,可能会触发扩容
-
切片的零值是nil,长度和容量为0。可以通过 make 函数创建一个非 nil 的切片,或者通过切片表达式从一个数组或另一个切片创建一个切片。
-
切片的底层数组可能会被多个切片共享,因此在修改切片时需要注意可能会影响到其他共享同一底层数组的切片。
-
切片的长度和容量可以通过内置函数 len() 和 cap() 获取。切片的长度是切片中元素的数量,容量是从切片的起始位置到底层数组末尾的元素数量。
-
切片可以使用索引访问元素,也可以使用 for-range 循环遍历切片,还可以使用内置函数 append() 向切片添加元素。
-
切片的扩容机制会根据需要自动调整底层数组的大小,以适应切片的增长。当切片的长度增加超过容量时,Go会自动分配一个更大的底层数组,并将原有元素复制到新的数组中。切片的扩容机制通常会将容量增加到原来的两倍,以减少频繁的扩容操作,提高性能。
-
slice [low : high] // 切片表达式,创建一个新的切片,包含从索引 low 到 high-1 的元素
-
切片表达式还可以省略 low 或 high,默认为0或底层数组的长度。例如,arr[:3]表示从索引0到索引2的元素,arr[2:]表示从索引2到数组末尾的元素。
-
切片表达式还可以同时省略 low 和 high,表示引用整个底层数组。例如,arr[:]表示引用整个数组。
-
切片可以继续切片,例如 slice2 := slice1[1:3] 表示从 slice1 的第 2 到第 3 个元素创建一个新的切片 slice2。
-
切片的底层数组可能会被多个切片共享,因此在修改切片时需要注意可能会影响到其他共享同一底层数组的切片。例如,slice1 := arr[1:4] 和 slice2 := slice1[1:3] 都引用了数组 arr 的同一部分,如果修改 slice1 或 slice2 中的元素,可能会影响到另一个切片中的元素,因为它们共享同一个底层数组
-
var slice []int = make([]int, 3, 5) // 定义一个切片,长度为3,容量为5
-
var slice2 []int = make([]int, 3) // 定义一个切片,长度为3,容量不确定的切片
-
使用 make 函数创建的切片底层会自动分配一个数组来支持切片的存储。切片的长度和容量由 make 函数的参数决定。容量是可选的