网站js下载,Wordpress手机短信,WordPress文章数据转emlog,无锡网站排名优化1.概念 在Go语言中#xff0c;切片#xff08;Slice#xff09;是一种基于数组的更高级的数据结构#xff0c;它提供了一种灵活、动态的方式来处理序列数据。切片在Go中非常常用#xff0c;因为它们可以动态地增长和缩小#xff0c;这使得它们比固定大小的数组更加灵活。…1.概念 在Go语言中切片Slice是一种基于数组的更高级的数据结构它提供了一种灵活、动态的方式来处理序列数据。切片在Go中非常常用因为它们可以动态地增长和缩小这使得它们比固定大小的数组更加灵活。 2.切片的内部实现 切片是对数组的抽象表示它包含三个要素 1. 指向底层数组的指针 2. 切片的长度元素数量 3. 切片的容量从开始到底层数组末尾的元素数量 切片是一个有三个字段的数据结构这些数据结构包含 Golang 需要操作底层数组的元数据 这 3 个字段分别是指向底层数组的指针、切片访问的元素的个数(即长度)和切片允许增长到的元素个数(即容量)。
3.切片创建和初始化
在 Golang 中可以通过多种方式创建和初始化切片。是否提前知道切片所需的容量通常会决定如何创建切片。
通过 make() 函数创建切片 使用 Golang 内置的 make() 函数创建切片此时需要传入一个参数来指定切片的长度
slice : make([]int, length, capacity)
这里length是切片的长度capacity是切片的容量。如果未指定capacity它将等于length。
// 创建一个整型切片
// 其长度和容量都是 5 个元素
slice : make([]int, 5)
分别指定长度和容量时创建的切片底层数组的长度是指定的容量但是初始化后并不能 访问所有的数组元素。
注意Golang 不允许创建容量小于长度的切片当创建的切片容量小于长度时会在编译时刻报错
// 创建一个整型切片
// 使其长度大于容量
myNum : make([]int, 5, 3)
分别指定长度和容量时创建的切片底层数组的长度是指定的容量但是初始化后并不能 访问所有的数组元素。
注意Golang 不允许创建容量小于长度的切片当创建的切片容量小于长度时会在编译时刻报错
4.通过字面量创建切片
另一种常用的创建切片的方法是使用切片字面量这种方法和创建数组类似只是不需要指定[]运算符里的值。初始的长度和容量会基于初始化时提供的元素的个数确定
// 创建字符串切片
// 其长度和容量都是 3 个元素
myStr : []string{Jack, Mark, Nick}
// 创建一个整型切片
// 其长度和容量都是 4 个元素
myNum : []int{10, 20, 30, 40}
当使用切片字面量创建切片时还可以设置初始长度和容量。要做的就是在初始化时给出所需的长度和容量作为索引。下面的语法展示了如何使用索引方式创建长度和容量都是100个元素的切片
// 创建字符串切片
// 使用空字符串初始化第 100 个元素
myStr : []string{99: }
区分数组的声明和切片的声明方式 当使用字面量来声明切片时其语法与使用字面量声明数组非常相似。二者的区别是如果在 [] 运算符里指定了一个值那么创建的就是数组而不是切片。只有在 [] 中不指定值的时候创建的才是切片。看下面的例子
// 创建有 3 个元素的整型数组
myArray : [3]int{10, 20, 30}
// 创建长度和容量都是 3 的整型切片
mySlice : []int{10, 20, 30}
切片的复制
当你有一个切片或者数组的时候当希望将其元素作为单独的参数传递给一个函数时候可以使用...操作符
func sum(x, y, z int) int {return x y z
}numbers : []int{1, 2, 3}
result : sum(numbers...) // 将 numbers 切片中的元素作为参数传递给 sum 函数
fmt.Println(result) // 输出: 6在这个例子中numbers...将切片numbers中的元素1,2,3展开为sum函数的参数
5.nil 和空切片
有时程序可能需要声明一个值为 nil 的切片也称nil切片。只要在声明时不做任何初始化就会创建一个 nil 切片
// 创建 nil 整型切片
var myNum []int
在 Golang 中nil 切片是很常见的创建切片的方法。nil 切片可以用于很多标准库和内置函数。在需要描述一个不存在的切片时nil 切片会很好用。比如函数要求返回一个切片但是发生异常的时候。下图描述了 nil 切片的状态 空切片和 nil 切片稍有不同下面的代码分别通过 make() 函数和字面量的方式创建空切片
// 使用 make 创建空的整型切片
myNum : make([]int, 0)
// 使用切片字面量创建空的整型切片
myNum : []int{} 区别 1. 内存分配• nil 切片没有指向任何底层数组没有任何内存分配。 • 空切片指向一个长度为0的底层数组但这个数组可能已经分配了内存尽管长度为0。 2. 函数返回值• 使用 nil 可以表示“没有值”这在错误处理和可选值中很有用。 • 空切片通常用于表示一个有效的切片但当前没有元素。 package main
import fmtfunc main() {//创建一个空切片emptySlice :make([]int,0)fmt.Println(emptySlice)//创建一个nil切片var nilSlice []intfmt.Println(nilSlice)//检查切片是否为空if len(emptySlice) 0 {fmt.Println(emptySlice is empty)}if nilSlice nil {fmt.Println(nilSlice is nil)}//尝试访问空切片的元素if len(emptySlice) 0 {fmt.Println(emptySlice[0])}else {fmt.Println(emptySlice is empty)}}• emptySlice是一个空切片它有长度0但是它指向一个内存地址。 • nilSlice是一个nil指针它没有指向任何内存地址因此不能用来访问任何元素。 尝试访问nil切片的元素会导致程序崩溃panic因为nil没有指向有效的内存。而空切片虽然长度为0但是它是指向一个有效的内存地址的所以可以安全地对其进行操作比如扩展或赋值。 6.为切片中的元素赋值
对切片里某个索引指向的元素赋值和对数组里某个索引指向的元素赋值的方法完全一样。使 用 [] 操作符就可以改变某个元素的值下面是使用切片字面量来声明切片
// 创建一个整型切片
// 其容量和长度都是 5 个元素
myNum : []int{10, 20, 30, 40, 50}
// 改变索引为 1 的元素的值
myNum [1] 25
切片之所以被称为切片是因为创建一个新的切片也就是把底层数组切出一部分。通过切片创建新切片的语法如下
slice[i:j]
slice[i:j:k]
其中 i 表示从 slice 的第几个元素开始切j 控制切片的长度(j-i)k 控制切片的容量(k-i)如果没有给定 k则表示切到底层数组的最尾部。下面是几种常见的简写形式
slice[i:] // 从 i 切到最尾部
slice[:j] // 从最开头切到 j(不包含 j)
slice[:] // 从头切到尾等价于复制整个 slice
让我们通过下面的例子来理解通过切片创建新的切片的本质
// 创建一个整型切片
// 其长度和容量都是 5 个元素
myNum : []int{10, 20, 30, 40, 50}
// 创建一个新切片
// 其长度为 2 个元素容量为 4 个元素
newNum : slice[1:3]
执行上面的代码后我们有了两个切片它们共享同一段底层数组但通过不同的切片会看到底层数组的不同部分 下面是详细的用法
package mainimport fmtfunc main() {// 创建一个原始切片original : []int{1, 2, 3, 4, 5}// 通过切片操作符创建新的切片a : original[:3] // 包含从索引0到索引2的元素b : original[2:] // 包含从索引2到末尾的元素c : original[:] // 创建一个新的切片它是原始切片的一个副本// 修改原始切片original[0] 10// 打印结果fmt.Println(Original slice:, original) // 输出: [10 2 3 4 5]fmt.Println(Slice a:, a) // 输出: [1 2 3]fmt.Println(Slice b:, b) // 输出: [3 4 5]fmt.Println(Slice c:, c) // 输出: [1 2 3 4 5]// 修改新的切片aa[0] 100// 打印结果fmt.Println(Original slice after modifying a:, original) // 输出: [10 2 3 4 5]fmt.Println(Slice a after modification:, a) // 输出: [100 2 3]
}共享底层数组的切片 需要注意的是现在两个切片 myNum 和 newNum 共享同一个底层数组。如果一个切片修改了该底层数组的共享 部分另一个切片也能感知到(请参考前图) 在Go语言中切片是引用类型这意味着它们指向底层数组的连续区域。当你通过切片操作创建一个新的切片时新切片和原始切片可能共享同一个底层数组。如果新切片的容量足够对新切片的修改可能会反映到原始切片上因为它们可能指向相同的底层数组元素。 如果你需要一个与原始切片完全独立的副本可以使用copy函数来创建一个新的底层数组。这样对新数组的修改就不会影响原始数组。 下面是一个详细的示例展示了如何使用copy函数创建一个完全独立的切片副本并解释了为什么这样做可以避免对原始切片的意外修改。
package mainimport fmtfunc main() {original : []int{1,2,3,4,5}//使用切片操作符创建一个新的切片它与原始切片共享同一个底层数组sharedSlice : original[:]independentSlice :make([]int, len(original))copy(independentSlice, original)// 打印原始切片和两个新切片的内容fmt.Println(Original slice:, original) // 输出: [1 2 3 4 5]fmt.Println(Shared slice:, sharedSlice) // 输出: [1 2 3 4 5]fmt.Println(Independent slice:, independentSlice) // 输出: [1 2 3 4 5]//修改原始切片original[0] 10fmt.Println(Modified original slice:, original) // 输出: [10 2 3 4 5]fmt.Println(Shared slice after modification:, sharedSlice) // 输出: [10 2 3 4 5]//修改独立切片independentSlice[0] 20fmt.Println(Independent slice after modification:, independentSlice) // 输出: [20 2 3 4 5]//修改独立切片之后原始切片不受影响fmt.Println(Original slice after independent modification:, original) // 输出: [10 2 3 4 5]fmt.Println(Shared slice after independent modification:, sharedSlice) // 输出: [10 2 3 4 5]}代码运行结果 7.切片扩容
Go 语言中的切片slice是一种动态数组它允许你动态地增长和缩小。当你向切片添加元素而切片的容量capacity不足以容纳更多元素时Go 语言会自动进行扩容。扩容的具体机制如下 1. 初始容量当你创建一个新的切片时它会有一个初始的容量。如果你通过 make 函数创建切片你可以指定切片的长度length和容量capacity。2. 扩容机制当你向切片添加元素超出当前容量时Go 语言会进行扩容。扩容通常遵循以下规则 扩容后的容量通常是原容量的两倍但具体增长因子可能因实现而异。 如果切片的容量已经很大扩容可能不会简单地翻倍而是增加一个固定的数量。3. 扩容过程扩容时Go 语言会分配一个新的数组并将原切片中的元素复制到新数组中。然后新元素会被添加到新数组中。最后切片的底层数组指针会被更新为指向新数组。4. 性能考虑频繁的扩容可能会导致性能问题因为每次扩容都需要分配新的内存并复制元素。为了避免这种情况你可以通过预先分配足够的容量来减少扩容的次数。5. 手动扩容虽然 Go 语言会自动管理切片的扩容但你也可以手动扩容切片。例如你可以使用 append 函数来添加元素或者使用 copy 函数和新的切片来手动复制元素。 下面是一个简单的 Go 语言切片扩容的
package mainimport fmtfunc main() {s : make([]int, 0, 1) // 创建一个长度为0容量为1的切片for i : 0; i 5; i {s append(s, i) // 向切片添加元素fmt.Printf(len%d cap%d slice%v\n, len(s), cap(s), s)}
}运行结果
len1 cap1 slice[0]
len2 cap2 slice[0 1]
len3 cap4 slice[0 1 2]
len4 cap4 slice[0 1 2 3]
len5 cap8 slice[0 1 2 3 4]函数 append() 会智能地处理底层数组的容量增长。在切片的容量小于 1000 个元素时总是会成倍地增加容量。一旦元素个数超过 1000容量的增长因子会设为 1.25也就是会每次增加 25%的容量(随着语言的演化这种增长算法可能会有所改变)。
8.遍历切片
8.1使用for循环和索引
package mainimport fmtfunc main() {slice : []int{10, 20, 30, 40, 50}for i : 0; i len(slice); i {fmt.Println(Element at index, i, is, slice[i])}
}8.2 使用for循环和range
使用range关键字可以同时获取索引和值这是遍历切片的常用方法
package mainimport fmtfunc main() {slice :[]int{10,20,30,40,50}for index,value : range slice{fmt.Printf(Index is %d and value is %d\n,index,value)}
}8.3 使用 for 循环和 range只获取值
如果你不需要索引只关心值可以使用以下方式简化代码。
用以下方式简化代码。
package mainimport fmtfunc main() {slice : []int{10, 20, 30, 40, 50}for _, value : range slice {fmt.Println(Value is, value)}
}
8.4 使用 for 循环和 range只获取索引
如果你只需要索引可以使用下划线 _ 忽略值。
package mainimport fmtfunc main() {slice : []int{10, 20, 30, 40, 50}for index, _ : range slice {fmt.Println(Index is, index)}
}8.5 使用 for 循环和 range遍历字符串切片
对于字符串切片range 会返回每个字符串中的每个字符作为 rune 类型。
package mainimport fmtfunc main() {slice : []string{hello, world}for _, word : range slice {for _, char : range word {fmt.Printf(%c , char)}fmt.Println()}
}9.切片操作
9.1 使用copy函数
copy 函数是标准库 copy 包中提供的一个函数用于将一个切片的内容复制到另一个切片中。它接受两个切片作为参数源切片和目标切片并返回复制的元素数量。
package mainimport fmtfunc main() {src :[]int{1,2,3,4,5}dst :make([]int,5)//从src复制到dstcopied :copy(dst,src)fmt.Println(Copied element,copied)fmt.Println(Destination,dst)
}9.2 使用 append 函数 第一个参数是目标切片用于接收新添加的元素。 后续参数是要添加到切片中的元素。 使用 append 进行切片复制在 append([]int(nil), original...) 中 []int(nil) 创建了一个类型为 []int 的空切片。这个切片的长度和容量都是0。 original... 是 Go 语言的变参语法它将 original 切片中的所有元素作为独立的参数传递给 append 函数。 package mainimport fmtfunc main() {original : []int{1, 2, 3, 4, 5}copy : append([]int(nil), original...)fmt.Println(Original slice:, original)fmt.Println(Copy slice:, copy)
}
在这个例子中copy 切片是通过 append 函数创建的它包含了 original 切片的所有元素。这种方法不仅代码简洁而且性能也很好因为它避免了不必要的内存分配和复制操作。 总结来说append([]int(nil), original...) 是一种利用 append 函数的灵活性和智能内存管理来高效复制切片的方法。
9.3手动复制
你也可以通过遍历源切片并手动将每个元素赋值到新的切片中来实现复制。
package mainimport (fmt
)func main() {src : []int{1, 2, 3, 4, 5}dst : make([]int, len(src)) // 创建一个相同长度的新切片for i : range src {dst[i] src[i]}fmt.Println(Source slice:, src)fmt.Println(Destination slice:, dst)
}
这种方法虽然直接但效率较低特别是对于大型切片。
注意事项 • 当使用 copy 函数时确保目标切片有足够的容量来接收所有元素否则它只会复制目标切片的容量允许的部分。 • 使用 append 函数时虽然方便但可能会因为扩容操作而增加额外的性能开销。 • 手动复制虽然控制更精细但代码更复杂且容易出错。 在实际应用中选择哪种方法取决于具体的需求和性能考虑。对于大多数情况copy 函数提供了一个简单而高效的方式来复制切片。