首页 > 其他分享 >golang中的切片

golang中的切片

时间:2022-10-20 23:27:09浏览次数:87  
标签:切片 元素 s2 cap len golang go append

索引:https://waterflow.link/articles/1666277946416

在go中切片的底层是数组,所以切片的数据连续存储在数组的数据结构中。如果底层的数组满了,切片还需要添加元素的话,底层数组就需要扩容。如果底层数组几乎为空时,就会缩容。

在切片内部其包含一个指向底部数组的指针、切片的长度、切片的容量。长度是指切片包含的元素树,容量底层数组中的元素数。

我们先看个例子:

s := make([]int, 2, 4)

我们初始化一个长度为2,容量为4的切片。长度是必选的,容量可选。当不指定容量时,容量和长度相同,也就是底层数组的长度。
http://image-1313007945.cos.ap-nanjing.myqcloud.com/image/1666278007.png

因为长度为2,所以go只初始化了2个元素。我们可以打印下看下结果:

package main

import "fmt"

func main() {
	s := make([]int, 2, 4)
	fmt.Printf("s:%v,len(s):%d,cap(s):%d\n",
		s, len(s), cap(s))
}
go run 4.go
s:[0 0],len(s):2,cap(s):4

下面我们把s[1]设置为1,所以s的第二个元素会被更新为1,s的长度和容量不会变化
http://image-1313007945.cos.ap-nanjing.myqcloud.com/image/1666278035.png

我们看下执行结果:

package main

import "fmt"

func main() {
	s := make([]int, 2, 4)
	s[1] = 1
	fmt.Printf("s:%v,len(s):%d,cap(s):%d\n",
		s, len(s), cap(s))
}
go run 4.go
s:[0 1],len(s):2,cap(s):4

但是当我们设置s[2] = 1时会报错,像下面这样:

go run 4.go
panic: runtime error: index out of range [2] with length 2

但是我们的容量是4,也就是底层数组还有2个空闲元素。那我们该如何给剩下的元素赋值呢?

我们可以使用go的内置函数append,他可以将元素追加到切片的末尾。它可以追加一个元素到切片末尾:

s = append(s, 1)

也可以追加多个元素到切片末尾:

s = append(s, 1, 2)
s = append(s, []int{1, 2}...)

还有一种比较特殊的情况,将字符串追加到字节切片末尾:

s = append([]byte("hello"), "world"...)

现在我们执行append(s, 2),将元素2追加到s[1]后面,我们会得到下面的结果
http://image-1313007945.cos.ap-nanjing.myqcloud.com/image/1666278054.png

切片的长度变成了3,容量还是4:

package main

import "fmt"

func main() {
	s := make([]int, 2, 4)
	s[1] = 1
	s = append(s, 2)
	fmt.Printf("s:%v,len(s):%d,cap(s):%d\n",
		s, len(s), cap(s))
}
go run 4.go
s:[0 1 2],len(s):3,cap(s):4

现在我们如果直接添加3个元素进去,像下面这样:

s = append(s, 1, 2, 3)

我们会得到下面的结果:

package main

import "fmt"

func main() {
	s := make([]int, 2, 4)
	s[1] = 1
	s = append(s, 2)
	s = append(s, 1, 2, 3)
	fmt.Printf("s:%v,len(s):%d,cap(s):%d\n",
		s, len(s), cap(s))
}
go run 4.go
s:[0 1 2 1 2 3],len(s):6,cap(s):8

我们发现现在元素的长度变成了6个,这个很好理解,因为我们总共添加了6个元素。但是切片的容量为什么会是8呢?

这是因为当我们添加第5个元素时,发现已经超过切片的容量了。这个时候会触发扩容,会将容量加倍,然后复制所有元素创建另一个数组。然后会把剩下的2和3插进去。
http://image-1313007945.cos.ap-nanjing.myqcloud.com/image/1666278070.png

扩容机制:如果不到1024个元素,会成倍扩容;超过1024个元素,按25%扩容

切片中还提供了一种半开区间的赋值方式,保留第一个索引,排除第二个索引,像下面这样:

package main

import "fmt"

func main() {
	s := make([]int, 2, 4)
	s[1] = 1
	s = append(s, 2)
	s = append(s, 1, 2, 3)
	fmt.Printf("s:%v,len(s):%d,cap(s):%d\n",
		s, len(s), cap(s))
	s2 := s[1:2] // 这里重新赋值给s2
	fmt.Printf("s2:%v,len(s2):%d,cap(s2):%d\n",
		s2, len(s2), cap(s2))
}
go run 4.go
s:[0 1 2 1 2 3],len(s):6,cap(s):8
s2:[1],len(s2):1,cap(s2):7

切片s和s2此时是引用同一个底层数组的,但是由于s2是从1开始,所以容量变成了7
http://image-1313007945.cos.ap-nanjing.myqcloud.com/image/1666278089.png

这时如果我们修改了s2[0]或者s[1],实际上他们指向的是底层数组的同一个元素。所以s2[0]或者s[1]都会被修改掉。

package main

import "fmt"

func main() {
	s := make([]int, 2, 4)
	s[1] = 1
	s = append(s, 2)
	s = append(s, 1, 2, 3)
	s2 := s[1:2]
	s2[0] = 8 // 这里
	fmt.Printf("修改俩切片指向的同一个元素:s:%v,len(s):%d,cap(s):%d)----s2:%v,len(s2):%d,cap(s2):%d\n",
		s, len(s), cap(s), s2, len(s2), cap(s2))
}
go run 4.go
修改俩切片指向的同一个元素:s:[0 8 2 1 2 3],len(s):6,cap(s):8)----s2:[8],len(s2):1,cap(s2):7

接着我们继续往s2中插入一个元素,看看会发生什么:

package main

import "fmt"

func main() {
	s := make([]int, 2, 4)
	s[1] = 1
	s = append(s, 2)
	s = append(s, 1, 2, 3)
	s2 := s[1:2]
	s2[0] = 8
	// 插入元素6
	s2 = append(s2, 6)
	fmt.Printf("往s2插入一个元素:s:%v,len(s):%d,cap(s):%d----s2:%v,len(s2):%d,cap(s2):%d\n",
		s, len(s), cap(s), s2, len(s2), cap(s2))
}
go run 4.go
往s2插入一个元素:s:[0 8 6 1 2 3],len(s):6,cap(s):8----s2:[8 6],len(s2):2,cap(s2):7

我们可以看到s2[1]的元素写进去了,长度变为2。但是s2的长度并没有变化,s2[2]的元素却被修改为了6。这是因为往s2插入元素时并没有超过s2的容量,所以还是共用同一个底层数组。

那现在我们继续往s2中添加6个元素,看看超出容量后的底层数组会是什么样的:

package main

import "fmt"

func main() {
	s := make([]int, 2, 4)
	s[1] = 1
	s = append(s, 2)
	s = append(s, 1, 2, 3)
	s2 := s[1:2]
	s2[0] = 8
	s2 = append(s2, 6)
	// 继续插入6个元素
	s2 = append(s2, 7)
	s2 = append(s2, 8)
	s2 = append(s2, 9)
	s2 = append(s2, 10)
	s2 = append(s2, 11)
	s2 = append(s2, 12)
	fmt.Printf("继续往s2插入6个元素:s:%v,len(s):%d,cap(s):%d----s2:%v,len(s2):%d,cap(s2):%d\n",
		s, len(s), cap(s), s2, len(s2), cap(s2))
}
go run 4.go
继续往s2插入6个元素:s:[0 8 6 7 8 9],len(s):6,cap(s):8----s2:[8 6 7 8 9 10 11 12],len(s2):8,cap(s2):14

我们来分析下上面的例子:

  1. 当我们往s2插入7的时候,此时s2的长度变为3,容量还是7。s[3]对应也被修改。
  2. 当我们往s2插入8的时候,此时s2的长度变为4,容量还是7。s[4]对应也被修改。
  3. 当我们往s2插入9的时候,此时s2的长度变为5,容量还是7。s[5]对应也被修改。
  4. 当我们往s2插入10的时候,此时s2的长度变为6,容量还是7。s[6]对应也被修改。
  5. 当我们往s2插入11的时候,此时s2的长度变为7,容量还是7。因为s切片长度为6,所以没有变化。
  6. 当我们往s2插入12的时候,此时s2超过s2的容量引发扩容,底层数组被复制,s2指向一个新的容量为14的数组。因为s长度小于容量,所以还是指向原来的数组。

我们可以通过下面的图片,增加更清晰的认识。
http://image-1313007945.cos.ap-nanjing.myqcloud.com/image/1666278106.png

标签:切片,元素,s2,cap,len,golang,go,append
From: https://www.cnblogs.com/liuyuede123/p/16811733.html

相关文章

  • Go 语言入门很简单:什么是 Golang
    Golang是一种相对较新的编程语言,很快就流行起来。StackOverflow对开发人员进行了民意调查,发现Golang是学习Go编程语言的第三大热门。为了更好地理解为什么Go如此......
  • 华科golang详细示例代码
    packagemainimport("net/url""net/http""bytes""fmt""io/ioutil")constProxyServer="ip.hahado.cn:39010"typeProxyAut......
  • golang os.Args (转)
    转自:301-golang之命令行参数os.Argsos.Args获取运行时的参数,是一个切片Slice,第一个值是当前可运行文件的绝对路径import("os""fmt")funcmain(){......
  • 爱上开源之golang入门至实战第四章函数(Func)(八)
    爱上开源之golang入门至实战第四章函数(Func)(八)4.4.8函数作为返回值在go语言里,func本质上是一个类型关键字。使用func来进行声明,本质上就是什么了一个func的对象。对象......
  • 06-Go语言数组和切片
    数组数组初始化 vararr[4]int arr[0]=1 arr[1]=2 fmt.Println(arr) vara=[4]int{1,2,3,4} fmt.Println(a) varb=[4]int{1,2,3} fmt.Println(b......
  • GeoServer加载Arcgis切片服务
    使用GeoServer中的GeoWebCache加载Arcgis切片服务下载安装GeoServer和GeoWebCache的下载安装都非常简单,这里选择“独立于平台的二进制版本”,即通常讲的免安装版。选择Geo......
  • 我的Vue之旅 07 Axios + Golang + Sqlite3 实现简单评论机制
    第三期·使用Vue3.1+TailWind.CSS+Axios+Golang+Sqlite3实现简单评论机制效果图CommentArea.vue我们需要借助js的Data对象把毫秒时间戳转化成UTCStrin......
  • 我的Vue之旅 07 Axios + Golang + Sqlite3 实现简单评论机制
    第三期·使用Vue3.1+Axios+Golang+Sqlite3实现简单评论机制效果图CommentArea.vue我们需要借助js的Data对象把毫秒时间戳转化成UTCString()。并在模板......
  • Golang编辑器 - GoLand安装
    GoLand官网https://www.jetbrains.com.cn/go/下载GoLand通过访问其官网进行安装包下载下载完成之后双击运行安装程序点击Next选择安装的路径下一步选择创建......
  • golang中的init初始化函数
    0.1、索引https://waterflow.link/articles/16660905308801、概念1.1、源文件里的代码执行顺序init函数是用于初始化应用程序状态的函数。它不接受任何参数并且不返回......