Go slice 用法及数据结构详解

本文详细介绍了Go语言中切片(slice)的数据结构、初始化方法、切片操作、append函数的工作原理、copy函数的使用以及切片表达式。通过实例展示了切片在内存管理、数组和字符串切片、扩容策略等方面的特点,帮助读者深入理解Go语言切片的使用和内部机制。
摘要由CSDN通过智能技术生成

slice数据结构

array指针指向底层数组,len表示切片长度,cap表示底层数组容量

type slice struct {
	array unsafe.Pointer	//切片底层数组的起始位置
	len int             	//切片长度
	cap int					//切片容量
}

slice初始化的方法

  1. 变量声明(与所有类型变量一样,变量声明后变量值为0,对于切片来讲,0值为nil)
  2. 字面量(声明长度为0的切片时推荐使用变量声明的方式获得一个nil切片,因为nil切片不需要分配内存)
  3. 内置函数make() (切片元素均初始化为相应类型的零值)
  4. 适用于任意类型的内置函数new()(此时创建的切片值为nil)
var s []int//变量声明 nil切片,不需要分配内存
s1 := []int{}//长度为0的切片,空切片,指长度为空,其值并不是nil
s2 := []int{1,2,3}//长度为3的切片
fmt.Println(s)   //[]
fmt.Println(s1)  //[]
fmt.Println(s2)  //[1 2 3]
fmt.Println(s==nil) //true
fmt.Println(s1==nil) //false

s3 := make([]int, 12) //指定长度
s4 := make([]int, 10, 100) //推荐指定长度和预估空间,可以有效减小切片扩容时内存分配及拷贝次数
fmt.Println(s3)  //[0 0 0 0 0 0 0 0 0 0 0 0]
fmt.Println(s4)  //[0 0 0 0 0 0 0 0 0 0]

slice := *new([]int)
fmt.Println(slice)  //[]
fmt.Println(slice==nil) //true

slice切取

  1. 切片可以基于数组和切片创建,切片与原数组或切片共享底层空间,修改切片会影响原数组或切片
  2. 切片表达式[low : high]表示的是左闭右开的区间[low, high),切取的长度为high-low
array := [5]int{1,2,3,4,5}
s5 := array[0:2] //从数组中切取
s6 := s5[0:1]    //从切片中切取
fmt.Println(s5)  //[1 2]
fmt.Println(s6)  //[1]

append()

当切片空间不足时,append()会创建新的大容量切片,添加元素后再返回新切片。

扩容容量原则:

  • 如果原slice的容量小于1024,则新的slice的容量扩大为原来的2倍
  • 如果原slice的容量大于或等于1024,则新的slice的容量扩大为原来的1.25倍
s7 := make([]int, 0)
fmt.Printf("slice:%v len:%d cap:%d\\n",s7,len(s7),cap(s7))//slice:[] len:0 cap:0
	
s7 = append(s7,1)
fmt.Printf("slice:%v len:%d cap:%d\\n",s7,len(s7),cap(s7))//slice:[1] len:1 cap:1
	
s7 = append(s7,2,3,4)
fmt.Printf("slice:%v len:%d cap:%d\\n",s7,len(s7),cap(s7))//slice:[1 2 3 4] len:4 cap:4

//当append前,切片空间不够时,会先创建2倍的cap大小,再拷贝返回新的切片,再将数据追加进去
s7 = append(s7, []int{5,6}...)
fmt.Printf("slice:%v len:%d cap:%d\\n",s7,len(s7),cap(s7))//slice:[1 2 3 4 5 6] len:6 cap:8

copy()

使用copy()内置函数拷贝两个切片时,会将源切片的数据逐个拷贝到目的切片指向的数组中,拷贝数量取两个切片长度的最小值,拷贝过程不会发生扩容。

s8 := []int{1,2,3,4,5,6,7,8,9}
s9 := make([]int,5)
copy(s9, s8)//把s8拷贝到s9
fmt.Printf("slice:%v len:%d cap:%d\\n",s8,len(s8),cap(s8))//slice:[1 2 3 4 5 6 7 8 9] len:9 cap:9
fmt.Printf("slice:%v len:%d cap:%d\\n",s9,len(s9),cap(s9))//slice:[1 2 3 4 5] len:5 cap:5

切片表达式

简单表达式(a[low : high])

  • 如果a为数组或切片,切片时产生新的切片,如果a为字符串(string),则产生新的字符串
  • low的默认值为0,high的默认值为表达式作用对象的长度
a[:high] 等同于 a[0:high]
a[low:] 等同于 a[low:len(a)]
a[:] 等同于 a[0:len(a)]
  • 新的切片与源切片共享底层数组
//使用简答表达式 b := a[low : high] ,b的生成逻辑可以用以下伪代码表示
b.array = &a[low]
b.len = high - low
b.cap = cap(a)-low

  • 简答表达式切取的对象为字符串或数组,那么在表达式a[low : high]中满足:
0 <= low <= high <= len(a) //如果不满足将会越界panic
  • 简答表达式切取的对象为切片,那么low和high可以超越len(a),满足:
0 <= low <= high <= cap(a)

扩展表达式(a[low : high : max])

  • 简单表达式生成的新切片与原数组或切片共享底层数组避免了拷贝元素
  • 新切片b(b := a[low : high])不仅可以读写 a[low ] 至 a[high - 1]之间的元素,而且在使用 append(b, x)函数增加新的元素x时,可能会覆盖a[high]及后面的元素。
a := [3]int{1,2,3}
b := a[1:2]
b = append(b,4) //此时元素a[2]将由3变为4
fmt.Printf("a:%v b:%v\\n",a,b) //a:[1 2 4] b:[2 4]
  • 扩展表达式max用于限制新生成切片的容量为max-low,满足:
0 <= low <= high <= max <= cap(a)
  • 当使用append()函数向扩展表达式或简单表达式生成的切片追加新元素时,如果存储容量不足则会产生一个全新的切片,而不会覆盖原始的数组或切片
a1 := [3]int{1,2,3}
b1 := a1[1:2:2]//扩展表达式
b1 = append(b1,4) //此时存储容量不足,元素b1变为一个全新的切片,不再和a1共享底层数组
fmt.Printf("a1:%v b1:%v\\n",a1,b1) //a1:[1 2 3] b1:[2 4]
  • 扩展表达式a[low : high : max]中,只有low是可以省略的,其默认值为0

面试题

1.下面函数输出什么?

func SliceCap() {
   var array [10]int
   var slice = array[5:6]
   fmt.Printf("len(slice)=%d\\n",len(slice))
   fmt.Printf("cap(slice)=%d\\n",cap(slice))
}
答案:
len(slice)=1
cap(slice)=5

2.下面函数输出什么?

func SliceRise(s []int) {
   s = append(s,0)
   for i := range s {
      s[i]++
   }
}
func SlicePrint() {
   s1 := []int{1,2}
   s2 := s1
   s2 = append(s2, 3)
   SliceRise(s1)
   SliceRise(s2)
   fmt.Println(s1,s2)
}
答案:
[1 2] [2 3 4]
解释:
s1 := []int{1,2} //创建切片s1,len(s1)=2,cap(s1)=2
s2 := s1 // 创建切片s2,其与s1共享底层数组

s2 = append(s2, 3) 
//追加新元素3时,cap为2,存储容量不足,产生一个全新的切片,不再与s1共享底层数组
//追加后,s1=[1 2],len(s1)=2,cap(s1)=2,而s2=[1 2 3],len(s2)=3,cap(s2)=4

SliceRise(s1)//这个函数中s为s1的引用,s与s1共享底层数组,
//但是执行s = append(s,0)时与上述s2 = append(s2, 3) 情况类似,
//存储容量不足,产生一个全新的切片,不再与s1共享底层数组,
//此时s1=[1 2],len(s1)=2,cap(s1)=2,而s=[1 2 3],len(s)=3,cap(s)=4,
//注意到s1始终没有改变

SliceRise(s2)//这个函数中s为s2的引用,s与s2共享底层数组,
//执行s = append(s,0)时,由于s2存储容量为4,可以追加,
//此时s2=[1 2 3 0],len(s2)=4,cap(s2)=4,而s=[1 2 3 0],len(s)=4,cap(s)=4
//执行 s[i]++后,s1 = s = [2 3 4 1]

最终s1=[1 2],s2 = s = [2 3 4 1],s与s2共享底层数组

3.下面函数输出什么?

func SliceExtend()  {
	s := make([]int, 0, 10)
	s1 := append(s, 1,2,3)
	s2 := append(s1,4)
  s2[0] = 5
	fmt.Println(&s1[0] == &s2[0] )
	fmt.Printf("s:%v len:%d cap:%d\\n",s,len(s),cap(s))
	fmt.Printf("s1:%v len:%d cap:%d\\n",s1,len(s1),cap(s1))
	fmt.Printf("s2:%v len:%d cap:%d\\n",s2,len(s2),cap(s2))
}
答案:
true
s:[] len:0 cap:10
s1:[5 2 3] len:3 cap:10
s2:[5 2 3 4] len:4 cap:10
解释:
s,s1,s2共享底层数组,但是它们的值以及len和cap不同

4.下面函数输出什么?

func SliceExpress() {
   orderLen := 5
   order := make([]uint16, 2*orderLen)

   pollorder := order[:orderLen:orderLen]
   lockorder := order[orderLen:][:orderLen:orderLen]

   fmt.Printf("len(pollorder)=%d\\n",len(pollorder))
   fmt.Printf("cap(pollorder)=%d\\n",cap(pollorder))
   fmt.Printf("len(lockorder)=%d\\n",len(lockorder))
   fmt.Printf("cap(lockorder)=%d\\n",cap(lockorder))
}
答案:
len(pollorder)=5
cap(pollorder)=5
len(lockorder)=5
cap(lockorder)=5
解释:
pollorder与lockorder分别将原切片order一分为二,二者共享order的底层数组却又不会越界
其中lockorder := order[orderLen:][:orderLen:orderLen] 为连续切片,即可以分为如下两步:
lockorder := order[5:]
lockorder = lockorder[:5:5]

又比如:
s1 := []int{1,2,3,4,5}
s2 := s1[0:4] [:3:3] //此时s2=[1 2 3]
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值