Go语言切片

在Go语言中,切片(Slice)是一种非常灵活且常用的数据结构,它是对底层数组的一个连续片段的轻量级视图。切片本身不持有数据,而是通过一个结构体来管理对底层数组的访问,这个结构体包含三个关键信息:

  1. 底层数组的指针:指向数组实际数据的起始位置。
  2. 长度(len):切片中元素的数量,即切片可视范围的大小。
  3. 容量(cap):切片可以从底层数组中使用的最大元素数量,从切片的起始位置到数组的末尾。

切片(slice)是对数组的一个连续片段的引用,所以切片是一个引用类型.切片相对于数组的主要优点在于其动态大小的特性,可以在运行时根据需要动态地增长(通过append等函数),也可以通过切片表达式来截取数组的特定部分形成新的切片,从而实现灵活的数据操作。

切片的创建

切片可以通过以下几种方式创建:

语法:

从连续内存区域生成切片是常见的操作,格式如下:

slice [开始位置 : 结束位置]

语法说明如下:

  • slice:表示目标切片对象;
  • 开始位置:对应目标切片对象的索引;
  • 结束位置:对应目标切片的结束索引。

基于数组创建:通过指定数组的一部分来创建切片。

arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:3] // 创建包含arr[1]和arr[2]的切片
fmt.Println(slice)  //[2 3]

从数组或切片生成新的切片拥有如下特性:

  • 取出的元素数量为:结束位置 - 开始位置;
  • 取出元素不包含结束位置对应的索引,切片最后一个元素使用 slice[len(slice)] 获取;
  • 当缺省开始位置时,表示从连续区域开头到结束位置;
  • 当缺省结束位置时,表示从开始位置到整个连续区域末尾;
  • 两者同时缺省时,与切片本身等效;
  • 两者同时为 0 时,等效于空切片,一般用于切片复位。

根据索引位置取切片 slice 元素值时,取值范围是(0~len(slice)-1),超界会报运行时错误,生成切片时,结束位置可以填写 len(slice) 但不会报错。

 从指定范围中生成切片:

var highRiseBuilding [20]int
for i := 0; i < 20; i++ {
	highRiseBuilding[i] = i + 1
}
// 区间 [11 12 13 14 15]
fmt.Println(highRiseBuilding[10:15])
// 中间到尾部的所有元素 [11 12 13 14 15 16 17 18 19 20]
fmt.Println(highRiseBuilding[10:])
// 开头到中间指定位置的所有元素 [1 2]
fmt.Println(highRiseBuilding[:2])



//输出所有元素
a := []int{1, 2, 3}
fmt.Println(a[:]) //[1 2 3]



a := []int{1, 2, 3}
fmt.Println(a[0:0]) //输出为0

直接声明新的切片

除了可以从原有的数组或者切片中生成切片外,也可以声明一个新的切片,每一种类型都可以拥有其切片类型,表示多个相同类型元素的连续集合,因此切片类型也可以被声明,切片类型声明格式如下:

var name [ ]Type

其中 name 表示切片的变量名,Type 表示切片对应的元素类型。

下面代码展示了切片声明的使用过程:

// 声明字符串切片
var strList []string

// 声明整型切片
var numList []int

// 声明一个空切片
var numListEmpty = []int{}

// 输出3个切片
fmt.Println(strList, numList, numListEmpty)

// 输出3个切片大小
fmt.Println(len(strList), len(numList), len(numListEmpty))

// 切片判定空的结果
fmt.Println(strList == nil)
fmt.Println(numList == nil)
fmt.Println(numListEmpty == nil)
代码输出结果:

注:将 numListEmpty 声明为一个整型切片,本来会在{ }中填充切片的初始化元素,这里没有填充,所以切片是空的,但是此时的 numListEmpty 已经被分配了内存,只是还没有元素。

使用 make() 函数构造切片

如果需要动态地创建一个切片,可以使用 make() 内建函数,格式如下:

make( []Type, size, cap )

其中 Type 是指切片的元素类型,size 指的是为这个类型分配多少个元素,cap 为预分配的元素数量,这个值设定后不影响 size,只是能提前分配空间,降低多次分配空间造成的性能问题。

示例如下:

a := make([]int, 2)
b := make([]int, 2, 10)

fmt.Println(a, b)  //[0 0] [0 0]

fmt.Println(len(a), len(b)) //2 2

其中 a 和 b 均是预分配 2 个元素的切片,只是 b 的内部存储空间已经分配了 10 个,但实际使用了 2 个元素。容量不会影响当前的元素个数,因此 a 和 b 取 len 都是 2。

温馨提示

使用 make() 函数生成的切片一定发生了内存分配操作,但给定开始与结束位置(包括切片复位)的切片只是将新的切片结构指向已经分配好的内存区域,设定开始与结束位置,不会发生内存分配操作。

append()

追加元素:使用append函数可以在切片的末尾添加元素,这可能导致切片容量的自动扩展。

切片是动态结构,只能与 nil 判定相等,不能互相判定相等。声明新的切片后,可以使用 append()函数向切片中添加元素。

var a []int
a = append(a, 1)                 // 追加1个元素
a = append(a, 1, 2, 3)           // 追加多个元素, 手写解包方式
a = append(a, []int{1, 2, 3}...) // 追加一个切片, 切片需要解包
fmt.Println(a) //[1 1 2 3 1 2 3]

除了在切片的尾部追加,我们还可以在切片的开头添加元素:

var a = []int{1,2,3}
a = append([]int{0}, a...) // 在开头添加1个元素
a = append([]int{-3,-2,-1}, a...) // 在开头添加1个切片
fmt.Println(a) //[-3 -2 -1 0 1 2 3]

在切片开头添加元素一般都会导致内存的重新分配,而且会导致已有元素全部被复制 1 次,因此,从切片的开头添加元素的性能要比从尾部追加元素的性能差很多。

因为 append 函数返回新切片的特性,所以切片也支持链式操作,我们可以将多个 append 操作组合起来,实现在切片中间插入元素:

var a = []int{1, 2, 3, 4, 5, 6}
a = append(a[:5], append([]int{1}, a[5:]...)...) // 在索引为5个位置插入1
fmt.Println(a) //[1 2 3 4 5 1 6]
a = append(a[:2], append([]int{1, 2, 3}, a[2:]...)...) // 在索引为2个位置插入切片
fmt.Println(a) //[1 2 1 2 3 3 4 5 1 6]

每个添加操作中的第二个 append 调用都会创建一个临时切片,并将 a[i:] 的内容复制到新创建的切片中,然后将临时创建的切片再追加到 a[:i] 中。

切片的复制

Go语言的内置函数 copy() 可以将一个数组切片复制到另一个数组切片中,如果加入的两个数组切片不一样大,就会按照其中较小的那个数组切片的元素个数进行复制。

copy() 函数的使用格式如下:

copy( destSlice, srcSlice []T) int

其中 srcSlice 为数据来源切片,destSlice 为复制的目标(也就是将 srcSlice 复制到 destSlice),目标切片必须分配过空间且足够承载复制的元素个数,并且来源和目标的类型必须一致,copy() 函数的返回值表示实际发生复制的元素个数。

slice1 := []int{1, 2, 3, 4, 5}
slice2 := []int{5, 4, 3}
copy(slice2, slice1) // 只会复制slice1的前3个元素到slice2中
fmt.Println(slice2)  //[1 2 3]
copy(slice1, slice2) // 只会复制slice2的3个元素到slice1的前3个位置
fmt.Println(slice1) //[1 2 3 4 5]

【示例】通过代码演示对切片的引用和复制操作后对切片元素的影响。

package main
import "fmt"
func main() {
    // 设置元素数量为1000
    const elementCount = 1000
    // 预分配足够多的元素切片
    srcData := make([]int, elementCount)
    // 将切片赋值
    for i := 0; i < elementCount; i++ {
        srcData[i] = i
    }
    // 引用切片数据
    refData := srcData
    // 预分配足够多的元素切片
    copyData := make([]int, elementCount)
    // 将数据复制到新的切片空间中
    copy(copyData, srcData)
    // 修改原始数据的第一个元素
    srcData[0] = 999
    // 打印引用切片的第一个元素
    fmt.Println(refData[0])
    // 打印复制切片的第一个和最后一个元素
    fmt.Println(copyData[0], copyData[elementCount-1])
    // 复制原始数据从4到6(不包含)
    copy(copyData, srcData[4:6])
    for i := 0; i < 5; i++ {
        fmt.Printf("%d ", copyData[i])
    }
}

代码说明如下:

  • 第 8 行,定义元素总量为 1000。
  • 第 11 行,预分配拥有 1000 个元素的整型切片,这个切片将作为原始数据。
  • 第 14~16 行,将 srcData 填充 0~999 的整型值。
  • 第 19 行,将 refData 引用 srcData,切片不会因为等号操作进行元素的复制。
  • 第 22 行,预分配与 srcData 等大(大小相等)、同类型的切片 copyData。
  • 第 24 行,使用 copy() 函数将原始数据复制到 copyData 切片空间中。
  • 第 27 行,修改原始数据的第一个元素为 999。
  • 第 30 行,引用数据的第一个元素将会发生变化。
  • 第 33 行,打印复制数据的首位数据,由于数据是复制的,因此不会发生变化。
  • 第 36 行,将 srcData 的局部数据复制到 copyData 中。
  • 第 38~40 行,打印复制局部数据后的 copyData 元素。

参考文章:Go语言copy():切片复制(切片拷贝) (biancheng.net)

  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值