Go语言-切片底层探索 —— 补充篇:切片和底层数组到底是什么关系?

之前的切片探索中,上篇通过一道算法题目,了解到切片的两大特性:一是:切片是引用类型,指向底层数组,修改其底层数组的时候,会影响切片中的值。二是:向切片中添加元素的时候,切片可能会发生扩容,改变其底层指向的数组。在下篇中,我们谈到了切片的底层实现原理以及扩容机制。在之后的学习中,切片的应用场景比较多,自己也有了一些新的发现,于是形成补充篇这个文章。接下来我会从切片的几种创建方式说明切片和底层数组之间的关系、切片作为函数的参数时,实际上传递的是什么?
上下两篇:
1.GO语言-切片底层探索(上)-CSDN博客
2.GO语言-切片底层探索(下)-CSDN博客

1. 查看切片底层依托的数组

func main() {
	//切片slice1基于array数组创建的
	array := [7]int{1, 2, 3, 4, 5, 0, 1}
	slice1 := array[3:5]
	//查看array数组的信息 [1 2 3 4 5 0 0] 7 7
	fmt.Println(array, len(array), cap(array))
	//查看slice1切片的信息 [4 5] 2 4
	fmt.Println(slice1[:], len(slice1[:]), cap(slice1[:]))
	//查看slice1切片依托底层数组的信息 [4 5 0 1] 4 4
	fmt.Println(slice1[:cap(slice1)], len(slice1[:cap(slice1)]), cap(slice1[:cap(slice1)]))
}

 在上面的代码中,我们发现一个奇怪的现象:当我们使用切片表达式slice1[:]和slice1[:cap(slice1)]所取到的值是完全不同的。

我们常用到的切片表达式是slice[low:high]这种两个参数的形式,我们称其为简单切片表达式。此外还有一种三个参数形式的切片表达式,我们称其为扩展切片表达式,但是一般不经常使用,这里权当扩展一下。在简单切片表达式中,切片的长度length = high-low,切片的容量cap = 底层数组的长度-low。这里的low和high都是可以省略的,如果省略low则默认为0,如果省略high则默认为切片的长度,而不是容量。

slice1[:]实际上取的是切片的范围,也就是从切片的下标0到切片的长度-1。而slice1[:cap(slice1)]底层数组的范围,从slice1所依托得底层数组从下标low(这里是3,因为我们的slice1= array[3:5]),到底层数组末尾。

虽然我们在项目中一般不使用slice[:cap(slice)]这种形式,但是我们知道如果通过获取切片所依托的底层数组的方法,可以帮助我们更加清晰地理解切片和底层数组的关系,以及切片中的len和cap实际代表的是什么!

2. 切片和底层数组的关系

  1. 直接赋值方式 slice:= []int{1,2,3,4,5}
  2. make字面量方式 slice:= make([]int,0,10)
  3. 使用切片表达式根据切片或数组生成 slice:= array0[1:3]

通过之前的文章,我们知道,切片是依托于数组实现的,相比于数组而言,切片在容量不足的时候,会进行自动扩容,更具有灵活性。我们一般都是通过以上三种方式创建切片的,这三种不同的创建方式,将形成三种不同的(切片和其底层数组之间的)关系。

  1. 直接赋值创建方式,这种方式创建出来的切片长度等于容量,此时如果我们向切片中添加一个新的元素,就会触发扩容机制,改变切片指向的底层数组。
  2.  make字面量方式 slice:= make([]int,0,10),通过make创建切片,可以指定切片的长度和容量(底层数组的长度),后续向切片中添加元素的个数,如果没有超过10就不会发生扩容。通过提前指定切片的容量,可以减少程序运行过程中,切片扩容带来的资源消耗。
  3. 使用切片表达式根据切片或数组生成 slice:= array0[low:high]。使用切片表达式创建的切片,其长度为high-low,容量是底层数组的长度-low。

3. 切片作为函数的参数时,传递的是对底层数组的引用

func main() {
	baseArray := [5]int{1, 2, 3, 4, 5}
	//基于baseArray创建slice
	slice := baseArray[:] 
	modifySlice(slice, 0)
	fmt.Println(slice, len(slice), cap(slice))             // 输出 [100 2 3 4 5]
	fmt.Println(baseArray, len(baseArray), cap(baseArray)) // 输出 [100 2 3 4 5]
}

func modifySlice(slice []int, index int) {
	slice[index] = 100
}

在代码中,我们基于baseArray创建slice,我们发现当传入的切片在函数中修改时,其依赖的底层数组也发生了修改。这说明,切片作为函数的参数时,实际上传递是对底层数组的引用。如果我们在函数的操作导致切片进行了扩容,那么我们的底层数组中的值将不会再发生变化了。

测试如下:

func main() {
	baseArray := [5]int{1, 2, 3, 4, 5}
	//基于baseArray创建slice
	slice := baseArray[:]
	modifySlice(slice, 0)
	fmt.Println(slice, len(slice), cap(slice))             // 输出 [100 2 3 4 5]
	fmt.Println(baseArray, len(baseArray), cap(baseArray)) // 输出 [100 2 3 4 5]
}

func modifySlice(slice []int, index int) {
	slice[index] = 100       //baseArray[index]被修改
	slice = append(slice, 1) //扩容,底层数组改变
	slice[index] = 1000      //baseArray[index]值不变
	fmt.Println(slice, len(slice), cap(slice)) //[1000 2 3 4 5 1] 6 10
}

4. 总结

在这篇博客中,我们主要讲解切片和底层数组之间的关系,并且通过切片的三种创建方式来进行详细的说明。我们在使用切片的时候,一定要注意切片扩容后,其底层指向的数组会发生变化,对切片的修改将不再作用与原来的底层数组。

相关推荐

  1. GO语言-切片底层探索(下)

    2024-06-08 19:28:02       23 阅读
  2. go slice切片的详细知识(包含底层扩容)——2

    2024-06-08 19:28:02       8 阅读
  3. 关于Go语言底层,Slice,map

    2024-06-08 19:28:02       34 阅读
  4. Go语言切片

    2024-06-08 19:28:02       15 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-06-08 19:28:02       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-08 19:28:02       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-08 19:28:02       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-08 19:28:02       20 阅读

热门阅读

  1. Linux 字体管理

    2024-06-08 19:28:02       7 阅读
  2. nginx

    nginx

    2024-06-08 19:28:02      9 阅读
  3. UG12编程怎么没有:深度解析与困惑探寻

    2024-06-08 19:28:02       11 阅读
  4. 《青少年编程与数学》课程方案:3、课程形式

    2024-06-08 19:28:02       6 阅读
  5. EXCEL上传得时候特殊情况

    2024-06-08 19:28:02       11 阅读
  6. 使用Redis缓存需要注意的地方

    2024-06-08 19:28:02       9 阅读
  7. git 下载openNeuro大文件

    2024-06-08 19:28:02       11 阅读
  8. 哈希表(Hash table)

    2024-06-08 19:28:02       8 阅读
  9. C++协程

    2024-06-08 19:28:02       9 阅读
  10. 【vuejs】vm.$set() 的原理解析和方法以及应用场景

    2024-06-08 19:28:02       8 阅读