【Golang】切片(slice)

2023-10-26

切片

切片(slice)是对数组的一个连续片段的引用,所以切片是一个引用类型,这个片段可以是整个数组,也可以是由起始和终止索引标识的一些项的子集,需要注意的是,终止索引标识的项不包括在切片内。

Go语言中切片的内部结构包含地址、大小和容量,切片一般用于快速地操作一块数据集合,如果将数据集合比作切糕的话,切片就是你要的“那一块”,切的过程包含从哪里开始(切片的起始位置)及切多大(切片的大小),容量可以理解为装切片的口袋大小,如下图所示:

在这里插入图片描述

切片默认指向一段连续内存区域,可以是数组,也可以是切片本身。

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

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

语法说明如下:

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

从数组生成切片,代码如下:

import "fmt"

func main() {
   var a = [3]int{1, 2, 3}

   fmt.Println(a, a[1:2])
}

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

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

直接声明新的切片

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

var name []Type
  • name 表示切片的变量名
  • Type 表示切片对应的元素类型
package main

import "fmt"

func main() {
   // 声明字符串切片
   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)
}

切片是动态结构 只能与nil判断相等,不能互相判等。

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

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

代码:

package main

import "fmt"

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

   fmt.Println(a, b)
   fmt.Println(len(a), len(b))
}

结果:

[0 0] [0 0]
2 2

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

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

append()函数为切片添加元素

Go语言的内建函数append()可以为切片动态添加元素,每个切片会指向一片内存空间,这片空间能容纳一定数量的元素。当空间不能容纳足够多的元素时,切片就会进行扩容(2倍),扩容操作往往发生在append()函数调用时。

代码:

package main

import "fmt"

func main() {
   //声明一个切片
   var numbers []int
   
   for i := 0; i < 10; i++ {
      numbers = append(numbers, i) //给切片中添加元素
      fmt.Printf("len: %d  cap: %d pointer: %p\n", len(numbers), cap(numbers), numbers)
   }
}

结果:

len: 1  cap: 1 pointer: 0xc000018080
len: 2  cap: 2 pointer: 0xc0000180a0
len: 3  cap: 4 pointer: 0xc00001a040
len: 4  cap: 4 pointer: 0xc00001a040
len: 5  cap: 8 pointer: 0xc00001e0c0
len: 6  cap: 8 pointer: 0xc00001e0c0
len: 7  cap: 8 pointer: 0xc00001e0c0
len: 8  cap: 8 pointer: 0xc00001e0c0
len: 9  cap: 16 pointer: 0xc000102000
len: 10  cap: 16 pointer: 0xc000102000

可以看到容量是以2倍进行扩增的。在进行扩增之后,地址也可能发生改变

也可以一次性添加多个元素

package main

import "fmt"

func main() {
   //声明一个切片
   var car []string

   //添加一个元素
   car = append(car, "oldDriver")
   //添加多个元素
   car = append(car, "ice", "sniper", "monk")

   fmt.Println(car)
   //添加切片
   team := []string{"pig", "cat"}

   car = append(car, team...)

   fmt.Println(car)

}

结果:

[oldDriver ice sniper monk]
[oldDriver ice sniper monk pig cat]

team后添加的...表示将team整个添加到car的后面

复制切到另一个切片

使用Go语言内建的copy函数,可以迅速地将一个切片的数据复制到另一个切片空间中,如果加入的两个数组切片不一样大,就会按照其中较小的那个数组切片的元素个数进行复制。

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

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

代码:

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])
   }
}

结果:

999
0 999
4 5 2 3 4
  • 第 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语言并没有对删除切片元素提供专用的语法或者接口,需要使用切片本身的特性来删除元素,根据要删除元素的位置有三种情况,分别是从开头位置删除、从中间位置删除和从尾部删除,其中删除切片尾部的元素速度最快。

从开头位置删除

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

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个元素

还可以用 copy() 函数来删除开头的元素:

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

从中间位置删除

对于删除中间的元素,需要对剩余的元素进行一次整体挪动,同样可以用 append 或 copy 原地完成:

a = []int{1, 2, 3, ...}
a = append(a[:i], a[i+1:]...) // 删除中间1个元素
a = append(a[:i], a[i+N:]...) // 删除中间N个元素
a = a[:i+copy(a[i:], a[i+1:])] // 删除中间1个元素
a = a[:i+copy(a[i:], a[i+N:])] // 删除中间N个元素

从尾部删除

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

删除开头的元素和删除尾部的元素都可以认为是删除中间元素操作的特殊情况,下面来看一个示例。

【示例】删除切片指定位置的元素。

package main

import "fmt"

func main() {
   seq := []string{"a", "b", "c", "d", "e"}

   // 指定删除位置
   index := 2

   // 查看删除位置之前的元素和之后的元素
   fmt.Println(seq[:index], seq[index+1:])

   // 将删除点前后的元素连接起来
   seq = append(seq[:index], seq[index+1:]...)

   fmt.Println(seq)
}

代码输出结果:

[a b] [d e]
[a b d e]

代码说明如下:

  • 第 1 行,声明一个整型切片,保存含有从 a 到 e 的字符串。
  • 第 4 行,为了演示和讲解方便,使用 index 变量保存需要删除的元素位置。
  • 第 7 行,seq[:index] 表示的就是被删除元素的前半部分,值为 [1 2],seq[index+1:] 表示的是被删除元素的后半部分,值为 [4 5]。
  • 第 10 行,使用 append() 函数将两个切片连接起来。
  • 第 12 行,输出连接好的新切片,此时,索引为 2 的元素已经被删除。

Go语言中删除切片元素的本质是,以被删除元素为分界点,将前后两个部分的内存重新连接起来。

连续容器的元素删除无论在任何语言中,都要将删除点前后的元素移动到新的位置,随着元素的增加,这个过程将会变得极为耗时,因此,当业务需要大量、频繁地从一个切片中删除元素时,如果对性能要求较高的话,就需要考虑更换其他的容器了(如双链表等能快速从删除点删除元素)

使用range关键字来迭代切片

代码:

package main

import "fmt"

func main() {
   //创建一个切片
   slice := []int{10, 20, 30}
   //迭代每个元素,并显示值和地址和切片中的地址
   for index, value := range slice {
      fmt.Printf("%d  value: %d value-addr: %X,slice-addr %X\n", index, value, &amp;value, &amp;slice[index])
   }
}

结果:

0  value: 10 value-addr: C000018080,slice-addr C00001C078
1  value: 20 value-addr: C000018080,slice-addr C00001C080
2  value: 30 value-addr: C000018080,slice-addr C00001C088

因为迭代返回的变量是一个在迭代过程中根据切片依次赋值的新变量,所以 value 的地址总是相同的,要想获取每个元素的地址,需要使用切片变量和索引值(例如上面代码中的 &slice[index])

如果不需要索引,可以使用匿名变量名-来忽略这个值。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Golang】切片(slice) 的相关文章

  • Golang 中的“相互”包导入

    是否可以在 Golang 中执行 相互 包导入之类的操作 举例来说 我有两个包 A 和 B 分别具有 AFunc 和 BFunc BFunc2 函数 package A import B func AFunc do stuff but al
  • 地图中的最大元素数

    GO 中的 Map 最多可以存储多少个元素 如果我需要经常从 Map 访问数据 那么在长时间运行的程序中不断向 Map 添加项目并从中检索项目是一个好主意吗 除了map length类型的最大值之外 map中的元素数量没有理论上的限制 in
  • 如何检查我的 golang 应用程序是否使用 Boringcrypto 而不是本机 golang crypto?

    上下文 我正在阅读多篇有关使我的 golang 应用程序符合 FIPS 要求的文章 换句话说 使我的应用程序使用 Boringcrypto 而不是本机 golang crypto https kupczynski info posts fi
  • 如果覆盖率低于一定百分比,则单元测试失败

    我制作了一个执行的 makefilego test cover 是否有可能失败make unit tests如果覆盖范围低于 X 则命令 我该怎么做呢 您可以使用TestMain在你的测试中做到这一点 TestMain 可以充当测试的自定义
  • IntelliJ 2017.1.2 GOLANG 调试不适用于包中的断点

    我的应用程序由一个 main go 文件和一些包组成 当在 main go 中命中断点时 IntelliJ 按预期工作 显示变量值等 但是 当在不同的包中设置断点时 除了被命中之外 不会显示任何变量 并且不会跳过 进入功能按预期工作 被击中
  • 为什么我不能执行 fmt.Sprintf("%d.%d.%d.%d", a...)?

    我正在学习 Go 但我一直坚持 Go 之旅 exercise stringer go https tour golang org methods 7 https tour golang org methods 7 这是一些代码 type I
  • 在 Go 中修改导入的库

    我的问题 弹性节拍 https www elastic co products beats是一个用 Go 编写的日志传送程序的开源项目 它具有多种日志输出功能 包括控制台 Elasticsearch 和 Redis 我想将我自己的输出添加到
  • 使用 google.protobuf.Timestamp 在 Go 中解析带有时区偏移的日期时间戳

    我正在创建一个将使用 GRPC 和 protobuf 的 Go 应用程序 我的 RPC 服务应获取包含类型的消息google protobuf Timestamp 解析它并最终将其保存在数据库中或对其执行更多操作 我对什么被认为是该类型的有
  • ReverseProxy取决于golang中的request.Body

    我想构建一个 http 反向代理 它检查 HTTP 主体 然后将 HTTP 请求发送到它的上游服务器 你怎么能在 Go 中做到这一点 初始尝试 如下 失败 因为 ReverseProxy 复制传入请求 修改它并发送 但正文已被读取 func
  • 如何在 Visual Studio Code 中使用 Delve 调试器进行远程调试

    我已经问过了 得到了很好的答复answer https stackoverflow com questions 39058823 how to use delve debugger in visual studio code用于使用 del
  • 我想在后端验证来自 golang 前端的时区

    前端在注册期间发送时区以及其他用户详细信息 我需要在时区上放置一个验证器来进行 api 测试 时区数据的格式为 GMT 10 00 Hawaii GMT 08 00 Pacific Time US amp Canada 我所做的是定义数组中
  • Go中如何自定义http.Client或http.Transport超时重试?

    我想实现一个自定义http Transport对于标准http Client 如果客户端超时 它将自动重试 附 由于某种原因 习俗http Transport is a 一定有 我已经查过了hashcorp go retryablehttp
  • 为什么结构中“[0]byte”的位置很重要?

    0 byte在golang中不应该占用任何内存空间 但这两个结构体的大小不同 type bar2 struct A int 0 byte type bar3 struct 0 byte A int 那么为什么这个位置 0 byte这里重要吗
  • 如何分析 VSCode 中函数的性能

    我用 C Golang 编写了一个程序 如何找到占用最高 CPU 周期的函数 目的是提高正在执行的程序的性能 2021 年 10 月 金香儿哈娜 https github com hyangah宣布 tweet https twitter
  • 如何在 Go 应用程序中处理打开/关闭数据库连接?

    我的 Web API 应用程序中有一组函数 他们对 Postgres 数据库中的数据执行一些操作 func CreateUser db err sql Open postgres user postgres password passwor
  • for 循环初始值设定项中的结构

    知道为什么 for 循环初始值设定项中的这个结构表达式在编译时会出现语法错误吗 在这种情况下 指向结构的指针工作正常 但 ofc 我需要如下所示的局部变量 感谢您的建议 type Request struct id int line byt
  • Golang GAE - 小胡子结构中的 intID

    这是一个Example https www dropbox com sh ur2ws1jnik6euef PjVJSwDTUc Blog Golang zip该应用程序的 关键代码在 golang code handler handler
  • 模板中的 bson.ObjectId

    我有一个具有 bson ObjectId 类型的结构 例如如下所示 type Test struct Id bson ObjectId Name string Foo string 我想在 html 模板中呈现它 Name Food a h
  • go中有memset的类似物吗?

    在 C 中 我可以使用某些值初始化数组memset https msdn microsoft com en us library aa246471 28v vs 60 29 aspx const int MAX 1000000 int is
  • 如何在 Linux 中编写文本模式 GUI? [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 当我编写脚本 程序时 我经常想弹出一个简单的文本 gui 来提示输入 我该怎么做 例如 来自 Shel

随机推荐

  • 用例的一些知识

    一 用例执行 说明 执行结果与用例的期望结果不一致 含义 为缺陷 提示 用例结果不通过为缺陷 需要进行缺陷管理 二 缺陷 01定义 软件中存在的各种问题 都为缺陷 简称bug 02缺陷标准 少功能 功能错误 多功能 缺少隐形功能 易用性 软
  • Android NDK 开发:实战案例

    0 前言 如果只学理论 不做实践 不踩踩坑 一般很难发现真正实践项目中的问题的 也比较难以加深对技术的理解 所以延续上篇 JNI 的实战Android NDK开发 JNI实战篇 这篇主要是一些 NDK 小项目的练习 由于这些项目网上都有 d
  • CGAL---点云处理

    CGAL是一款几何处理库 但是介绍其在点云中处理的资料比较少 现介绍一个专门介绍CGAL在点云数据中处理的网站 比包括常见的点云数据处理 功能包括 1 聚类 欧式聚类 2 配准 ICP 3 简化 格网 4 平滑 5 法向量估算 6 特征估算
  • C++ static的作用

    1 什么是static static 是C 中很常用的修饰符 它被用来控制变量的存储方式和可见性 2 为什么要引入static 函数内部定义的变量 在程序执行到它的定义处时 编译器为它在栈上分配空间 大家知道 函数在栈上分配的空间在此函数执
  • 鱼眼去锯齿

    include
  • hexo更换主题后出现问题:WARN No layout: index.html

    hexo更换主题后出现问题 WARN No layout index html hexo本地测试运行重启后页面空白 hexo g出现以上错误 错误原因 运行git clone 指令获得主题后 假设是NEXT主题 在theme主题下保存文件夹
  • 火狐不能同步的问题

    火狐不能同步的问题 解决方法 下载同版本的软件并且切换成同一个服务器类型 打开 帮助 gt 关于Firefox 查看连个软件版本号和上线时间是否一致 如果不一致 则下载最新版 同版本同上线时间 的软件 如果一致 右上角菜单 gt 选项 gt
  • 【状态估计】基于UKF法、AUKF法的电力系统三相状态估计研究(Matlab代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 2 运行结果 3 参考文献 4 Matlab代码及数据 1 概述 基于UKF法和AUKF法的电力系统三相状
  • Unity 鼠标拖动旋转物体,并且物体不会越转越乱

    用Mathf Abs 绝对值 计算鼠标水平和竖直方向谁的位移更大 减少因为一丢丢的其他方向移动导致的物体微幅旋转影响后面物体旋转会越来越混乱 这样可以让物体旋转更好的单个方向进行旋转 代码如下 public float speed 2f v
  • vue使用富文本编辑器:vue-quill-editor粘贴图片+图片上传服务器+预览图片

    引入vue quill editor 初始化vue quill editor npm install vue quill editor save 部分页面引入组件 import quill dist quill core css impor
  • 51单片机实现串口通信(主单片机到从单片机发送LED流水灯)

    其实这是个51单片机串口通信的小例子 课堂上老师说你们可以去尝试弄一下 于是就去网上找一下资料 就做了这个实验 先把一个作为主机 用来发送数据 另一个作为从机 用来接收数据 将两个程序各自烧录到对应的板子上去 并将主机的TX P3 0 接到
  • VS C++ 生成类图

    C 中如何快速清晰的了解定义类型及类型之间的关联关系 一个好的类图有助于你快速了解 那么怎么去生成一个类图呢 下面步骤可以帮到你 一 安装类设计器组件 1 确定是否已经安装 类设计器 如果未安装 可以打开 工具 gt 获取工具和功能 或者直
  • springboot Junit单元测试默认事务不提交

    目录 一 Junit初次使用 二 Junit事务问题 1 默认不提交事务 默认回滚 2 设置rollback 让Junit提交事务 一 Junit初次使用 因为以前总觉得Junit单元测试配置比较繁琐 代码功能大多使用main方法或者pos
  • SD秋叶安装教程

    前言 本部署整合包基于开源项目 stable diffusion webui制作 部署包作者 秋葉aaaki 免责声明 本安装包及启动器免费提供 无任何盈利目的 电脑配置要求 操作系统 windows10以后 CPU 不做强制要求 内存 推
  • 输出斐波那契数列的每一项,每五个换行

    7 2 利用数组计算斐波那契数列 15 分 本题要求编写程序 利用数组计算菲波那契 Fibonacci 数列的前N项 每行输出5个 题目保证计算结果在长整型范围内 Fibonacci数列就是满足任一项数字是前两项的和 最开始两项均定义为1
  • FFmpeg录制流

    FFmpeg下windows安装 下载地址 http ffmpeg org download html windows 下载 ffmpeg release essentials zip 这个文件名 解压后将bin目录加到环境变量path 录
  • 内存使用(分段、分区、分页、多级页表、快表)--OS

    内存使用 内存使用 将程序放在内存中 PC指向内存地址 首先 我们需要让程序进入内存 举个例子 int main int argc char argv text entry 入口地址 call main call exit main ret
  • windows默认文件(桌面、下载、文档等)设置为C盘根路径后怎么修改回去

    桌面 下载 文档等设置为C盘根路径后怎么修改回去 1 问题 2 解决办法 2 1 按 Win R 调出运行窗口 输入 regedit 并按回车 2 2 在弹出的注册表窗口里 打开下面路径 计算机 HKEY CURRENT USER SOFT
  • 数据结构——迪杰斯特拉(Dijkstra)算法

    迪杰斯特拉算法又叫狄克斯特拉算法 是从一个顶点到其余各顶点的最短路径算法 解决的是有权图中最短路径问题 迪杰斯特拉算法主要特点是从起始点开始 采用贪心算法的策略 每次遍历到始点距离最近且未访问过的顶点的邻接节点 直到扩展到终点为止 以下是数
  • 【Golang】切片(slice)

    文章目录 切片 直接声明新的切片 append 函数为切片添加元素 复制切到另一个切片 从切片中删除元素 从开头位置删除 从中间位置删除 从尾部删除 切片 切片 slice 是对数组的一个连续片段的引用 所以切片是一个引用类型 这个片段可以