当前位置:   article > 正文

slice在Golang中的实现和应用_golang创建一个无界slice

golang创建一个无界slice

底层实现

struct Slice
{ 
  byte* array; // actual data
  uintgo len;  // number of elements
  uintgo cap;  // allocated number of elements
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

我们知道slice是用来管理一个内存地址连续的内存空间,类似C++中的vector。由上面的定义可以看到,Slice在底层实现是一个结构体。

  • array 是内存空间的首地址
  • len 是内存中元素的个数,这个标记很重要,由它可以知道下一次插入数据时该插入到哪个位置
  • cap 内存空间的长度,这个标记同样很重要,由它可以知道什么时候空间被使用完,然后申请更大的空间。

基本过程:

不断地向slice插入新的数据,len会不断增大,当len 大于 cap时,会申请更大的空间(据我观察,申请的空间是原来的2倍),然后把原来的数据复制到新的空间中。

创建slice的三种方法

1、使用make创建slice

由上面定义可知,slice底层有三个参数,所以使用make创建slice也需要指定3个参数。

s1 := make([]int, 3)    // 有3个元素的切片, len为3, cap缺省此时等于len
s2 := make([]int, 3, 3) // 有3个元素的切片, len为3, cap为3
s3 := make([]int, 0, 3) // 有0个元素的切片, len为0, cap为3
  • 1
  • 2
  • 3

其中s1和s2的定义方式,本质上是一致的。但是他们和s3不同,因为s1和s2已经有了3个元素,再插入一个新元素就会触发内存扩容,新的数据被插入第四个位置上。而s3内没有元素,下一个新元素被插入到第一个位置上。

2、使用数组创建slice

语法格式:

s := data[low : high : max]

同样是三个参数,但是这三个参数都是data数组上的索引位置,言外之意就是:假设data数组容量为10,那么这三个参数最大为10,超过10就是不合法,就会编译报错(out of bounds for 10-element array)。

所以:

  • low 确定了slice中内存的起始位置,即确定了array的值
  • high 确定了slice中len的值,len等于 high - low
  • max 确定了slice中cap的值,cap等于 max - low

另外有一点需要说明,这样产生的切片底层指向data数组,修改s中数据,就会修改到data中的数据。

举例:

data := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} // 创建数组,容量为10,同时包含10个元素 
s1 := data[1:3:8] // 包含2个元素为1,2, cap为7 
s2 := data[0:10:10] // 包含10个元素,cap为10
s3 := data[10:10:10] // 不包含任何元素,cap为0
s4 := data[0:] // 包含10个元素,cap为10
s5 := data[:10]  // 包含10个元素,cap为10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

刚才说了,指定的三个参数,都是原数组的索引位置,但是原数组10个元素,其索引范围为0-9,怎么能出现10呢?这个需要从指针的角度去理解,索引为10,其实就是第11个元素内存的起始指针,同时也是第10个元素内存的终止位置,这个位置是确实存在,没有越界。比如s3的定义方式,就很特殊,它的容量为0。

3、直接定义

var s []int  // 表示一个容量为0的切片
  • 1

向slice插入数据

直接使用在索引位置插入

data := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} // 创建数组,容量为10,同时包含10个元素 
s1 := data[1:3:8] // 包含2个元素为1,2, cap为7 

// 此时 s1包含2个元素,即s1[0]为1,s1[1]为2
// 往s1[2]写数据,注意s1的cap为7,即索引位置最大为6 
s1[2] = 100 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

使用append插入数据

// 等价于s1[2]=100
// append表示在切片尾部插入新的数据,相当于C++中的push_back操作
s1 = append(s1, 100)  

s2 := data[5:7]
s1 = append(s1, s2...) // 表示插入多个元素,因为s2有2个元素,后面要加三个点,注意语法格式
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

append的语法格式:

s1 = append(s1, 100) 
s2 := append(s1, 100) 
  • 1
  • 2

必须要接收append的返回值,否则编译报错。

为甚么一定需要一个值接收呢?

因为slice本质是一个结构体,不是指针,是值传递。当append对一个slice操作后,增加新的元素,它的len值就变化了,需要返回新的slice结构体,必须要接收。

总结

  • slice是一个结构体,不是指针,map和channel是指针
  • slice使用append插入数据,就是一个动态数组,会自动扩容
  • 利用数组创建切片时,修改切片会修改原数组的数组(在扩容之前)
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/206453
推荐阅读
相关标签
  

闽ICP备14008679号