一文看懂 slice

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Go 语言中虽然有数组,但在代码中直接用的比较少,而是会间接的用到,slice 存储数据就是用的 数组,甚至可以认为数组是为了 slice 存在。Go 语言中的 slice 可以当做数组来使用,也可以当做其他语言中的 List 来使用。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"slice 表示一个拥有相同类型元素的可变长的序列。可变长是 slice 最重要的一个特性,这是它和数组最不同的地方,既然 slice 是依靠数组而存在的,那么 slice 是如何做到可变长的呢,这篇文章就来聊一下。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"先从 slice 的创建说起。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#FF7021","name":"orange"}}],"text":"1. slice 的创建","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"slice 可以主动创建,也可从现有的数组中获取。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"带初始化元素的方式:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"nums := []int{0, 1, 2, 3, 4, 5}\nfmt.Println(len(nums)) // 6\nfmt.Println(cap(nums)) // 6","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不带初始化元素的方式,下面的这种方式会自动填充零值:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"nums := make([]int, 6)\nfmt.Println(len(nums)) // 6\nfmt.Println(cap(nums)) // 6","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"还可以创建一个更大容量的 slice:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"nums := make([]int, 6, 12)\nfmt.Println(len(nums)) // 6\nfmt.Println(cap(nums)) // 12","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在这里需要需要注意,虽然 slice 容量有 12,但是却不能直接访问 slice 长度范围外的元素,否则会出现 panic:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"fmt.Println(nums[6]) // panic","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"要么通过扩展 slice 的长度,要么使用 append 函数来添加元素。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"nums = nums[:len(nums)+1] // 将 slice 的长度扩展大 1\nnums = append(nums, 1) // 使用 append 将 nums 的长度加 1","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在使用上面的方式创建 slice 时,Go 会自动的创建一个底层数组来存储数据,slice 本身并不会存储数据。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"还有一种方式是通过数组来创建 slice:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"numsArr := [...]int{0, 1, 2, 3, 4, 5}\n\nnumsSlice := numsArr[:] // 这表示 slice 容纳数组的所有元素\nfmt.Println(len(numsSlice)) // 6\nfmt.Println(cap(numsSlice)) // 6","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#FF7021","name":"orange"}}],"text":"2. slice 的结构","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一般 可以把 slice 看成是一个复合结构,结构如下,由三部分组成:指针、长度和容量:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"type Slice struct {\n ptr *T\n len, cap int\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"slice 本身不会存储任何数据,slice 通过指针指向底层数组的某一个位置,这个位置就是 slice 的初始位置。长度表示当前 slice 中的元素个数,容量表示从指针的位置到底层数组的最后一个位置。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"slice 可变长的第一个原因就是可以通过改变指针的位置来改变 slice 的长度。比如下面这样:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"numsSlice2 := numsSlice[1:3]\nfmt.Println(len(numsSlice2)) // 2\nfmt.Prinltn(cap(numsSlice2)) // 5","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这个操作在 numsSlice 的基础上创建的一个新的 slice,其实就是移动了一下 slice 的指针。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外 slice 不能使用 == 进行比较,因为 slice 的元素是非直接的,也就是 slice 本身不存储值,slice 甚至可以包含自身。另外因为 slice 的元素是靠底层的数组存储,所以当底层数组变动的时候,slice 读取到的值也会产生变化。如果 使用 == 来进行比较,可能会产生很多预料之外的结果。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#FF7021","name":"orange"}}],"text":"3. 理解 slice 的 cap","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"对于初学者来说,会把 slice 的长度和容量搞混。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"先来看下面这段代码:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"numsArr := [...]int{1, 2, 3, 4, 5, 6}\n\ns1 := numsArr[0 : 3]\nfmt.Println(s1) // [1,2,3]\nfmt.Println(cap(s1)) // 6\nfmt.Println(len(s1)) // 3\n\ns2 := numsArr[3:5]\nfmt.Println(s2) // [4,5]\nfmt.Println(cap(s2)) // 3\nfmt.Println(len(s2)) // 2","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们可以发现,同样是从数组上来生成 slice,但是生成之后的 slice 的容量却不一样,在上面而我们说到了 slice 的初始位置靠指针来决定,s1 指针的位置在数组的最开始的位置。按照容量的定义,从指针到底层数组结束的地方,所以它的 容量还是 6。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而 s2 中,指针的位置是数组的第四个元素,长度是 3,容量也是 3。这是初学者最容易犯错的地方。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"长度表示 slice 中目前可访问的元素个数,容量则表示这个 slice 在不改变底层数组的情况下,最多个扩展到的长度。如果要扩展 slice,使用下面的操作就可以:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"s2 = s2[:3] // 把上面 s2 的长度从 2 扩展到 3\nfmt.Println(s2[2]) // 访问第三个元素","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这种方式是让 slice 可变长的第二种方式。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#FF7021","name":"orange"}}],"text":"4. 理解 append","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"append 方法也是让 slice 可变长的一种方式。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"append 函数经常会配合 slice 一起使用,在使用 append 的时候,我们需要使用下面的语法:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"nums = append(nums, 1)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而且这种做法是必须的,如果没有采用这种做法,可能会产生意料之外的结果。看下面的代码:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"numsArr := [...]int{1, 2, 3, 4, 5, 6}\n\ns1 := numsArr[3:5]fmt.Println(cap(s1)) // 3fmt.Println(len(s1)) // 2\n\n_ = append(s1, 1)\n\nfmt.Println(s1[2]) // panic","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面的代码在调用 append 之后,如果没有使用返回的 slice,而是直接使用原来的 slice,就会产生越界的错误。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因为 slice 本身虽然是可变长的,所以如果 slice 还有容量,那么每次添加元素,都需要扩展 slice 的长度,返回的也是一个新的 slice。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外 slice 依赖的底层数组是固定长度,在使用 append 时,如果底层的数组不足以存储新的元素之后,就需要扩容,扩容之后就会产生一个新的 slice 返回。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以理解 append 方法修改传入的 slice,而我们在调用 append 函数之后,就需要使用函数返回的结果。所以上面的代码需要修改为:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"numsArr := [...]int{1, 2, 3, 4, 5, 6}\n\ns1 := numsArr[3:5]\nfmt.Println(cap(s1)) // 3\nfmt.Println(len(s1)) // 2\n\ns1 = append(s1, 1)\n\nfmt.Println(s1[2]) //1\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#FF7021","name":"orange"}}],"text":"5. 小结","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"slice 是一个很有用的数据接口,既可以当数组用,也可以当 list 使用。在使用 slice 的过程中,一定要注意 slice 本身不存储数据,它只是在一个底层数组上,截取不同长度序列,可以说 slice 就是一个数组的子序列。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外我们说到了 slice 可变长的几种方式,一种是通过移动 slice 的指针,改变 slice 的长度,第二种是 slice 容量范围内扩展长度,第三种是通过 append 方式,这种方式会创建一个新的 slice。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外我们经常会对 string 做子串的截取操作和这里 slice 工作原理是一样的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#FF7021","name":"orange"}}],"text":"文 / Rayjun","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文首发于微信公众号【Rayjun】","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章