Go语言切片
<p class="toc">目录 在上一篇文章中已经了解了数组,数组有特定的用处,但是却有一些呆板(数组长度固定不可变),所以在 Go 语言的代码里并不是特别常见。接下来聊聊切片(slice),相对的,切片却是随处可见的,Go语言切片是一种建立在数组类型之上的抽象,它构建在数组之上并且提供更强大的能力和便捷。 切片(slice)是对数组一个连续片段的引用(该数组我们称之为相关数组,通常是匿名的),所以切片是一个引用类型(因此更类似于 C/C++ 中的数组类型)。这个片段可以是整个数组,或者是由起始和终止索引标识的一些项的子集。需要注意的是,终止索引标识的项不包括在切片内。切片提供了一个相关数组的动态窗口。 切片是可索引的,并且可以由 len() 函数获取长度。 给定项的切片索引可能比相关数组的相同元素的索引小。和数组不同的是,切片的长度可以在运行时修 改,最小为 0 最大为相关数组的长度:切片是一个 长度可变的数组。 切片对象非常小,是因为它是只有3个字段的数据结构:一个是指向底层数组的指针,一个是切片的长度,一个是切片的容量。这3个字段,就是Go语言操作底层数组的元数据,有了它们,我们就可以任意的操作切片了。 切片提供了计算容量的函数 多个切片如果表示同一个数组的片段,它们可以共享数据;因此一个切片和相关数组的其他切片是共享存储的,相反,不同的数组总是代表不同的存储。数组实际上是切片的构建块。 优点 因为切片是引用,所以它们不需要使用额外的内存并且比使用数组更有效率,所以在 Go 代码中 切片比数组更常用。 声明切片的方式和声明数组的方式差不都 // 变量名 变量类型 var variable_name = []var_type 如果切片只声明而没有初始化,那么这个切片的默认值为 package main <h2 id="初始化数组">初始化数组 make方式创建切片,对应用类型的数据都可以使用 slice := make([]int,10,20) 这里我们创建了一个类型为 因为切片的底层是数组,所以创建切片时,如果不指定字面值的话,默认值就是数组的元素的零值。这里我们所以指定了容量是20,但是我们职能访问10个元素,因为切片的长度是10,剩下的10个元素,需要切片扩充后才可以访问。
还有一种创建切片的方式,是使用字面量,就是指定初始化的值。 slice := []int{1,2,3,4,5} 通过字面量创建切片和创建数组的方式非常像,只不过不用指定 slice := []int{4:1} 这是指定了第5个元素为1,其他元素都是默认值0。这时候切片的长度和容量也是一样的。这里再次强调一下切片和数组的微小差别。 //数组 array := [...]int{4:1} //切片 slice := []int{4:1} 切片还有nil切片和空切片,它们的长度和容量都是0,但是它们指向底层数组的指针不一样,nil切片意味着指向底层数组的指针为nil,而空切片对应的指针是个地址。 //nil切片 var slice []int //空切片 slice:=[]int{} nil切片表示不存在的切片,而空切片表示一个空集合,它们各有用处。 切片另外一个用处比较多的创建是基于现有的数组或者切片创建。 slice := []int{1,5} slice1 := slice[:] slice2 := slice[0:] slice3 := slice[:5] 基于现有的切片或者数组创建,使用
slice := []int{1,5} newSlice := slice[1:3] 这个例子证明了,新的切片和原切片共用的是一个底层数组,所以当修改的时候,底层数组的值就会被改变,所以原切片的值也改变了。当然对于基于数组的切片也一样的。 我们基于原数组或者切片创建一个新的切片后,那么新的切片的大小和容量是多少呢?这里有个公式: 对于底层数组容量是k的切片slice[i:j]来说 长度:j-i 容量:k-i 比如我们上面的例子 slice := []int{1,5} newSlice := slice[1:3] 以上基于一个数组或者切片使用2个索引创建新切片的方法,此外还有一种3个索引的方法,第3个用来限定新切片的容量,其用法为 slice := []int{1,5} newSlice := slice[1:2:3] 这样我们就创建了一个长度为
我们可以分析一下,上图中的切片在内存中结构 切片在内存中的组织方式实际上是一个有 3 个域的结构体:指向相关数组的指针,切片长度以及切片容量。 注意 绝对不要用指针指向 slice。切片本身已经是一个引用类型,所以它本身就是一个指针!! 正因为这样,所以我们数组首地址需要取地址符(&),但是打印一个切片的地址的时候就不要加取地址符了。 func main() { <h2 id="使用切片">使用切片 使用切片,和使用数组一样,通过索引就可以获取切片对应元素的值,同样也可以修改对应元素的值。 slice := []int{1,5} fmt.Println(slice[2]) //获取值 slice[2] = 10 //修改值 fmt.Println(slice[2]) //输出10
我们前面讲了,切片算是一个动态数组,所以它可以按需增长,我们使用内置 slice := []int{1,5} newSlice := slice[1:3] 例子中,通过
如果切片的底层数组,没有足够的容量时,就会新建一个底层数组,把原来数组的值复制到新底层数组里,再追加新值,这时候就不会影响原来的底层数组了。
内置的 newSlice=append(newSlice,20,30) 此外,我们还可以通过 slice := []int{1,5} newSlice := slice[1:2:3] <h2 id="迭代切片">迭代切片 切片是一个集合,我们可以使用 for range 循环来迭代它,打印其中的每个元素以及对应的索引。 slice := []int{1,5} for i,v:=range slice{ fmt.Printf("索引:%d,值:%dn",i,v) } 如果我们不想要索引,可以使用 slice := []int{1,5} for _,v:=range slice{ fmt.Printf("值:%dn",v) } 这里需要说明的是 除了for range循环外,我们也可以使用传统的for循环,配合内置的len函数进行迭代。 slice := []int{1,5} for i := 0; i < len(slice); i++ { fmt.Printf("值:%dn",slice[i]) } 我们已经知道切片创建的时候通常比相关数组小,例如: slice1 := make([]type,start_length,capacity) 其中 start_length 作为切片初始长度而 capacity 作为相关数组的长度。 这么做的好处是我们的切片在达到容量上限后可以扩容。改变切片长度的过程称之为切片重组 reslicing,做法如下: 将切片扩展 1 位可以这么做: slice = slice[0:len(slice)+1] 切片可以反复扩展直到占据整个相关数组。 其实无论是值类型还是引用类型,函数间的传递都是值传递,只不过值类型的数据传递是传递的是变量的值,而引用类型在函数间的传递的是变量的地址,然而这个地址其实也是一个值。 package main 打印的输出如下:
仔细看,这两个切片的地址是一样的,所以这两个切片指向同一个内存地址。因此我们修改一个索引的值后,发现原切片的值也被修改了,说明它们共用一个底层数组。 看起来二者没有什么区别,都在堆上分配内存,但是它们的行为不同,适用于不同的类型。
假设 s 是一个字符串(本质上是一个字节数组),那么就可以直接通过 同样的,还可以使用 for-range 来获得每个元素 package main 输出: 我 爱 你 中 国 我们知道,Unicode 字符会占用 2 个字节,有些甚至需要 3 个或者 4 个字节来进行表示。如果发现错误的 UTF8 字符,则该字符会被设置为 U+FFFD 并且索引向前移动一个字节。和字符串转换一样,您同样可以使用 可以通过代码 您还可以将一个字符串追加到某一个字符数组的尾部: import "fmt" <h3 id="字符串和切片的内存结构">字符串和切片的内存结构 在内存中,一个字符串实际上是一个双字结构,即一个指向实际数据的指针和记录字符串长度的整数。因为指针对用户来说是完全不可见,因此我们可以依旧把字符串看做是一个值类型,也就是一个字符数组。 字符串 Go 语言中的字符串是不可变的,也就是说 因此,您必须先将字符串转换成字节数组,然后再通过修改数组中的元素值来达到修改字符串的目的,最后将字节数组转换回字符串格式。 例如,将字符串 "hello" 转换为 "cello": s := "hello" c := []byte(s) c[0] = 'c' s2 := string(c) //s2 == "cello" 所以,您可以通过操作切片来完成对字符串的操作。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |