go语言中的切片详解

1.概念

在Go语言中,切片(Slice)是一种基于数组的更高级的数据结构,它提供了一种灵活、动态的方式来处理序列数据。切片在Go中非常常用,因为它们可以动态地增长和缩小,这使得它们比固定大小的数组更加灵活。

2.切片的内部实现

 切片是对数组的抽象表示,它包含三个要素:

1.  指向底层数组的指针
2.  切片的长度(元素数量)
3.  切片的容量(从开始到底层数组末尾的元素数量)

切片是一个有三个字段的数据结构,这些数据结构包含 Golang 需要操作底层数组的元数据: 

 这 3 个字段分别是指向底层数组的指针、切片访问的元素的个数(即长度)和切片允许增长到的元素个数(即容量)。

3.切片创建和初始化

在 Golang 中可以通过多种方式创建和初始化切片。是否提前知道切片所需的容量通常会决定如何创建切片

通过 make() 函数创建切片
使用 Golang 内置的 make() 函数创建切片,此时需要传入一个参数来指定切片的长度:

slice := make([]int, length, capacity)

这里,length是切片的长度,capacity是切片的容量。如果未指定capacity,它将等于length。

// 创建一个整型切片
// 其长度和容量都是 5 个元素
slice := make([]int, 5)

分别指定长度和容量时,创建的切片,底层数组的长度是指定的容量,但是初始化后并不能
访问所有的数组元素。

注意,Golang 不允许创建容量小于长度的切片,当创建的切片容量小于长度时会在编译时刻报错:

// 创建一个整型切片
// 使其长度大于容量
myNum := make([]int, 5, 3)

分别指定长度和容量时,创建的切片,底层数组的长度是指定的容量,但是初始化后并不能
访问所有的数组元素。

注意,Golang 不允许创建容量小于长度的切片,当创建的切片容量小于长度时会在编译时刻报错 

4.通过字面量创建切片

另一种常用的创建切片的方法是使用切片字面量,这种方法和创建数组类似,只是不需要指定[]运算符里的值。初始的长度和容量会基于初始化时提供的元素的个数确定:

// 创建字符串切片
// 其长度和容量都是 3 个元素
myStr := []string{"Jack", "Mark", "Nick"}
// 创建一个整型切片
// 其长度和容量都是 4 个元素
myNum := []int{10, 20, 30, 40}

当使用切片字面量创建切片时,还可以设置初始长度和容量。要做的就是在初始化时给出所需的长度和容量作为索引。下面的语法展示了如何使用索引方式创建长度和容量都是100个元素的切片:

// 创建字符串切片
// 使用空字符串初始化第 100 个元素
myStr := []string{99: ""}

区分数组的声明和切片的声明方式
当使用字面量来声明切片时,其语法与使用字面量声明数组非常相似。二者的区别是:如果在 [] 运算符里指定了一个值,那么创建的就是数组而不是切片。只有在 [] 中不指定值的时候,创建的才是切片。看下面的例子:

// 创建有 3 个元素的整型数组
myArray := [3]int{10, 20, 30}
// 创建长度和容量都是 3 的整型切片
mySlice := []int{10, 20, 30}

切片的复制:

当你有一个切片或者数组的时候,当希望将其元素作为单独的参数传递给一个函数时候,可以使用...操作符

func sum(x, y, z int) int {return x + y + z
}numbers := []int{1, 2, 3}
result := sum(numbers...) // 将 numbers 切片中的元素作为参数传递给 sum 函数
fmt.Println(result) // 输出: 6

在这个例子中,numbers...将切片numbers中的元素1,2,3展开为sum函数的参数 

5.nil 和空切片

有时,程序可能需要声明一个值为 nil 的切片(也称nil切片)。只要在声明时不做任何初始化,就会创建一个 nil 切片

// 创建 nil 整型切片
var myNum []int

在 Golang 中,nil 切片是很常见的创建切片的方法。nil 切片可以用于很多标准库和内置函数。在需要描述一个不存在的切片时,nil 切片会很好用。比如,函数要求返回一个切片但是发生异常的时候。下图描述了 nil 切片的状态:

空切片和 nil 切片稍有不同,下面的代码分别通过 make() 函数和字面量的方式创建空切片: 

// 使用 make 创建空的整型切片
myNum := make([]int, 0)
// 使用切片字面量创建空的整型切片
myNum := []int{}

区别:
1.  内存分配:
•  nil 切片:没有指向任何底层数组,没有任何内存分配。
•  空切片:指向一个长度为0的底层数组,但这个数组可能已经分配了内存(尽管长度为0)。


2.  函数返回值:
•  使用 nil 可以表示“没有值”,这在错误处理和可选值中很有用。
•  空切片通常用于表示一个有效的切片,但当前没有元素。

 

package main
import "fmt"func main() {//创建一个空切片emptySlice :=make([]int,0)fmt.Println(emptySlice)//创建一个nil切片var nilSlice []intfmt.Println(nilSlice)//检查切片是否为空if len(emptySlice) == 0 {fmt.Println("emptySlice is empty")}if nilSlice == nil {fmt.Println("nilSlice is nil")}//尝试访问空切片的元素if len(emptySlice) > 0 {fmt.Println(emptySlice[0])}else {fmt.Println("emptySlice is empty")}}

•  emptySlice是一个空切片,它有长度0,但是它指向一个内存地址。
•  nilSlice是一个nil指针,它没有指向任何内存地址,因此不能用来访问任何元素。


尝试访问nil切片的元素会导致程序崩溃(panic),因为nil没有指向有效的内存。而空切片虽然长度为0,但是它是指向一个有效的内存地址的,所以可以安全地对其进行操作,比如扩展或赋值。

6.为切片中的元素赋值

对切片里某个索引指向的元素赋值和对数组里某个索引指向的元素赋值的方法完全一样。使
用 [] 操作符就可以改变某个元素的值,下面是使用切片字面量来声明切片:

// 创建一个整型切片
// 其容量和长度都是 5 个元素
myNum := []int{10, 20, 30, 40, 50}
// 改变索引为 1 的元素的值
myNum [1] = 25

切片之所以被称为切片,是因为创建一个新的切片,也就是把底层数组切出一部分。通过切片创建新切片的语法如下:

slice[i:j]
slice[i:j:k]

其中 i 表示从 slice 的第几个元素开始切,j 控制切片的长度(j-i),k 控制切片的容量(k-i),如果没有给定 k,则表示切到底层数组的最尾部。下面是几种常见的简写形式:

slice[i:]  // 从 i 切到最尾部
slice[:j]  // 从最开头切到 j(不包含 j)
slice[:]   // 从头切到尾,等价于复制整个 slice

让我们通过下面的例子来理解通过切片创建新的切片的本质:

// 创建一个整型切片
// 其长度和容量都是 5 个元素
myNum := []int{10, 20, 30, 40, 50}
// 创建一个新切片
// 其长度为 2 个元素,容量为 4 个元素
newNum := slice[1:3]

执行上面的代码后,我们有了两个切片,它们共享同一段底层数组,但通过不同的切片会看到底层数组的不同部分:

 

下面是详细的用法: 

package mainimport "fmt"func main() {// 创建一个原始切片original := []int{1, 2, 3, 4, 5}// 通过切片操作符创建新的切片a := original[:3] // 包含从索引0到索引2的元素b := original[2:] // 包含从索引2到末尾的元素c := original[:] // 创建一个新的切片,它是原始切片的一个副本// 修改原始切片original[0] = 10// 打印结果fmt.Println("Original slice:", original)  // 输出: [10 2 3 4 5]fmt.Println("Slice a:", a)               // 输出: [1 2 3]fmt.Println("Slice b:", b)               // 输出: [3 4 5]fmt.Println("Slice c:", c)               // 输出: [1 2 3 4 5]// 修改新的切片aa[0] = 100// 打印结果fmt.Println("Original slice after modifying a:", original)  // 输出: [10 2 3 4 5]fmt.Println("Slice a after modification:", a)               // 输出: [100 2 3]
}

共享底层数组的切片
需要注意的是:现在两个切片 myNum 和 newNum 共享同一个底层数组。如果一个切片修改了该底层数组的共享
部分,另一个切片也能感知到(请参考前图):

在Go语言中,切片是引用类型,这意味着它们指向底层数组的连续区域。当你通过切片操作创建一个新的切片时,新切片和原始切片可能共享同一个底层数组。如果新切片的容量足够,对新切片的修改可能会反映到原始切片上,因为它们可能指向相同的底层数组元素。

如果你需要一个与原始切片完全独立的副本,可以使用copy函数来创建一个新的底层数组。这样,对新数组的修改就不会影响原始数组。
下面是一个详细的示例,展示了如何使用copy函数创建一个完全独立的切片副本,并解释了为什么这样做可以避免对原始切片的意外修改。

package mainimport "fmt"func main() {original := []int{1,2,3,4,5}//使用切片操作符创建一个新的切片,它与原始切片共享同一个底层数组sharedSlice := original[:]independentSlice :=make([]int, len(original))copy(independentSlice, original)// 打印原始切片和两个新切片的内容fmt.Println("Original slice:", original)       // 输出: [1 2 3 4 5]fmt.Println("Shared slice:", sharedSlice)      // 输出: [1 2 3 4 5]fmt.Println("Independent slice:", independentSlice) // 输出: [1 2 3 4 5]//修改原始切片original[0] = 10fmt.Println("Modified original slice:", original) // 输出: [10 2 3 4 5]fmt.Println("Shared slice after modification:", sharedSlice) // 输出: [10 2 3 4 5]//修改独立切片independentSlice[0] = 20fmt.Println("Independent slice after modification:", independentSlice) // 输出: [20 2 3 4 5]//修改独立切片之后,原始切片不受影响fmt.Println("Original slice after independent modification:", original) // 输出: [10 2 3 4 5]fmt.Println("Shared slice after independent modification:", sharedSlice) // 输出: [10 2 3 4 5]}

代码运行结果: 

 7.切片扩容

Go 语言中的切片(slice)是一种动态数组,它允许你动态地增长和缩小。当你向切片添加元素,而切片的容量(capacity)不足以容纳更多元素时,Go 语言会自动进行扩容。扩容的具体机制如下:

  • 1.  初始容量:当你创建一个新的切片时,它会有一个初始的容量。如果你通过 make 函数创建切片,你可以指定切片的长度(length)和容量(capacity)。
  • 2.  扩容机制:当你向切片添加元素,超出当前容量时,Go 语言会进行扩容。扩容通常遵循以下规则:
  •   扩容后的容量通常是原容量的两倍,但具体增长因子可能因实现而异。
  •   如果切片的容量已经很大,扩容可能不会简单地翻倍,而是增加一个固定的数量。
  • 3.  扩容过程:扩容时,Go 语言会分配一个新的数组,并将原切片中的元素复制到新数组中。然后,新元素会被添加到新数组中。最后,切片的底层数组指针会被更新为指向新数组。
  • 4.  性能考虑:频繁的扩容可能会导致性能问题,因为每次扩容都需要分配新的内存并复制元素。为了避免这种情况,你可以通过预先分配足够的容量来减少扩容的次数。
  • 5.  手动扩容:虽然 Go 语言会自动管理切片的扩容,但你也可以手动扩容切片。例如,你可以使用 append 函数来添加元素,或者使用 copy 函数和新的切片来手动复制元素。

下面是一个简单的 Go 语言切片扩容的:

package mainimport "fmt"func main() {s := make([]int, 0, 1) // 创建一个长度为0,容量为1的切片for i := 0; i < 5; i++ {s = append(s, i)    // 向切片添加元素fmt.Printf("len=%d cap=%d slice=%v\n", len(s), cap(s), s)}
}

运行结果:

len=1 cap=1 slice=[0]
len=2 cap=2 slice=[0 1]
len=3 cap=4 slice=[0 1 2]
len=4 cap=4 slice=[0 1 2 3]
len=5 cap=8 slice=[0 1 2 3 4]

函数 append() 会智能地处理底层数组的容量增长。在切片的容量小于 1000 个元素时,总是会成倍地增加容量。一旦元素个数超过 1000,容量的增长因子会设为 1.25,也就是会每次增加 25%的容量(随着语言的演化,这种增长算法可能会有所改变)。

8.遍历切片

8.1使用for循环和索引

package mainimport "fmt"func main() {slice := []int{10, 20, 30, 40, 50}for i := 0; i < len(slice); i++ {fmt.Println("Element at index", i, "is", slice[i])}
}

8.2 使用for循环和range

使用range关键字可以同时获取索引和值,这是遍历切片的常用方法

package mainimport "fmt"func main() {slice :=[]int{10,20,30,40,50}for index,value := range slice{fmt.Printf("Index is %d and value is %d\n",index,value)}
}

8.3 使用 for 循环和 range(只获取值)

如果你不需要索引,只关心值,可以使用以下方式简化代码。

用以下方式简化代码。
package mainimport "fmt"func main() {slice := []int{10, 20, 30, 40, 50}for _, value := range slice {fmt.Println("Value is", value)}
}

8.4 使用 for 循环和 range(只获取索引)

如果你只需要索引,可以使用下划线 _ 忽略值。

package mainimport "fmt"func main() {slice := []int{10, 20, 30, 40, 50}for index, _ := range slice {fmt.Println("Index is", index)}
}

8.5 使用 for 循环和 range(遍历字符串切片)

对于字符串切片,range 会返回每个字符串中的每个字符(作为 rune 类型)。

package mainimport "fmt"func main() {slice := []string{"hello", "world"}for _, word := range slice {for _, char := range word {fmt.Printf("%c ", char)}fmt.Println()}
}

9.切片操作

9.1 使用copy函数

copy 函数是标准库 copy 包中提供的一个函数,用于将一个切片的内容复制到另一个切片中。它接受两个切片作为参数:源切片和目标切片,并返回复制的元素数量。

package mainimport "fmt"func main() {src :=[]int{1,2,3,4,5}dst :=make([]int,5)//从src复制到dstcopied :=copy(dst,src)fmt.Println("Copied element",copied)fmt.Println("Destination",dst)
}

9.2 使用 append 函数

  •   第一个参数是目标切片,用于接收新添加的元素。
  •  后续参数是要添加到切片中的元素。
  •   使用 append 进行切片复制
  • 在 append([]int(nil), original...) 中:
  •   []int(nil) 创建了一个类型为 []int 的空切片。这个切片的长度和容量都是0。
  •  original... 是 Go 语言的变参语法,它将 original 切片中的所有元素作为独立的参数传递给 append 函数。
package mainimport "fmt"func main() {original := []int{1, 2, 3, 4, 5}copy := append([]int(nil), original...)fmt.Println("Original slice:", original)fmt.Println("Copy slice:", copy)
}

在这个例子中,copy 切片是通过 append 函数创建的,它包含了 original 切片的所有元素。这种方法不仅代码简洁,而且性能也很好,因为它避免了不必要的内存分配和复制操作。
总结来说,append([]int(nil), original...) 是一种利用 append 函数的灵活性和智能内存管理来高效复制切片的方法。

9.3手动复制

你也可以通过遍历源切片并手动将每个元素赋值到新的切片中来实现复制。

package mainimport ("fmt"
)func main() {src := []int{1, 2, 3, 4, 5}dst := make([]int, len(src)) // 创建一个相同长度的新切片for i := range src {dst[i] = src[i]}fmt.Println("Source slice:", src)fmt.Println("Destination slice:", dst)
}

这种方法虽然直接,但效率较低,特别是对于大型切片。

注意事项

•  当使用 copy 函数时,确保目标切片有足够的容量来接收所有元素,否则它只会复制目标切片的容量允许的部分。
•  使用 append 函数时,虽然方便,但可能会因为扩容操作而增加额外的性能开销。
•  手动复制虽然控制更精细,但代码更复杂,且容易出错。
在实际应用中,选择哪种方法取决于具体的需求和性能考虑。对于大多数情况,copy 函数提供了一个简单而高效的方式来复制切片。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

芯片开发(1)---BQ76905---底层参数配置

主要开发思路:AFE主要是采集、保护功能、均衡&#xff0c;所以要逐一去配置芯片的寄存器 采集、均衡功能主要是配置引脚 保护功能主要是参数寄存器配置&#xff0c;至于如何使用命令修改寄存器参数该系列芯片提供了子命令和直接命令两种方式 BQ76905的管脚配置 I、参数配置 …

使用Renesas R7FA8D1BH (Cortex®-M85)和微信小程序App数据传输

目录 概述 1 系统架构 1.1 系统结构 1.2 系统硬件框架结构 1.3 蓝牙模块介绍 2 微信小程序实现 2.1 UI介绍 2.2 代码实现 3 上位机功能实现 3.1 通信协议 3.2 系统测试 4 下位机功能实现 4.1 功能介绍 4.2 代码实现 4.3 源代码文件 5 测试 5.1 编译和下载代码…

Codeforces Round 974 (Div. 3) A-F

封面原图 画师礼島れいあ 下午的ICPC网络赛的难受一晚上全都给我打没了 手速拉满再加上秒杀线段树 这场简直了啊 唯一可惜的是最后还是掉出了1000名 一把上蓝应该没啥希望了吧 A - Robin Helps 题意 侠盗罗宾因劫富济贫而闻名于世 罗宾遇到的 n n n 人&#xff0c;从 1 s …

mysqldump使用cmd窗口和powersell窗口导出sql中文乱码的问题

项目场景 我在使用Mariadb数据库更新数据的时候&#xff0c;由于数据库的表格中含有中文&#xff0c;在使用mysqldump导出sql语句的时候&#xff0c;中文显示乱码&#xff0c;如下图所示&#xff1a; 环境描述 系统&#xff1a;windows10数据库&#xff1a; Mariadb -10.6.16…

linux安装Anaconda3

先将Anaconda3安装包下载好&#xff0c;然后在主文件夹里新建一个文件夹&#xff0c;将Anaconda3安装包拖进去。 打开终端未来不出现缺东西的异常情况&#xff0c;我们先安装 yum install -bzip2然后进入根目录下&#xff0c;在进入Anaconda3文件夹下 sh包安装方式 sh Anac…

动手学深度学习(李沐)PyTorch 第 2 章 预备知识

2.1 数据操作 N维数组样例 N维数组是机器学习和神经网络的主要数据结构 张量表示一个由数值组成的数组&#xff0c;这个数组可能有多个维度。 具有一个轴的张量对应数学上的向量&#xff08;vector&#xff09;&#xff1b; 具有两个轴的张量对应数学上的矩阵&#xff08;…

S-Clustr-Simple 飞机大战:骇入现实的建筑灯光游戏

项目地址:https://github.com/MartinxMax/S-Clustr/releases Video https://www.youtube.com/watch?vr3JIZY1olro 飞机大战 这是一个影子集群的游戏插件&#xff0c;可以将游戏画面映射到现实的设备&#xff0c;允许恶意控制来完成游戏。亦或者设备部署在某建筑物中,来控制…

2024年中国研究生数学建模竞赛A题“风电场有功功率优化分配”全析全解

问题一&#xff1a; 针对问题一&#xff0c;可以采用以下低复杂度模型&#xff0c;来计算风机主轴及塔架的疲劳损伤累积程度。 建模思路&#xff1a; 累积疲劳损伤计算&#xff1a; 根据Palmgren-Miner线性累积损伤理论&#xff0c;元件的疲劳损伤可以累积。因此&#xff0c;…

Android-UI设计

控件 控件是用户与应用交互的元素。常见的控件包括&#xff1a; 按钮 (Button)&#xff1a;用于执行动作。文本框 (EditText)&#xff1a;让用户输入文本。复选框 (CheckBox)&#xff1a;允许用户选择或取消选择某个选项。单选按钮 (RadioButton)&#xff1a;用于在多个选项中…

分享两道算法题

分享两道算法题 王者荣耀分组 题目描述 部门准备举办一场王者荣耀表演赛&#xff0c;有 10 名游戏爱好者参与&#xff0c;分 5 为两队&#xff0c;每队 5 人。 每位参与者都有一个评分&#xff0c;代表着他的游戏水平。 为了表演赛尽可能精彩&#xff0c;我们需要把 10 名参赛…

leetcode练习 二叉树的最大深度

给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;3提示&#xff1a; 树中节点的数量在 [0, 104] 区间内。-100 …

RabbitMQ08_保证消息可靠性

保证消息可靠性 一、生产者可靠性1、生产者重连机制&#xff08;防止网络波动&#xff09;2、生产者确认机制Publisher Return 确认机制Publisher Confirm 确认机制 二、MQ 可靠性1、数据持久化交换机、队列持久化消息持久化 2、Lazy Queue 惰性队列 三、消费者可靠性1、消费者…

Springboot 文件上传下载相关问题

文章目录 关于Springboot 文件上传下载问题解决方案注意事项文件上传文件下载文件删除文件在线打开在写练习的时候&#xff0c;发现了一些小小的问题&#xff0c;已经在 上述代码中体现。① 代码路径碰到中文的时候&#xff0c;会有乱码&#xff0c;需要转换&#xff08;内容中…

华润电力最新校招社招润择认知能力测评:逻辑推理数字计算语言理解高分攻略

​ 尊敬的求职者们&#xff0c; 在您准备加入华润电力这个大家庭之前&#xff0c;了解其招聘测评的详细流程和要求是至关重要的。以下是我们为您整理的测评系统核心内容&#xff0c;希望对您的求职之旅有所帮助。 测评系统概览 华润电力的招聘测评系统旨在全面评估求职者的认…

【WEB】序列一下

1、 2、反序列化 <?phpclass Polar{public $url polarctf.com;public $ltsystem;public $bls /;function __destruct(){$a $this->lt;$a($this->b);} }$a new Polar(); echo serialize($a); ?>###O:5:"Polar":3:{s:3:"url";s:12:"…

CSS 布局三大样式简单学习

目录 1. css 浮动 1.1 效果1 1.2 效果2 1.3 效果3 1.4 效果4 2. css 定位 2.1 absolute 2.2 relative 2.3 fixed 3. css 盒子模型 3.1 效果1 3.2 效果2 3.3 效果3 3.4 效果4 1. css 浮动 1.1 效果1 1.2 效果2 1.3 效果3 1.4 效果4 2. css 定位 2.1 absolute 2.2 …

AI 时代的网络危机沟通计划

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

吉首大学--23级题目讲解

7-1 单链表基本操作 在 C/C 中&#xff0c;.&#xff08;点&#xff09;和 ->&#xff08;箭头&#xff09;运算符用于访问结构体或类的成员&#xff0c;但它们的使用场景不同。 1. . 运算符 . 运算符用于访问结构体或类的成员&#xff0c;通过对象或结构体变量直接访问。…

51单片机——独立按键

一、独立按键对应单片机P3管脚&#xff0c;如图 二、按键点亮LED灯 #include <STC89C5xRC.H> void main() { while(1) { if(P300) { P200; } else { P201; } } } 当按键为0时&#xff0c;代表按下&#xff0c;所以当P30按下时&#xff0c;让P20&#xff1d;0&#…

[产品管理-32]:NPDP新产品开发 - 30 - 文化、团队与领导力 - 领导力与团队的可持续发展

目录 一、团队领导的领导力 1.1 领导力 1、领导力的定义 2、领导力的重要性 3、领导力的构成要素 4、如何提升领导力 1.2 情商 二、虚拟团队 1、团队定义与特征 2、团队优势 3、团队挑战与应对策略 三、可持续发展 四、团队管理和领导力中的度量指标 4.1 激励创新…