【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 没有进行扩容操作。

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

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

相关文章

GCC的符号可见性: 解决Linux多个库同名符号冲突问题以及引用不同版本库的问题

目录 1 -fvisibilitydefault|internal|hidden|protected 1.1 __attribute__((visibility("default"))) 与 CXXg -fvisibilityhidden 的作用 1.2 __attribute__((visibility("hidden"))) 与 CXXg -fvisibilitydefault的作用 2 我的问题 2.1 解决措…

雾锁王国服务器怎么建?雾锁王国服务器搭建方法

雾锁王国Enshrouded服务器搭建怎么搭建&#xff1f;非常简单&#xff0c;阿里云计算巢雾锁王国程序&#xff0c;可以一键搭建雾锁王国多人联机服务器&#xff0c;腾讯云是基于雾锁王国镜像系统&#xff0c;阿里云服务网aliyunfuwuqi.com汇总雾锁王国服务器搭建&#xff0c;超简…

kafka三节点集群平滑升级过程指导

一、前言 Apache Kafka作为常用的开源分布式流媒体平台&#xff0c;可以实时发布、订阅、存储和处理数据流,多用于作为消息队列获取实时数据&#xff0c;构建对数据流的变化进行实时反应的应用程序&#xff0c;已被数千家公司用于高性能数据管道、流分析、数据集成和任务关键型…

DolphinScheduler——工作流实例的生命周期

目录 一、DolphinScheduler架构原理 1.1 系统架构图 1.2 DolphinScheduler核心概念 1.2 创建工作流 1.2.1 如何触发一个工作流实例 1.2.2 任务调度链路监控 1.2.3 Workflow-DAG解析 DAG解析 Dispatch分发流程 Master和Worker的交互过程 1.3 任务运行状态 该篇文章主…

就业班 2401--2.28 Linux Day7--存储管理1

一 .存储管理 主要知识点: 基本分区、逻辑卷LVM、EXT3/4/XFS文件系统、RAID 初识硬盘 机械 HDD 固态 SSD SSD的优势 SSD采用电子存储介质进行数据存储和读取的一种技术&#xff0c;拥有极高的存储性能&#xff0c;被认为是存储技术发展的未来新星。 与传统硬盘相比&#…

Codeforces Round 929 (Div. 3)(A,B,C,D,E,F,G)

这场没考什么算法&#xff0c;比较水&#xff0c;难度也不是很高。比赛链接 硬要说的话E有个 前缀和 加 二分&#xff0c;F是数学BFS&#xff0c;G是个构造 A. Turtle Puzzle: Rearrange and Negate 题意&#xff1a; 给你一个由 n n n 个整数组成的数组 a a a 。您必须对…

Rocky Linux 运维工具yum

一、yum的简介 ​​yum​是用于在基于RPM包管理系统的包管理工具。用户可以通过 ​yum​来搜索、安装、更新和删除软件包&#xff0c;自动处理依赖关系&#xff0c;方便快捷地管理系统上的软件。 二、yum的参数说明 1、install 用于在系统的上安装一个或多个软件包 2、seach 用…

golang使用gorm操作mysql1

1.mysql连接配置 package daoimport ("fmt""gorm.io/driver/mysql""gorm.io/gorm""gorm.io/gorm/logger" )var DB *gorm.DB// 连接数据库&#xff0c;启动服务的时候&#xff0c;init方法就会执行 func init() {username : "roo…

stm32学习笔记:ADC

1 ADC简介 ADC的作用ADC就是一个电压表&#xff0c;把引脚的电压值测出来&#xff0c;放在一个变量里 DAC的作用信号发生器、音频解码芯片 ADC的两个关键参数&#xff1a; 1、分辨率&#xff0c;一般用多少位来表示&#xff0c;12位AD值&#xff0c;它的表示范围就是0~2^12-1&…

Modern C++ std::any为何要求Tp可拷贝构造?

小问题也会影响设计的思路&#xff0c;某个问题或某种case的探讨有助于理解设计的初衷。 声明&#xff1a;以下_Tp/Tp都是指要放入std::any的对象的类型。 它要求_Tp is_copy_constructible, 仅仅是因为有很多函数的实现调用了Tp的拷贝构造函数吗&#xff1f;比如说上节提到的初…

pikachu之xss获取键盘记录

前备知识 跨域 跨域&#xff08;Cross-Origin&#xff09;是指在互联网中&#xff0c;浏览器为了保护用户信息安全而实施的一种安全策略——同源策略&#xff08;Same-Origin Policy&#xff09;&#xff0c;即浏览器禁止一个域上的文档或者脚本&#xff08;如通过JavaScript发…

Rocky Linux安装部署Elasticsearch(ELK日志服务器)

一、Elasticsearch的简介 Elasticsearch是一个强大的开源搜索和分析引擎&#xff0c;可用于实时处理和查询大量数据。它具有高性能、可扩展性和分布式特性&#xff0c;支持全文搜索、聚合分析、地理空间搜索等功能&#xff0c;是构建实时应用和大规模数据分析平台的首选工具。 …

(十七)devops持续集成开发——使用jenkins流水线pipeline方式发布一个微服务项目

前言 本节内容是一套关于微服务项目在docker环境中使用jenkins流水线部署的完整方案,在开始本节内容之前,我们需要提前安装好docker环境,以及docker本地镜像仓库docker harbor,同时安装好SonarQube用于代码验证,具体的安装步骤可参考作者的往期博客内容。 正文 在源码仓…

【MySQL】学习和总结标量子查询

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-kLo6jykc7AcEVEQk {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

爱普生为姿态和振动控制开发理想的IMU

- M-G370PDS0改善了短期噪声&#xff0c;扩展了型号阵容&#xff0c;使产品选择范围更广- 精工爱普生公司(“爱普生”&#xff0c;TSE: 6724)已经开发出M-G370PDS0样品&#xff0c;这是一种配备高性能六轴传感器的新型惯性测量单元(IMU)1。新产品计划于2022年春季批量生产。 爱…

MarkDown实用技巧:MarkDown中如何实现换行?

MarkDown实用技巧&#xff1a;MarkDown中如何实现换行&#xff1f; &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程 &#x1f448; 希望…

虚拟机安装+固定ip地址

一、下载CentOS 二、安装CentOS 1、打开你的VMware Workstation Pro&#xff0c;并点击“创建新的虚拟机” 2、点选典型(推荐)(T)&#xff0c;并点击“下一步” 3、点选稍后安装操作系统(S)&#xff0c;并点击“下一步” 4、点选Linux&#xff0c;并点击“下一步” 6、点击“…

flutter 一键打出不同包名、应用名、版本名、签名、应用图标、版本号的安装包

1. build.gradle 文件中配置不同的应用信息 flavorDimensions "app"productFlavors {app1 {//配置包名manifestPlaceholders [str: "releaseStr", package_name: "com.example.demo1"]applicationId "com.example.demo1"versionCode …

【SpringCloudAlibaba系列--OpenFeign组件】OpenFeign的配置、使用与测试以及OpenFeign的负载均衡

步骤一 准备两个服务&#xff0c;provider和consumer 本文使用kotlin语言 provider是服务的提供者&#xff0c;由provider连接数据库 RestController RequiredArgsConstructor RequestMapping("/provider/depart") class DepartController(private val departServ…

【GameFramework框架内置模块】6、实体(Entity)

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址 大家好&#xff0c;我是佛系工程师☆恬静的小魔龙☆&#xff0c;不定时更新Unity开发技巧&#xff0c;觉得有用记得一键三连哦。 一、前言 【GameFramework框架】系列教程目录&#xff1a; https://blog.csdn.net/q7…