数组和切片

对比学习数组和切片

数组的长度是固定的,切片是可变长的。

数组的类型字面量中必须有元素的类型和长度。数组的长度在声明的时候必须给定,并且之后不会再改变,数组的长度是其类型的一部分。比如[1]string[2]string就是2个不同的数组类型。

切片的类型字面量中只有元素的类型,而没有长度。切片的长度可以自动地随着其中元素数量的增长而增长,但不会随着元素数量的减少而减小。

可以把切片看作是对数组的一层简单封装,因为在每个切片额底层数据结构中,一定会包含一个数组。数组可以叫做切片的底层数组,而切片可以看做是对数组的某个连续片段的引用。

正因为“切片可以看作是数组的某个连续片段的引用”,Go语言的切片类型属于引用类型。同属于引用类型的还有字典类型,通道类型,函数类型等;而Go语言的数组类型则属于值类型,同属于值类型的还有基础数据类型以及结构体类型。

Go语言里不存在像Java等编程语言中令人困惑的“传值或传引用”问题。在Go语言中,我们判断所谓的“传值”或者“传引用”只要看传递的值的类型就好了,如果传递的值的类型是值类型,那么就是“传值”,如果传递的值的类型是引用类型的,那么就是“传引用”。从传递成本的角度讲,引用类型的值往往要比值类型的值低很多。

可以在数组和切片之上应用“索引表达式”,得到的是某个元素。 也可以在数组和切片上应用“切片表达式”,得到的是一个新的切片。

调用内建函数len,可以得到数组和切片的长度。数组的容量永远等于其长度,都是不可变的。 调用内建函数cap,可以得到数组和切片的容量。切片的容量是可变的,变化也是有规律可循的。

怎样正确估算切片的长度和容量?

内建函数make可以创建切片。 make([]int,5,8)第一个参数[]int指明切片的类型,第二个参数5指明切片的长度,第三个参数8指明切片的容量。

切片的容量是什么意思呢? 还记得上面说的吗:数组是切片的底层数据结构的一部分。切片的容量实际上代表了它的底层数组的长度。 在切片s1上应用切片表达式,得到的新的切片s2的底层数组和s1的底层数组是一样的。

回答问题:怎样估算切片容量的增长?

一旦一个切片无法容纳更多的元素,Go语言就会想办法扩容。但是它并不会改变原来的切片,而是会生成一个容量更大的切片,然后把原有的元素和新元素一并拷贝到新的切片总。在一般情况下,可以简单地认为新切片的容量(简称新容量)将会是原切片容量(简称原容量)的2倍。

但是当原切片的长度(原长度)大于或等于1024时,Go语言将会以原容量的1.25倍作为新容量的基准(新容量基准)。新容量基准将会被调整(不断地与1.25相乘),直到结果不小于原长度与要追加的元素数量之和(简称新长度)。最终,新容量往往会比新长度大一些,当然,相等也是可能的。

另外,如果我们一次追加的元素过多,以至于新长度比原容量的2倍还要大,那么新容量就会以新长度为基准。与前面的情况一样,最终的容量在很多时候都要比新容量基准更大一些。

问题2:切片的底层数组什么时候会被替换?

确切的说,一个切片的底层数组永远不会被替换。为什么?虽然在扩容的时候,Go语言一定会生成新的底层数组,但是它同时也生成了新的切片。它是把新的切片作为了新底层数组的窗口,而没有对原切片及其底层数组做任何改动。

请记住,在无需扩容的时候,append函数返回的是指向原底层数组的新切片,而在需要扩容的时候,append函数返回的是指向新底层数组的新切片。所以,严格来讲,“扩容”这个词用在这里虽然形象但是不合适。

只要新长度不会超过原切片的原容量,那么使用append函数对其追加元素的时候就不会引起扩容。这只会使紧邻切片窗口右边(底层数组中的)元素被新的元素替换掉。

问题3:如果有多个切片指向了同一个底层数组,应该注意什么?

初始时两个切片引用同一个底层数组,在后续操作中对某个切片的操作超出底层数组的容量时,这两个切片引用的就不是同一个数组了

s1 :=[]int{1,2,3,4} s2 := s1[0:4] 就像这样,这样的话改变s2会影响s1,如何消除这种影响呢

可以用copy函数,或者自己深拷贝。

问题4:怎样援用“扩容”的思想对切片进行“缩容”?

切片缩容之后还是会引用底层的原数组,这有时候会造成大量缩容之后的多余内容没有被垃圾回收。可以使用新建一个数组然后copy的方式。

猜你喜欢

转载自my.oschina.net/u/3672057/blog/2979055