网站开发一键上架淘宝,网站开发图片,风景旅游网站建设的设计思路,wordpress赞赏切片 切片是一种数据结构#xff0c;这种数据结构便于使用和管理数据集合。切片是围绕动态数组的概念构建的#xff0c;可以按需自动增长和缩小。切片的动态增长是通过内置函数 append 来实现的。这个函数可以快速且高效地增长切片。还可以通过对切片再次切片来缩小一个切片的…切片 切片是一种数据结构这种数据结构便于使用和管理数据集合。切片是围绕动态数组的概念构建的可以按需自动增长和缩小。切片的动态增长是通过内置函数 append 来实现的。这个函数可以快速且高效地增长切片。还可以通过对切片再次切片来缩小一个切片的大小。因为切片的底层内存也是在连续块中分配的所以切片还能获得索引、迭代以及为垃圾回收优化的好处。
声明切片
使用 var 关键字声明切片。指定切片的类型。
初始化切片
可以在声明时初始化切片。也可以使用 make 函数创建切片。
访问切片元素
使用索引访问切片中的元素。
切片的长度和容量
使用内置函数 len 获取切片的长度。使用内置函数 cap 获取切片的容量。
切片的动态扩展
使用 append 函数动态扩展切片。
[]int
[]interface{}内部实现
在运行期间Slice是由如下的SliceHeader结构体进行表示的
type SliceHeader struct {Data uintptr // 数组指针Len int // 长度Cap int // 容量
}这 3 个字段分别是指向底层数组的指针、切片访问的元素的个数即长度和切片允许增长到的元素个数即容量 和C的vector一样在进行扩容时一般会大于实际需要的长度在实际使用中能有效的减少扩容的次数。
空切片和nil切片
空切片和nil切片并不等价空切片和nil切片是不同的。空切片是一个长度为0容量为0的切片而nil切片是一个未初始化的切片它指向一个不存在的内存区域。
创建一个nil切片非常简单只需要声明一个切片变量而不需要初始化。
// 定义一个nil切片
var slice []int
// 定义一个nil切片
slice : []int(nil)创建一个空切片需要使用make函数来创建但是指定长度和容量为0
slice : make([]int, 0)当使用make指定的切片长度为0时那么底层其实会创建一个指针指向zerobase的切片这时的Data指向 zerobase
// base address for all 0-byte allocations
var zerobase uintptr每次调用make来创建切片都会调用到runtime.slice.go中的makeslice函数我们来看下当cap和len为0时makeslice的处理逻辑。
//len 0, cap 0
func makeslice(et *_type, len, cap int) unsafe.Pointer {// mem 0, overflow falsemem, overflow : math.MulUintptr(et.Size_, uintptr(cap))...return mallocgc(mem, et, true)
}func MulUintptr(a, b uintptr) (uintptr, bool) {// a|b 1(4*goarch.PtrSize) 满足// a 0 满足if a|b 1(4*goarch.PtrSize) || a 0 {// 这里会返回a * b 0, overflow falsereturn a * b, false}...
}
// 这里调用时 size 0
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {if gcphase _GCmarktermination {throw(mallocgc called with gcphase _GCmarktermination)}// size 0if size 0 {return unsafe.Pointer(zerobase)}...
}.空切片Data 为 zerobase 使用示例
切片的初始化
通过下标方式获得数组或者切片的一部分使用字面量对切片进行初始化使用关键字make创建切片
// 使用空字符串初始化一个长度为100个元素的切片
slice : []string{99:}
// 引用方式初始化切片
slice : arr[0:3]
// 切片字面量来声明一个长度为3的切片容量为3的切片
slice : []int{1,2,3}
// 只指定长度那么底层长度和容量相同
slie : make([]int, 10)
// 创建一个长度为10,容量为20的切片需要确保长度容量否则会报错
slice : make([]int, 10, 20)使用for range迭代的是副本不是引用
// 创建一个整型切片
// 其长度和容量都是 4 个元素
slice : []int{10, 20, 30, 40}
// 迭代每一个元素使用range迭代切片是副本
for index, value : range slice {fmt.Printf(Index: %d Value: %d\n, index, value)
}slice : make([]int, 5, 20)
// 切片引用为前闭后开 [1,3)
sliceRef : slice[1:3]
// 切片长度为2容量为 20 - 1引用之后切片前端会从引用点开始剩余元素保留
fmt.Println(len(sliceRef), cap(sliceRef))
// 修改引用切片的内容也会同步修改原切片
sliceRef[0] 100
// slice[1] 100
fmt.Println(slice[1])
// 当修改超过切片长度但是在容量范围内的元素时会抛出panic
//slice[6] 200
//函数 append 总是会增加新切片的长度而容量有可能会改变也可能不会改变这取决于被操作的切片的可用容量。
slice append(slice, 200)
//slice : source[low:high:max]
//source 是原始切片或数组。
//low 是切片的起始索引包含。
//high 是切片的结束索引不包含。
//max 是切片的最大容量位置(不包含)
slice : slice[2:4:10]// 如果容量设置大于已有容量会出现运行时错误
source : [5]int{1, 2, 3, 4, 5}
slice : source[2:3:4]
//长度 (len(slice))切片的长度是从 low 到 high 之间的元素数量即 high - low。
//容量 (cap(slice))切片的容量是从 low 到 max 之间的元素数量即 max - low。
//对于 slice[i:j:k] 或 [2:3:4]
//长度: j – i 或 3 - 2 1
//容量: k – i 或 4 - 2 2
fmt.Println(Slice:, slice) // 输出: Slice: [3]
fmt.Println(Length:, len(slice)) // 输出: Length: 1
fmt.Println(Capacity:, cap(slice)) // 输出: Capacity: 2// 设置长度和切片容量一致的好处
// 创建字符串切片
// 其长度和容量都是 5 个元素
source : []string{Apple, Orange, Plum, Banana, Grape}
// 对第三个元素做切片并限制容量
// 其长度和容量都是 1 个元素
slice : source[2:3:3]
// 向 slice 追加新字符串因为引用时容量和长度一致只要调用append() 方法就会自动增加容量
// 后面再修改引用切片就不会影响原切片的内容了
slice append(slice, Kiwi)切片的子切片
package mainimport fmtfunc main() {// 创建一个整数切片numbers : []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}// 创建子切片切片的引用subSlice : numbers[2:5] // 子切片从索引 2 到 4不包括 5// 打印子切片fmt.Println(subSlice) // 输出: [3 4 5]
}切片使用注意事项
初始容量设置合理设置初始容量减少内存重新分配的次数。避免不必要的拷贝尽量减少频繁的 append 操作。预分配足够的容量如果可以预估最终大小一次性分配足够的容量。避免多次 append 操作收集所有要添加的元素一次性 append。检查切片容量在 append 前检查容量必要时手动重新分配内存。使用 copy 函数在某些情况下使用 copy 函数可以更高效地复制数据。
避免不必要的拷贝
每次 append 操作超过当前切片的容量时Go 会分配一个新的更大的底层数组并将原有数据复制到新的数组中。频繁的拷贝操作会影响性能。
package mainimport fmtfunc main() {// 创建一个初始容量为 2 的切片numbers : make([]int, 0, 2)// 动态扩展切片for i : 1; i 15; i {numbers append(numbers, i)}// 打印切片fmt.Println(numbers) // 输出: [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15]
}预分配足够的容量
如果可以预估最终切片的大小建议一次性分配足够的容量以减少内存重新分配的次数。
package mainimport fmtfunc main() {// 创建一个初始容量为 15 的切片numbers : make([]int, 0, 15)// 动态扩展切片for i : 1; i 15; i {numbers append(numbers, i)}// 打印切片fmt.Println(numbers) // 输出: [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15]
}避免多次 append 操作如果需要多次 append 操作可以考虑先收集所有要添加的元素然后一次性 append。
package mainimport fmtfunc main() {// 创建一个初始容量为 10 的切片numbers : make([]int, 0, 10)// 收集要添加的元素newElements : []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}// 一次性 appendnumbers append(numbers, newElements...)// 打印切片fmt.Println(numbers) // 输出: [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15]
}在进行 append 操作前可以检查当前切片的容量以决定是否需要重新分配内存。一旦发生内存重新分配就会存在大规模的copy因此一定要提前初始化足够的切片避免冲突发生切片赋值的情况
package mainimport fmtfunc main() {// 创建一个初始容量为 10 的切片numbers : make([]int, 0, 10)// 动态扩展切片for i : 1; i 15; i {if cap(numbers) len(numbers) 1 {// 需要重新分配内存newCapacity : cap(numbers) * 2if newCapacity len(numbers) 1 {newCapacity len(numbers) 1}newSlice : make([]int, len(numbers), newCapacity)copy(newSlice, numbers)numbers newSlice}numbers append(numbers, i)}// 打印切片fmt.Println(numbers) // 输出: [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15]
}在某些情况下使用 copy 函数可以更高效地复制数据。
package mainimport fmtfunc main() {// 创建一个初始容量为 10 的切片numbers : make([]int, 0, 10)// 收集要添加的元素newElements : []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}// 一次性 appendnumbers append(numbers, newElements...)// 使用 copy 函数newNumbers : make([]int, len(numbers))copy(newNumbers, numbers)// 打印切片fmt.Println(newNumbers) // 输出: [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15]
}