【Go语言】Go语言中的切片

Go语言中的切片

1.切片的定义

Go语言中,切片是一个新的数据类型数据类型,与数组最大的区别在于,切片的类型中只有数据元素的类型,而没有长度:

var slice []string = []string{"a", "b", "c"}

因此,Go语言中的切片是一个可变长度的、同一类型元素集合,切片的长度可以随着元素数量的增长而增长,但不会随着元素数量的减少而减少,但切片底层依然使用数组来管理元素,可以看作是对数组做了一层简单的封装。

创建切片的方法共有三种,分别是基于数组、切片和直接创建。

1.1 基于数组创建切片

切片可以基于一个已存在的数组创建,切片可以只使用数组的一部分元素或者全部元素,甚至可以创建一个比数组更大的切片。

// 先定义一个数组
months := [...]string{"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}
// 基于数组创建切片
q2 := months[3:6]     // 第二季度
summer := months[5:8] // 夏季

Go语言支持通过 array[start:end]这样的方式基于数组生成一个切片,start表示切片在数组中的下标七点,end表示切片在数组中的下表终点,两者之间的元素就是切片初始化后的元素集合,以下是几种创建切片的示例:

  • 基于months 的所有元素创建切片(全年)

    all := months[:]
  • 基于 months 的前6个元素创建切片(上半年)

    firsthalf := months[:6]
  • 基于第6个元素开始的后的后续元素创建切片(下半年)

    secondhalf := months[6:]

1.2 基于切片创建切片

类似于切片能够基于一个数组创建,切片也能够基于另一个切片创建:

firsthalf := months[:6]
q1 := firsthalf[:3]	// 基于firsthalf的前三个元素构建新切片

基于切片创建切片时,选择的元素范围可以超过所包含元素的个数,如下:

// 基于切片创建切片
firsthalf := months[:6]
q1 := firsthalf[:3]
// 可以创建超过切片的元素
q3 := q1[:12]

如上图所示,q3长度远超过q1的长度,超出的部分由原数组months中的元素进行补充,那能不能超过这个原数组的长度呢?

产生了报错,显示切片的长度为13,但是容量是12,因此这里虽然是基于切片创建切片,但其本质依旧是基于数组创建切片。

1.3 直接创建切片

创建切片并不是一定需要一个数组,Go语言的内置函数make()可以灵活地创建切片。

创建一个初始长度位5的整型切片:

mySlice := make([]int, 5)

创建一个初始长度为5,容量为10的整型切片:

mySlice2 := make([]int, 5, 10)

创建并初始化包含5个元素的数组切片(长度和容量均为5):

// 这个语句容易和数组的初始化语句混淆
// 数组的初始化语句 array := [5]int{1,2,3,4,5}
// 这两个的区别在于切片初始化不需要指定切片长度,而数组需要指定数组长度
mySlice3 := []int{1, 2, 3, 4, 5}

和数组类型一样,所有未初始化的切片,会填充元素类型对应的零值。

实际上,使用直接方式创建切片时,Go底层还是会有一个匿名数组被创建出来,然后调用基于数组创建切片的方式返回切片,只是上层并不需要关心这个匿名数组的操作。因此,最终切片都是基于数组创建的,切片可以看作是操作数组的指针。

2 切片的遍历

前面提到,切片可以看作是数组指针,因此操作数组元素的所有方法也适用于切片,例如切片也能够使用下标获取元素,使用len()函数获取元素个数,并支持使用range关键字来快速遍历所有的元素。

传统的数组遍历方法:

for i := 0; i < len(summer); i++ {
    fmt.Println("summer[", i, "] =", summer[i]) 
}

也可以使用range关键字遍历:

for i, v := range summer { 
    fmt.Println("summer[", i, "] =", v) 
}

3 动态增加元素

切片与数组相比,优势在于支持动态增加元素,甚至能够在容量不足的情况,在切片类型中,元素个数和实际可分配的存储空间是两个不同的值,元素的个数即切片的实际长度,而可分配的存储空间就是切片的容量。

一个切片的容量初始值根据创建方式有以下两种情况:

  • 对于基于数组和切片创建的切片而言,默认的容量是从切片起始索引到对应底层数组的结尾索引。

  • 对于通过内置make函数创建的切片而言,在没有指定容量参数的情况下,默认容量和切片长度一致。

因此,通常情况下一个切片的长度值小于等于其容量值,能够通过Go语言内置的cap()函数和len()函数来获取某个切片的容量和实际长度:

var oldSlice = make([]int, 5, 10)
fmt.Println("len(oldSlice):", len(oldSlice))
fmt.Println("cap(oldSlice):", cap(oldSlice))

此时,切片 oldSilece 的默认值是 [0,0,0,0,0],可以通过append()函数向切片追加新元素:

newSlice := append(oldSlice, 1, 2, 3)

append() 函数的第二个参数是一个不定参数,可以根据自己的需求添加元素(大于等于1个),也可以直接将一个切片追加到另一个切片的末尾:

slice2 := []int{1, 2, 3, 4, 5}
// 注意append()后面的...不能省略
slice3 := append(newSlice, slice2...)

4 自动扩容

如果追加的元素个数超出切片的默认容量,则底层会自动进行扩容:

oldSlice := []int{1, 2, 3, 4, 5}
newSlice := append(oldSlice, 6, 7, 8, 9)
fmt.Println("oldSlice:", oldSlice, "len:", len(oldSlice), "cap:", cap(oldSlice))
fmt.Println("newSlice:", newSlice, "len:", len(newSlice), "cap:", cap(newSlice))

此时,newSlice 的长度变成了9,容量变成了10,需要注意的是 append() 函数并不会改变原来的切片,而是会生成一个容量更大的切片,然后把原有的元素和新元素一并拷贝到新切片中。

默认情况下,扩容后的新切片容量将会是原切片容量的两倍,如果还不能够容纳新元素,则按照同样的操作继续扩容,直到新切片的容量不小于原长度与要追加的元素之和。但是,当原切片的长度大于或等于1024时,Go语言会以原容量的1.25倍作为新容量的基准。

在编码中,如果能够事先预估切片的容量并在初始化时合理地设置容量值,可以大幅降低切片内部重新分配内存和搬送内存块的操作次数,从而提升程序性能。

5 内容复制

Go语言提供了内置函数copy(),用于将元素从一个切片复制到另一个切片,如果两个切片不一样大,就会按照其中较小的那个切片元素个数进行复制。

slice1 := []int{1, 2, 3, 4, 5}
slice2 := []int{6, 7, 8}
// 复制slice1到slice2,复制slice1的前三个元素到slice2中
copy(slice2, slice1)
fmt.Println("slice1:", slice1, "len:", len(slice1), "cap:", cap(slice1))
fmt.Println("slice2:", slice2, "len:", len(slice2), "cap:", cap(slice2))
slice3 := []int{1, 2, 3, 4, 5}
slice4 := []int{6, 7, 8}
fmt.Println("复制slice4到slice3")
// 复制slice4到slice3,复制slice4的所有元素到slice3的前三个元素
copy(slice3, slice4)

6 动态删除元素

切片除了支持动态增加元素之外,还可以动态删除元素,在切片中动态删除元素可以通过多种方式实现(底层是通过切片的切片实现):

slice1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
slice1 = slice1[:len(slice1)-5] // 删除 slice1 尾部 5 个元素
slice2 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
slice2 = slice2[5:] // 删除 slice2头部 5 个元素

还能够通过 append 实现切片元素的删除:

slice3 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
slice4 := append(slice3[:0], slice3[3:]...) // 删除开头三个元素

注意append方法的使用, 如 slice4 := append(slice3[:0], slice3[3:]...) 这种方式:

  • slice3[:0] 创建了一个长度为 0 的切片,但底层数组仍然是 slice3 的底层数组。

  • slice3[3:] 创建了一个包含 slice3 从索引3开始的所有元素的切片。

append 将第一个切片的元素追加到第二个切片中,因此 slice4 包含 slice3 从索引3开始的所有元素。

这里的问题在于,由于slice4最初共享底层数组,对 slice4 的修改实际上也会影响到 slice3,从而导致 slice3 切片也发生了变化。

如果 slice4 由两个切片拼接,也会出现类似的问题,例如:

slice5 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
slice6 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
slice7 := append(slice5[:3], slice6[6:]...)

如果想要保证两个切片是完全独立的,不共享底层数组,可以使用copy函数来进行切片的删除。

使用 copy 函数进行元素的删除:

slice8 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
slice9 := make([]int, len(slice3)-3)
copy(slice9, slice3[3:]) // 删除开头前三个元素

7 数据共享问题

切片底层是基于数组实现的,对应的结构体对象如下所示:

type slice struct {
    array unsafe.Pointer //指向存放数据的数组指针
    len   int            //长度有多大
    cap   int            //容量有多大
}

在结构体中使用指针存在不同实例的数据共享问题,示例代码如下:

slice1 := []int{1, 2, 3, 4, 5}
slice2 := slice1[1:3]
slice2[1] = 6
fmt.Println("slice1:", slice1)
fmt.Println("slice2:", slice2)

slice2 是基于 slice1 创建的,它们的数组指针指向了同一个数组,因此,修改 slice2 元素会同步到 slice1,因为修改的是同一份内存数据,这就是切片的数据共享问题。

可以按照如下方式,避免切片的数据共享问题。

slice3 := make([]int, 4)
slice4 := slice3[1:3]
slice3 = append(slice3, 0)
slice3[1] = 2
slice4[1] = 6
fmt.Println("slice3:", slice3)
fmt.Println("slice4:", slice4)

虽然 slice2 是基于 slice1 创建的,但是修改 slice2 不会再同步到 slice1,因为 append 函数会重新分配新的内存,然后将结果赋值给 slice1,这样一来,slice2 会和老的 slice1 共享同一个底层数组内存,不再和新的 slice1 共享内存,也就不存在数据共享问题了。

如下代码,虽然使用了append函数,但是没有重新分配内存空间,仍然存在数据共享问题。

slice5 := make([]int, 4, 5)
slice6 := slice5[1:3]
slice5 = append(slice5, 0)
slice5[1] = 2
slice6[1] = 6

slice5 容量为5,执行 append 没有进行扩容操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/415143.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

android应用开发基础知识,安卓面试2020

第一章&#xff1a;设计思想与代码质量优化 1、设计思想六大原则 2、三大设计模式 3、数据结构 4、算法 第二章&#xff1a;程序性能优化 1、启动速度和执行效率优化 2、布局检测与优化 3、内存优化 4、耗电优化 5、网络传输与数据存储优化 6、APK大小优化 7、屏幕适配 8、…

Sora - 真正单兵作战时代来临了

一、 OpenAI Sora 视频生成模型技术报告总结 不管是在视频的保真度、长度、稳定性、一致性、分辨率、文字理解等方面&#xff0c;Sora都做到了SOTA&#xff08;当前最优&#xff09;。 技术细节写得比较泛&#xff08;防止别人模仿&#xff09;大概就是用视觉块编码&#xff08…

通过QScrollArea寻找最后一个弹簧并且设置弹簧大小

项目原因&#xff0c;最近需要通过QScrollArea寻找其中最后一个弹簧并且设置大小和策略&#xff0c;因为无法直接调用UI指针&#xff0c;所以只能用代码寻找。 直接上代码&#xff1a; if (m_scrollArea){int iScrollWidth m_labelSelectedTitle->width();m_scrollArea-&g…

第三百七十二回

文章目录 1. 概念介绍2. 实现方法2.1 maskFilter2.2 shader 3. 代码与效果3.1 示例代码3.2 运行效果 4. 内容总结 我们在上一章回中介绍了"两种阴影效果"相关的内容&#xff0c;本章回中将介绍如何绘制阴影效果.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概…

Python爬虫实战第二例【二】

零.前言&#xff1a; 本文章借鉴&#xff1a;Python爬虫实战&#xff08;五&#xff09;&#xff1a;根据关键字爬取某度图片批量下载到本地&#xff08;附上完整源码&#xff09;_python爬虫下载图片-CSDN博客 大佬的文章里面有API的获取&#xff0c;在这里我就不赘述了。 一…

一文搞懂运放!

11.集成运放 集成电路是采用专门的制造工艺&#xff0c;在半导体单晶硅上&#xff0c;把晶体管、场效应管、二极管、电阻和电容等元器件以及它们之间的连线所组成的电路制作在一起&#xff0c;使其具有特定功能的芯片。 1.组成 集成运放由输入级、中间级、输出级和偏置电路四…

Python 深拷贝在接口自动化里的用法!

深拷贝&#xff08;deep copy&#xff09;常用于复制请求参数、配置对象或其他复杂数据结构&#xff0c;以确保每次发送请求时使用的是独立的数据副本&#xff0c;避免不同请求之间的数据互相影响。例如&#xff0c;当你需要多次调用同一个接口&#xff0c;但每次调用的参数略有…

关于静态应用程序安全测试(SAST)的自动修复(AutoFix)

SAST&#xff08;Static Application Security Testing&#xff0c;静态应用程序安全测试&#xff09;具是一种在软件工程中使用的安全解决方案&#xff0c;它主要用于在程序员编写好源代码后&#xff0c;无需经过编译器编译&#xff0c;直接对源代码进行扫描&#xff0c;以找出…

旋转齿轮加载

效果演示 实现了一个旋转齿轮的动画效果。具体来说&#xff0c;页面背景为深灰色&#xff0c;中间有一个齿轮装置&#xff0c;包括四个齿轮。每个齿轮都有内部的齿轮条&#xff0c;整体呈现出旋转的效果。其中&#xff0c;齿轮2是顺时针旋转的&#xff0c;齿轮1、3、4是逆时针旋…

如何开通微信小程序商城

微信小程序店铺是一种新型的线上商城&#xff0c;可以帮助商家快速搭建自己的线上销售平台&#xff0c;吸引更多的用户进行购买。作为小程序服务商&#xff0c;我们可以帮助商家开通微信小程序店铺&#xff0c;提升他们的线上销售业绩。 1. 进入采云小程序。进入采云小程序首页…

Python——Tchisla求解器(暴力搜索法)

Tchisla简介 最近玩到一个挺有意思的数字解密小游戏《Tchisla》&#xff0c;其规则类似算24点&#xff0c;也是利用一些数学运算和初始数字计算出目标数字&#xff0c;与算24点不同的是&#xff0c;Tchisla允许不限次数地使用一种初始数字&#xff08;1~9&#xff09;&#xf…

MySQL篇—持久化和非持久化统计信息介绍(第一篇,总共三篇)

☘️博主介绍☘️&#xff1a; ✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ ✌✌️擅长Oracle、MySQL、SQLserver、Linux&#xff0c;也在积极的扩展IT方向的其他知识面✌✌️ ❣️❣️❣️大佬们都喜欢静静的看文章&#xff0c;并且也会默默的点赞收藏加关注❣…

科技论文编写思路

科技论文编写思路 1.基本框架2.课题可行性评估1.研究目标和意义2.研究方法和技术3.可行性和可操作性4.风险和不确定性5.经济性和资源投入6.成果预期和评估 3.写作思路4.利用AI读论文5.实验流程 1.基本框架 IntroductionRelated worksMethodExperiment and analysisDiscussionC…

JavaScript作用域及预解析

文章目录 1. 作用域介绍2. 变量的作用域*3. JS中没有块级作用域4. 作用域链5. 预解析预解析案例 1. 作用域介绍 全局作用域局部作用域相同的变量名称在不同的作用域中是不会相互影响的&#xff01; 2. 变量的作用域 全局变量&#xff1a;在全局下都可以使用&#xff1b;局部变…

集群分发脚本xsync

集群分发脚本xsync 一、简介二、环境准备三、添加到机器的 hosts 文件四、ping 命令测试五、SSH 配置5.1.本地先生成公钥和私钥5.2.将公钥拷贝到其他机器 六、xsync 脚本编写6.1.安装 rsync6.2.新建 xsync.sh6.3.xsync.sh脚本6.4.赋予脚本执行权限6.5.测试 endl 一、简介 配置…

学习笔记-李沐动手学深度学习(七)(19-21,卷积层、填充padding、步幅stride、多输入多输出通道)

总结 19-卷积层 【补充】看评论区建议的卷积动画视频 数学中的卷积 【链接】https://www.bilibili.com/video/BV1VV411478E/?fromsearch&seid1725700777641154181&vd_sourcee81e116c4ffe5e79d4bc44738263eda4 【可判断是否为卷积的典型标志】两个函数中自变量相加…

Unity零基础到进阶 | Unity中的 RectTransformUtility 方法整理汇总

Unity零基础到进阶 ☀️| RectTransformUtility 方法整理汇总一、RectTransformUtility 官方文档1.1 RectTransformUtility.CalculateRelativeRectTransformBounds&#xff08;重&#xff09;1.2 RectTransformUtility.FlipLayoutAxes1.3 RectTransformUtility.FlipLayoutOnAxi…

Unity中URP实现水体(水的焦散)

文章目录 前言一、原理1、 通过深度图&#xff0c;得到 对应像素 在 世界空间下的Z值2、得到模型顶点在 观察空间 下的坐标3、由以上两点得到 深度图像素 对应的 xyz 值4、最后&#xff0c;转化到 模型本地空间下&#xff0c;用其对焦散纹理采样 二、实现1、获取深度图2、在顶点…

[WebUI Forge]ForgeUI的安装与使用 | 相比较于Auto1111 webui 6G显存速度提升60-75%

ForgeUI的github主页地址:https://github.com/lllyasviel/stable-diffusion-webui-forge Stable Diffusion WebUI Forge 是一个基于Stable Diffusion WebUI(基于Gradio)的平台,可简化开发、优化资源管理并加快推理速度。 “Forge”这个名字的灵感来自于“Minecraft Forge”…

《Vite 基础知识》Vitepress 技术文档站点搭建与配置

前言 简介 VitePress 是一个静态站点生成器 (SSG)&#xff0c;专为构建快速、以内容为中心的站点而设计。 简而言之&#xff0c;可构建你自己的 技术文档站点&#xff1b; 环境要求 Node.js 18 及以上版本。我使用 v20.11.0 创建 第一步&#xff1a; 全局安装 npm i vitep…