ugfet92vl4va2v 发表于 2022-5-15 17:38:03

Go中的数组与切片

容器类型

java

java中的容器类型常用的是List,Set,HashMap等。
在java中谈容器,一般指的是Collection和Map。数组不属于容器的范围。

https://p3.toutiaoimg.com/large/tos-cn-i-qvj2lq49k0/9c6cdecbb45a40e0a13cbd063a2b161e

但是go中我们说到容器类型,一般是说数组、切片和map
go的数组

go数组的两个特性:长度固定,元素类型相同。
var arrName T    //长度为n,类型是TT 是arrName的数组类型。也就是说如果两个数组类型的元素类型 T 与数组长度 N 都是一样 的,那么这两个数组类型是等价的,如果有一个属性不同,它们就是两个不同的数组类 型。
因为数组在定义时其类型和长度都是明确的,所以实际内存分配上,也是一块连续的,可容纳所有数据的内存。
   // 数组的声明   var a int // 默认初始化为int的零值   a = 1      b := int{1, 3, 5}         // 声明同时初始化   c := int{{1, 1}, {2, 2}} //多维数组      d := [...]int{1, 2, 3, 4, 5} // 不用写数组的长度          var e = [...]int{ // 稀疏数组         99: 39, // 将第100个元素(下标值为99)的值赋值为39,其余元素值均为0         }Go 提供了预定义函数 len 可以用于获取一 个数组类型变量的长度,通过 unsafe 包提供的 Sizeof 函数,我们可以获得一个数组变量 的总大小
   t.Log(len(d))// 5   t.Log(unsafe.Sizeof(d)) // 40切片slice

切片的定义和数组很像,仅仅是少了一个“长度”属性。切片的存在是为了解决数组的问题,数组长度固定,很不灵活。
var myslice = []int{1, 2, 3, 4, 5, 6}使用内置的append函数添加元素
myslice = append(myslice, 100)slice的实现

slice的底层结构是
type slice struct {   array unsafe.Pointer // 指向底层数组的指针   len int// 切片的长度,即切片中当前元素的个数;   cap int// 底层数组的长度,也是切片的最大容量,cap 值永远大于等于 len 值 }每个新建的slice都会新建一个底层数组。数组的长度和切点初始元素的个数相同。
我们还有其他方法创建切片。

[*]通过make 函数来创建切片,并指定底层数组的长度 c := make([]int, 3, 5) // 切点的len是3,cap是5,即底层数组的长度是5.如果不指定。默认cap = len
t.Log(len(c), cap(c))

[*]在已有数组的基础上创建切片
[*]采用 array语法基于一个已存在的数组创建切片。这种方式被 称为数组的切片化 func TestArr2Slice(t *testing.T) {
month := string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
slc := month
t.Log(slc) //
t.Log(len(slc), cap(slc)) // 3 6
}len = high - lowcap = max - low。通常省略max,max默认是数组的长度注意1,现在这个切片slc是直接指向数组month的。也就是说slc的改变会直接改变数组month slc = "4月"
t.Log(month) // 注意2,对一个数组可以创建多个切片。因为这些切片底层都是指向数组的。所以任意一个切片的改变都会影响其他切片。 slc2 := month
t.Log(slc2) //
t.Log(len(slc2), cap(slc2)) // 3 9
slc2 = "5月"
t.Log(month) //
t.Log(slc) //
[*]基于切片创建切片用法和基于数组创建切片一样,底层指向同一个数组,所以互相影响。
slice的动态扩容

slice相比array的特点就是不定长。当len == cap时,再对slice进行append,就会发生切片的动态扩容。
// 切片的容量是翻倍增加 func TestSliceGrowing(t *testing.T) {   s := []int{}   for i := 0; i < 20; i++ {         s = append(s, i)         t.Log(len(s), cap(s))   } } 结果打印:   slice_test.go:49: 1 1   slice_test.go:49: 2 2   slice_test.go:49: 3 4   slice_test.go:49: 4 4   slice_test.go:49: 5 8   slice_test.go:49: 6 8   slice_test.go:49: 7 8   slice_test.go:49: 8 8   slice_test.go:49: 9 16   slice_test.go:49: 10 16   slice_test.go:49: 11 16   slice_test.go:49: 12 16   slice_test.go:49: 13 16   slice_test.go:49: 14 16   slice_test.go:49: 15 16   slice_test.go:49: 16 16   slice_test.go:49: 17 32   slice_test.go:49: 18 32   slice_test.go:49: 19 32   slice_test.go:49: 20 32可以看到slice的容量cap时翻倍增加的。
动态扩容导致的与原数组的分割

前面说了,切片可以从数组创建。切片的修改会直接修改原数组。但是,切片是可以继续追加元素的,那么切片追加元素超出了原数组的最大边界会怎么样呢?
func TestArr2SliceOut(t *testing.T) {   month := string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}   slc := month   t.Log(slc)//    t.Log(len(slc), cap(slc))// 3 6   slc = append(slc, "7月")   t.Log(slc)    //    t.Log(month)//    slc = append(slc, "8月")   slc = append(slc, "9月")   slc = append(slc, "10月")   t.Log(slc)//    t.Log(month)// }还是用月份举例。slc切片出了4,5,6三个月。len是3,cap是6。
追加一个7月。此时还在slc的容量范围内,所以直接影响了原数组。
但是当追加到10月时,已经超出了slc的容量范围。此时会进行扩容。slc的扩容会创建一个新的数组,与原数组不在有关系。所以10月不会影响原数组。之后slc做的任何操作都与原数组无关。
同样的道理推广到多个切片指向同一个数组。当某一个切片发生扩容后,他便于其他切片不在指向同一数组,也就不会再相互影响了。
切片的扩容这里是经常埋坑的地方。
一定要清楚的认识到slice与底层数组的关系
页: [1]
查看完整版本: Go中的数组与切片