当前位置:   article > 正文

Go语言基础 | Slice的特性及使用_go声明slice

go声明slice

一、Slice介绍

Slice称为动态数组或切片,底层是数组实现,实际使用比数组更加灵活,可以方便地进行扩容和传递。

二、初始化

字面量声明

	var s []int	// 变量声明
	s1 := []int{}	// 空切片
	s2 := []int{1,2,3}
  • 1
  • 2
  • 3

函数声明

	s1 := make([]int,12)	// 指定长度
	s2 := make([]int,10,20)	// 指定长度和容量
	s3 := *new([]int)	// 空切片
  • 1
  • 2
  • 3

三、特性

1. 切取

切片可以基于其他数组或切片创建,且与原数组和切片共享底层空间,修改切片会影响原数组或切片。

2. 切片表达式

简单表达式
	slice := a[low:high]
  • 1

其中切片长度等于 high - low,容量长度等于底层数组的长度,其中 low 和 high 可以省略。

这种表达式存在两个问题

  • 一是在切片使用 appen() 方法追加元素时,大概率会篡改后续元素;
  • 二是扩容后的切片底层数组可能不是原数组了。
// 情况一
/* 代码 */
	a := [5]int{1, 2, 3, 4, 5}
	aSlice := a[1:2]	// 前闭后开,取下标为1的位置,即 2 = a[1]
	fmt.Println("aSlice:", aSlice, " aSliceLen:", len(aSlice), " aSliceCap:", cap(aSlice)) // aSlice: [2]  aSliceLen: 1  aSliceCap: 4
	fmt.Println("a:", a)	// a: [1 2 3 4 5] 
	aSlice = append(aSlice, 8)	// 追加一个新元素
	fmt.Println("aSlice:", aSlice, " aSliceLen:", len(aSlice), " aSliceCap:", cap(aSlice))	// aSlice: [2 8]  aSliceLen: 2  aSliceCap: 4
	fmt.Println("a:", a)	// a: [1 2 8 4 5]

/* 控制台 */
aSlice: [2]  aSliceLen: 1  aSliceCap: 4
a: [1 2 3 4 5]                                    
aSlice: [2 8]  aSliceLen: 2  aSliceCap: 4         
a: [1 2 8 4 5]  // a[2]位置已经从 3 -> 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
// 情况二
/* 代码 */
	a := [5]int{1, 2, 3, 4, 5}
	fmt.Println("a:", a)
	aSlice := a[0:]
	aSlice[0] = 2	// 修改下标0位置元素
	fmt.Println("aSlice:", aSlice, "  a:", a)
	aSlice = append(aSlice, 9) // 简单表达式下 cap 到达数组极限,扩容后的切片将不再影响原数组
	aSlice[0] = 5	// 修改下标0位置元素
	fmt.Println("aSlice:", aSlice, "  a:", a)
/* 控制台 */
原数组 a: [1 2 3 4 5]
aSlice: [2 2 3 4 5]   a: [2 2 3 4 5] 	 // 修改下标0位置后的 aSlice 和 a,此时切片影响了原数组
aSlice: [5 2 3 4 5 9]   a: [2 2 3 4 5]	// 触发扩容后的 aSlice 和数组,此时的修改操作对原数组没有影响,说明扩容产生的新切片底层数组已经不是原数组。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

*边界问题:0 ≤ low ≤ high ≤ cap(a),否则触发 panic | 意味着 high 的取值可以大于 len(a) *

扩展表达式
	slice := a[low:high:max]
  • 1

限制新切片容量的表达式,因为简单表达式可能会覆盖 a[high] 后的元素,产生意料之外的影响。

边界问题:0 ≤ low ≤ high ≤ max ≤ cap(a)

3. 其他方法

appen()

向切片追加元素。
当切片 len=cap 时,向切片追加元素会产生扩容,扩容后生成的新切片将不会影响原数组或切片。
扩容: 当切片容量不足时会重新申请内存空间更大的新切片,将原切片内容复制到新切片,最后追加元素。
扩容规则: ① Slice容量<1024时,申请两倍内存空间;② Slice容量≥1024时,申请1.25倍内存空间。

len()

查询切片长度

cap()

查询切片容量

二、原理

数据结构

// src/runtime/slice.go:slice
type slice struct {
	array unsafe.Pointer	// 底层数组指针
	len   int	// 切片长度
	cap   int	// 切片容量
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 底层数组指针指向数组中某个节点,例如[1,2,3],它可以指向 2
  • 切片容量 cap 映射底层数组的大小
  • 切片长度 len 反映切片访问的范围,切片访问下标大于 len 时会触发 panic

小结

  • 每个切片都只想一个底层数组,每个切片都保存了当前切片的长度和容量
  • 使用 len()、cap() 方法计算切片长度和容量的时间复杂度为O(1)
  • 通过函数传递切片不会拷贝整个切片,切片本身只是一个数据结构
  • 使用 appen() 方法向切片追加元素可能触发扩容,扩容会生成新切片

Tip

  • 根据实际需要分配容量,避免在追加过程中扩容影响性能
  • 谨慎使用多个切片操作同一数组/切片,防止冲突
  • 使用扩展表达式代替简单表达式

Question

当切片很小,但底层数组占据了大量空余内存,该如何优化?
在这里插入图片描述

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号