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

2024-08-25 23:04

本文主要是介绍【golang学习之旅】复杂数据类型——切片(slice),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

系列文章

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

这篇关于【golang学习之旅】复杂数据类型——切片(slice)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1106903

相关文章

golang内存对齐的项目实践

《golang内存对齐的项目实践》本文主要介绍了golang内存对齐的项目实践,内存对齐不仅有助于提高内存访问效率,还确保了与硬件接口的兼容性,是Go语言编程中不可忽视的重要优化手段,下面就来介绍一下... 目录一、结构体中的字段顺序与内存对齐二、内存对齐的原理与规则三、调整结构体字段顺序优化内存对齐四、内

Java深度学习库DJL实现Python的NumPy方式

《Java深度学习库DJL实现Python的NumPy方式》本文介绍了DJL库的背景和基本功能,包括NDArray的创建、数学运算、数据获取和设置等,同时,还展示了如何使用NDArray进行数据预处理... 目录1 NDArray 的背景介绍1.1 架构2 JavaDJL使用2.1 安装DJL2.2 基本操

Golang操作DuckDB实战案例分享

《Golang操作DuckDB实战案例分享》DuckDB是一个嵌入式SQL数据库引擎,它与众所周知的SQLite非常相似,但它是为olap风格的工作负载设计的,DuckDB支持各种数据类型和SQL特性... 目录DuckDB的主要优点环境准备初始化表和数据查询单行或多行错误处理和事务完整代码最后总结Duck

Golang的CSP模型简介(最新推荐)

《Golang的CSP模型简介(最新推荐)》Golang采用了CSP(CommunicatingSequentialProcesses,通信顺序进程)并发模型,通过goroutine和channe... 目录前言一、介绍1. 什么是 CSP 模型2. Goroutine3. Channel4. Channe

Rust 数据类型详解

《Rust数据类型详解》本文介绍了Rust编程语言中的标量类型和复合类型,标量类型包括整数、浮点数、布尔和字符,而复合类型则包括元组和数组,标量类型用于表示单个值,具有不同的表示和范围,本文介绍的非... 目录一、标量类型(Scalar Types)1. 整数类型(Integer Types)1.1 整数字

Golang使用minio替代文件系统的实战教程

《Golang使用minio替代文件系统的实战教程》本文讨论项目开发中直接文件系统的限制或不足,接着介绍Minio对象存储的优势,同时给出Golang的实际示例代码,包括初始化客户端、读取minio对... 目录文件系统 vs Minio文件系统不足:对象存储:miniogolang连接Minio配置Min

使用Python实现大文件切片上传及断点续传的方法

《使用Python实现大文件切片上传及断点续传的方法》本文介绍了使用Python实现大文件切片上传及断点续传的方法,包括功能模块划分(获取上传文件接口状态、临时文件夹状态信息、切片上传、切片合并)、整... 目录概要整体架构流程技术细节获取上传文件状态接口获取临时文件夹状态信息接口切片上传功能文件合并功能小

Golang使用etcd构建分布式锁的示例分享

《Golang使用etcd构建分布式锁的示例分享》在本教程中,我们将学习如何使用Go和etcd构建分布式锁系统,分布式锁系统对于管理对分布式系统中共享资源的并发访问至关重要,它有助于维护一致性,防止竞... 目录引言环境准备新建Go项目实现加锁和解锁功能测试分布式锁重构实现失败重试总结引言我们将使用Go作

java poi实现Excel多级表头导出方式(多级表头,复杂表头)

《javapoi实现Excel多级表头导出方式(多级表头,复杂表头)》文章介绍了使用javapoi库实现Excel多级表头导出的方法,通过主代码、合并单元格、设置表头单元格宽度、填充数据、web下载... 目录Java poi实现Excel多级表头导出(多级表头,复杂表头)上代码1.主代码2.合并单元格3.

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert