【golang学习之旅】复杂数据类型——切片(slice)

news2024/9/25 5:23:10

系列文章

【golang学习之旅】使用VScode安装配置Go开发环境
【golang学习之旅】报错:a declared but not used
【golang学习之旅】Go 的基本数据类型
【golang学习之旅】深入理解字符串string数据类型
【golang学习之旅】go mod tidy
【golang学习之旅】记录一次 panic case : reflect: reflect.Value.SetInt using unaddressable value
【golang学习之旅】记录一次 error case : full error output: cc1: error: unrecognized command line option
【golang学习之旅】Go程序快速开始 & Go程序开发的基本注意事项
【golang学习之旅】Go语言常用转义字符
【golang学习之旅】Go中的变量(1)
【golang学习之旅】Go中的变量——基本数据类型(1)
【golang学习之旅】Go中的变量——基本数据类型(2)
【golang学习之旅】复杂数据类型——指针 & 函数
【golang学习之旅】延迟调用——defer
【golang学习之旅】复杂数据类型——数组


  • 系列文章
  • 1. 理解切片(slice)
  • 2. 切片的定义
    • 2.1 切片的内存结构
    • 2.2 创建切片
  • 3. 操作切片
    • 3.1 添加切片元素
      • 3.1.1 append
      • 3.1.2 copy
    • 3.2 删除切片元素


1. 理解切片(slice)

切片(slice)是对数组一个连续片段的引用(该数组我们称之为相关数组,通常是匿名的),所以切片是一个引用类型(因此更类似于 C/C++ 中的数组类型,或者 Python 中的 list 类型)。这个片段可以是整个数组,或者是由起始和终止索引标识的一些项的子集

简单地说,切片就是一种简化版的动态数组

因为动态数组的长度是不固定,切片的长度自然也就不能是类型的组成部分了。数组虽然有适用它们的地方,但是数组的类型和操作都不够灵活,因此在 Go 代码中数组使用的并不多。而切片则使用得相当广泛,理解切片的原理和用法是一个 Go 程序员的必备技能。


2. 切片的定义

2.1 切片的内存结构

我们先看看切片的结构定义,reflect.SliceHeader

切片在内存中的组织方式实际上是一个有 3 个域的结构体:指向相关数组的指针,切片长度以及切片容量。

type SliceHeader struct {  
    Data uintptr // 指向底层数组的指针  
    Len  int     // 切片的长度(即元素的数量)  
    Cap  int     // 切片的容量(即底层数组可以容纳的元素数量)  
}

现在,考虑以下两个切片的声明和初始化:

x := []int{2, 3, 5, 7, 11}  
y := x[1:3]
  • x切片的内存结构
    • Data:指向一个包含至少5个int类型元素的数组的指针。具体地,这个数组包含元素[2, 3, 5, 7, 11]。
    • Len:5,因为x包含5个元素。
    • Cap:5,因为底层数组也恰好有5个元素的空间,所以切片x的容量等于其长度。
  • y切片的内存结构
    • Data:同样指向那个包含[2, 3, 5, 7, 11]的数组,但切片y的视角是从第二个元素开始的,即3(索引为1,因为索引从0开始)。
    • Len:2,因为y是通过x[1:3]创建的,所以它包含x中从索引1到索引2(不包含3)的元素,即[3, 5]。
      Cap:4,尽管y只包含了2个元素,但它的容量是从y的起始元素到原数组x末尾的元素数量,即[3, 5, 7, 11]这四个元素的空间。这是因为y和x共享同一个底层数组,所以y的容量受到这个共享数组的限制。
  • 示意图
    下面是一个简化的示意图,用于说明这两个切片的内存结构:
    在这里插入图片描述

2.2 创建切片

在Go中,我们可以通过多种方式创建切片:

  1. 直接使用数组创建切片:通过数组的一部分来初始化切片。
a := [5]int{1, 2, 3, 4, 5}  
s := a[1:4] // s是一个切片,包含a中索引为1到3的元素(即2, 3, 4)
  1. 使用make函数:make函数是Go中用于分配和初始化内置类型的内置函数,也可以用来创建切片。
s := make([]int, 0, 5) // 创建一个长度为0、容量为5的int类型切片
  1. 切片字面量:直接使用切片字面量创建切片。
s := []int{1, 2, 3} // 直接初始化一个切片

还有一些比较特殊的切片:

  1. nil 切片。未初始化的切片默认值为nil
var a []int 
  1. 空切片,也叫零值切片。和 nil 不相等, 一般用来表示一个空的集合。len 和 cap 都为 0。空切片在内部拥有一个非nil的、零长度的底层数组
var b = []int{} 
  • 在判断一个切片是否为空时,一般通过 len 获取切片的长度来判断,一般很少将切片和 nil 值做直接的比较

3. 操作切片

3.1 添加切片元素

3.1.1 append

使用append函数可以向切片末尾追加一个或多个元素。如果追加的元素超出了当前切片的容量,append会分配一个新的底层数组,并将原切片的内容以及新元素复制到新数组中。

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

这里需要注意...(三个点,称为“变参”或“展开运算符”)。在Go中,当你将切片或数组作为函数参数,并且希望将它的所有元素作为独立的参数传递给函数时,可以使用这个操作符。在这个上下文中,...[]int{1,2,3} 切片中的所有元素“展开”成独立的参数,传递给 append 函数。

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

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

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

3.1.2 copy

copy函数可以用来复制切片的内容:

src := []int{1, 2, 3}  
dst := make([]int, 3)  
copy(dst, src) // 现在dst是[1, 2, 3]的拷贝

可以用 copy 和 append 组合可以避免创建中间的临时切片,同样是完成添加元素的操作:

a = append(a, 0)     // 切片扩展 1 个空间
copy(a[i+1:], a[i:]) // a[i:] 向后移动 1 个位置
a[i] = x             // 设置新添加的元素
  • 第一句 append 用于扩展切片的长度,为要插入的元素留出空间
  • 第二句 copy 操作将要插入位置开始之后的元素向后挪动一个位置
  • 第三句真实地将新添加的元素赋值到对应的位置

用 copy 和 append 组合也可以实现在中间位置插入多个元素(也就是插入一个切片):

用 copy 和 append 组合也可以实现在中间位置插入多个元素(也就是插入一个切片):

a = append(a, x...)       // 为 x 切片扩展足够的空间
copy(a[i+len(x):], a[i:]) // a[i:] 向后移动 len(x) 个位置
copy(a[i:], x)            // 复制新添加的切片

稍显不足的是,在第一句扩展切片容量的时候,扩展空间部分的元素复制是没有必要的。没有专门的内置函数用于扩展切片的容量,append 本质是用于追加元素而不是扩展容量,扩展切片容量只是 append 的一个副作用。

3.2 删除切片元素

根据要删除元素的位置有三种情况:从开头位置删除,从中间位置删除,从尾部删除。

其中删除切片尾部的元素最快:

a = []int{1, 2, 3}
a = a[:len(a)-1]   // 删除尾部 1 个元素
a = a[:len(a)-N]   // 删除尾部 N 个元素

删除开头的元素可以直接移动数据指针:

a = []int{1, 2, 3}
a = a[1:] // 删除开头 1 个元素
a = a[N:] // 删除开头 N 个元素

删除开头的元素也可以不移动数据指针,但是将后面的数据向开头移动。可以用 append 原地完成(所谓原地完成是指在原有的切片数据对应的内存区间内完成,不会导致内存空间结构的变化):

a = []int{1, 2, 3}
a = append(a[:0], a[1:]...) // 删除开头 1 个元素
a = append(a[:0], a[N:]...) // 删除开头 N 个元素

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OpenHarmony实战开发: unittest单元测试的编写

背景 程序开发与单元测试二者密不可分,是每个开发人员的基本业务。当功能性代码开发完成后,要自行测试其是否满足设计预期,如果不满足就要回去完善代码,满足则可以提交功能代码及测试用例。 测试用例的另一个重要作用就是阅读者…

【USRP】 Link 16 战术数据链 实训系统

Link 16 战术数据链 实训系统 一、基于USRP的Link16平台简介1、整体架构2、JTIDS终端架构3、平台特点3.1、提高技术理解与应用能力3.2、培养创新思维与问题解决能力3.3、加强跨学科融合与团队合作 4、平台建设4.1、基础理论教学模块4.2、LabVIEW 算法模块4.3、USRP仿真模块4.4、…

如何用ChatGPT快速提升论文质量:实用技巧大公开

近年来,人工智能技术急速发展,尤其在自然语言处理领域取得了显著进展。作为OpenAI推的一款先进的语言模型,ChatGPT不仅在日常交互中表现卓越,也在专业写作领域显示了巨大的潜力。本文旨在详尽介绍如何高效运用ChatGPT来撰写和修改…

JWT双令牌认证实现无感Token自动续约

概念 JSON Web Token (JWT)是一个开放标准(RFC 7519) ,它定义了一种紧凑和自包含的方式,用于作为 JSON 对象在各方之间安全地传输信息。此信息可以进行验证和信任,因为它是经过数字签名的。JWT 可以使用机密(使用 HMAC 算法)或使用 RSA 或 E…

LeetCode --- 411周赛

题目列表 3258. 统计满足 K 约束的子字符串数量 I 3259. 超级饮料的最大强化能量 3260. 找出最大的 N 位 K 回文数 3261. 统计满足 K 约束的子字符串数量 II 一、统计满足K约束的子字符串数量I 这种要求满足区间内某种性质的题,一般都可以用滑动窗口来做。这题…

STM32MP157_uboot_初次编译

STM32MP157_uboot_初次编译 前言: 为了快速入门,这边选择直接使用正点原子提供的uboot源码,先体验一下uboot的编译流程,为后面的移植原厂uboot做环境准备。 1、获取正点原子的uboot源码(复制到虚拟机里面) …

均值漂移算法原理及Python实践

均值漂移算法(Mean Shift Algorithm)是一种基于密度的非参数聚类算法,其原理主要基于核密度估计和梯度上升方法。以下是均值漂移算法原理的详细解析: 1. 基本思想 均值漂移算法的基本思想是通过迭代地更新数据点的位置&#xff…

Android APK优化系列瘦身篇:实战一个APK从11MB压缩到4MB,APK无用资源去除与代码压缩、混淆,瘦身维度的选型分析

目录: 为什么要进行APK瘦身呢?APK瘦身主要是瘦身哪些呢? a. 优化resources.arsc: b. res优化 c. lib优化 d. 资源优化,代码混淆和压缩总结 一、为什么要进行APK瘦身呢? 减少下载时间和流量消耗&#xff1…

el-table 表格自定义添加表格数据后自动滚动到最底部

动态表格,可以新增行列数,为了用户体验,新增后超出表格流体高度后,自动滚动到最下方 需要element-plus如下api 代码如下: const addCapacity () > {inputList.value.push({name: "",desc: "&quo…

案例-登录认证

案例-登录认证 登录认证。 最终实现的效果就是用户必须登录之后,才可以访问后台系统中的功能。 1. 登录功能 1.1 需求 在登录界面中,我们可以输入用户的用户名以及密码,然后点击 “登录” 按钮就要请求服务器,服务端判断用户输入…

streamlit创建python的web应用

目录 简介基本示例:运行 Streamlit 应用: 简介 Streamlit 是一个开源的 Python 库,可以让你快速创建和分享自定义的 Web 应用,尤其适用于机器学习和数据科学项目。它简化了将数据脚本转换为交互式应用的过程,不需要任…

【案例54】登录显示ORA-28000 账户被锁定

问题现象 登录后显示ora-28000 账户被锁定 问题分析 ORA-28000 表示账户被锁定。 错误原因分析 1、密码多次输入错误:这是最常见的导致账户锁定的原因之一。用户在尝试登录数据库时,如果连续多次输入错误的密码,数据库系统为了安全起见&am…

sheng的学习笔记-AI-生成式方法

AI目录:sheng的学习笔记-AI目录-CSDN博客 需要额外的知识对应连接: EM:sheng的学习笔记-AI-EM算法-CSDN博客 贝叶斯: sheng的学习笔记-AI-贝叶斯(Bayesian)分类-CSDN博客 高斯混合模型:shen…

利用Matlab求解常微分方程(dsolve与ode45)

1.微分方程的基本概念 含义微分方程(英语:Differential equation,DE)是一种数学方程,用来描述某一类函数与其导数之间的关系。微分方程的通解是一个符合方程的函数。而在初等数学的代数方程里,解析解是一个…

RocketMQ~刷盘机制、主从复制方式、存储机制

刷盘机制 生产者发布MQ给Brocker,Brocker在存储这些数据的时候,需要进行刷盘,分为同步刷盘和异步刷盘。 在同步刷盘中需要等待一个刷盘成功的ACK,同步刷盘对MQ消息可靠性来说是一种不错的保障,但是性能上会有较大影响…

推荐5款能够写出高质量文章的ai写作工具!

在如今的信息爆炸时代,内容创作者面临着前所未有的挑战:如何快速而高质量地产生内容,满足日益增长的读者群需求。 无论是自媒体人士、博客作者、学者还是学生,大家都在寻找一种能够提升写作效率与质量的解决方案。 幸运的是&…

oneapi 获取通义千问模型密钥

1、打开 阿里云百炼大模型平台,点击开通,登录账号 2、获取密钥 3、查看模型名称,可以添加到 oneapi 的渠道中 别忘了添加向量模型

基于Java+SpringBoot+Vue的师生共评的作业管理系统设计与实现

基于JavaSpringBootVue的师生共评的作业管理系统设计与实现 前言 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN[新星计划]导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取项目下载方…

MySQL从入门到精通(第9-10章)

文章目录 9 子查询9.1 需求分析与问题解决9.1.1 实际问题9.1.2 子查询的使用9.1.3 子查询的分类 9.2 单行子查询9.2.1 单行比较操作符9.2.2 代码示例9.2.3 HAVING中的子查询9.2.4 CASE中的子查询9.2.5 子查询中的空值问题9.2.6 非法使用子查询 9.3 多行子查询9.3.1 多行比较操作…

文心快码 Baidu Comate 前端工程师观点分享:以文心快码 Baidu Comate为例,智能代码助手需要什么(三)

本系列视频来自百度工程效能部的前端研发经理杨经纬,她在由开源中国主办的“AI编程革新研发效能”OSC源创会杭州站105期线下沙龙活动上,从一款文心快码(Baidu Comate)前端工程师的角度,分享了关于智能研发工具本身的研…