Go 语言 slice(切片) 的使用

news2025/5/13 11:27:52

序言

 在许多开发语言中,动态数组是必不可少的一个组成部分。在实际的开发中很少会使用到数组,因为对于数组的大小大多数情况下我们是不能事先就确定好的,所以他不够灵活。动态数组通过提供自动扩容的机制,极大地提升了开发效率。这篇文章将介绍 Go 语言中的动态数组 — slice(切片)


1. 数据结构

 切片的组成如下, 每一个字段的含义如下:

  • Data:指向存储元素数组的指针;
  • Len:该数组中元素的个数
  • Cap:该数组的容量大小
type SliceHeader struct {
	Data uintptr
	Len  int
	Cap  int
}

如果你之前了解过 C++ 中的 vector 你会发现其实他们的思路是一样的。一个实际存储元素的切片如下:在这里插入图片描述


2. 切片的初始化

a. 声明但不初始化

 在 Go 语言中,如果你声明一个切片但不初始化它,它的默认值是 nil。这意味着该切片没有指向任何底层数组,长度和容量都为 0:

func main() {
	var slice []int

	sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&slice))
	dataPtr := unsafe.Pointer(sliceHeader.Data)
	fmt.Printf("data = %v, len = %d, cap = %d\n", dataPtr, len(slice), cap(slice))
}

这里程序的输出是:

data = , len = 0, cap = 0

b. 带初始值初始化

 比起第一种方式,这个会在声明的时候带上字面值来初始化一个切片:

slice := []int{1, 2, 3} 
fmt.Printf("data = %v, len = %d, cap = %d\n", slice, len(slice), cap(slice))

此时,该切片的 lencap 会和元素数量保持一致,程序输出:

data = [1 2 3], len = 3, cap = 3

c. 使用 make 初始化

 使用 make 来初始化一个切片也有两者方式,首先是第一种:

slice := make([]int, 5)
fmt.Printf("data = %v, len = %d, cap = %d\n", slice, len(slice), cap(slice))

这代表创建一个切片,并且切片的 lencap 都是 5,切片的元素的值采用该类型的默认值:

data = [0 0 0 0 0], len = 5, cap = 5

第二种是将 lencap 分别赋值:

slice := make([]int, 2, 4)
fmt.Printf("data = %v, len = %d, cap = %d\n", slice, len(slice), cap(slice))

这代表创建一个切片,并且切片的len 是 2,cap 是 4:

data = [0 0], len = 2, cap = 4

这也是最常用的方式,使用 make 来预先分配内存大小可以避免后续添加元素时频繁进行扩容操作!

d. 下标索引初始化

Go 支持指定一个索引范围来初始化一个切片,这是 C++vector 所不具备的能力,举个例子:

arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4]
fmt.Println(slice)   // [2 3 4]

这里有一个长度为 5 的数组,现在使用索引范围 [1, 4) 「左闭右开」 来初始化一个切片,甚至还可以这样表达:

slice = arr[:4]   // 等价于 [0:4]
slice = arr[1:]   // 等价于 [1:len(arr) - 1]

现在有一个问题,使用索引初始化的切片和原数组是什么关系呢?换句话说这里是否涉及到了深拷贝呢?上代码:

arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4] // [2, 3, 4]
slice[0] = 0 // 修改值
fmt.Printf(“arr=%v\n”, arr)
fmt.Printf(“slice=%v\n”, slice)

输出结果是:

arr=[1 0 3 4 5]
slice=[0 3 4]

上文中我们了解到了一个 slice 的结构是怎么样的,结合输出的结果,不难推断出 data 指针指向了该数组的第二个位置,如下:
在这里插入图片描述
这里 cap 的大小为什么是 4 怎么得到的呢 — cap = cap(arr) - 1


3. 切片的追加和扩容

a. 元素追加

 我们可以通过 append 操作来在切片最后追加元素,追加方式也有多种,举个栗子:

slice := []int{1, 2}
slice = append(slice, 3)       // 追加一个元素
slice = append(slice, 4, 5, 6) // 追加多个元素
slice = append(slice, []int{7, 8}...) // 追加一个切片,...表示解包,不能省略

对于追加的操作,大家是否存在疑惑的点呢?我刚开始就不理解为什么在追加操作后对 slice 进行赋值的操作。这是因为 append 函数有一个重要的特性需要特别注意:它可能会返回一个新的底层数组(取决于是否进行扩容操作)。如果没有进行赋值操作,那么 slice 还是指向原来的数组,举个栗子:

在这里插入图片描述

b. 切片扩容

 当切片的 len 等于 cap 时,在下一次 append 操作前就会进行一次扩容操作,扩容的逻辑如下:

func growslice(et *_type, old slice, cap int) slice {
	...
	newcap := old.cap
	doublecap := newcap + newcap
	if cap > doublecap {
		newcap = cap
	} else {
		if old.len < 1024 {
			newcap = doublecap
		} else {
			for 0 < newcap && newcap < cap {
				newcap += newcap / 4
			}
			if newcap <= 0 {
				newcap = cap
			}
		}
	}
	...
}

扩容的策略总结如下:
在这里插入图片描述
可以看到 Go 语言增长容量的策略还是比较缓和的。


4. 切片易踩的坑

a. 参数传递类型傻傻分不清

 首先,我们先聊聊 C++ 当中的值传递和引用传递,就比如:

int main() {
	vector<int> vec = { 1, 2, 3, 4, 5 }
	funcJustForRead(vec)
	return 0;
}

void funcJustForRead(vector<int> &vec) {
	...
}

对于某些只读的场景,我们一般会传引用,这样就大大减少了拷贝带来的开销。在 Go 语言中好像并没有 引用 的概念?但是仔细思考一下,Go 真的需要吗:

func main() {
	slice := []int{ 1, 2, 3, 4, 5 }
	funcJustForRead(slice)
}

func funcJustForRead(slice []int) {
	...
}

形参是实参的拷贝,slice 中指向元素的是 data 指针,即使形参和实参的 data 不一样,但是两者是指向的同一个数组,所以不需要引用。
 现在,这里有一个函数会对切片进行追加操作,我依然是值传递是否还是可行呢?举个栗子(假设这里不涉及扩容操作):

func main() {
	slice := []int{1, 2}
	fmt.Println(slice)
	funcForAppend(slice)
	fmt.Println(slice)
}

func funcForAppend(slice []int) {
	slice = append(slice, 3)
}

输出结果是:

[1 2]
[1 2]

并没有预想的新增一个值,为什么?上面我们介绍了,append 会返回一个新的切片,我们在 main 中使用的还是原来的切片。怎么解决呢?传递指针:

func main() {
	slice := make([]int, 0, 2)
	fmt.Println(slice)
	funcForAppend(&slice)
	fmt.Println(slice)
}

func funcForAppend(slice *[]int) {
	*slice = append(*slice, 3)
}

b. len 和 cap 傻傻分不清

 之前我们谈到过,可以预先分配好空间,可以避免后续的频繁扩容操作,但是是否会有以下的误解呢:

func main() {
	slice := make([]int, 5)
	slice = append(slice, 1)
	slice = append(slice, 1)
	slice = append(slice, 1)
	slice = append(slice, 1)
	fmt.Println(slice) // [0 0 0 0 0 1 1 1 1]
}

这里代表预先分配好 5 个空间,并且每一个空间使用该类型的默认值填充,当我们新加入元素时,是在已有的基础上往后添加而不是从前开始覆盖。正确的姿势应该是这样子的:

func main() {
	slice := make([]int, 0, 5)
	slice = append(slice, 1)
	slice = append(slice, 1)
	slice = append(slice, 1)
	slice = append(slice, 1)
	fmt.Println(slice) // [1 1 1 1]
}

5. 总结

 不仅只是会使用,并且知其所以然。我自认为这是非常重要的,这不仅能够很大程度上减小我们在开发中犯错的概念,还能够有效提升代码的质量。所以通过这篇 silce 带我们走入 Go 的世界吧。

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

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

相关文章

js常用的数组遍历方式

以下是一个完整的示例&#xff0c;将包含图片、文字和数字的数组渲染到 HTML 页面&#xff0c;使用 ​多种遍历方式​ 实现不同的渲染效果&#xff1a; 1. 准备数据&#xff08;数组&#xff09; const items [{ id: 1, name: "苹果", price: 5.99, image: "h…

【网络编程】五、三次握手 四次挥手

文章目录 Ⅰ. 三次握手Ⅱ. 建立连接后的通信Ⅲ. 四次挥手 Ⅰ. 三次握手 ​ 1、首先双方都是处于未通信的状态&#xff0c;也就是关闭状态 CLOSE。 ​ 2、因为服务端是为了服务客户端的&#xff0c;所以它会提前调用 listen() 函数进行对客户端请求的监听。 ​ 3、接着客户端就…

从 AGI 到具身智能体:解构 AI 核心概念与演化路径全景20250509

&#x1f916; 从 AGI 到具身智能体&#xff1a;解构 AI 核心概念与演化路径全景 作者&#xff1a;AI 应用实践者 在过去的几年中&#xff0c;AI 领域飞速发展&#xff0c;从简单的文本生成模型演进为今天具备复杂推理、感知能力的“智能体”系统。本文将从核心概念出发&#x…

Docker Compose 的历史和发展

这张图表展示了Docker Compose从V1到V2的演变过程&#xff0c;并解释了不同版本的Compose文件格式及其支持情况。以下是对图表的详细讲解&#xff1a; Compose V1 No longer supported: Compose V1已经不再支持。Compose file format 3.x: 使用了版本3.x的Compose文件格式。 …

从 JIT 即时编译一直讲到CGI|FastGGI|WSGI|ASGI四种协议的实现细节

背景 我一度理解错了这个东西&#xff0c;之前没有AI的时候&#xff0c;也没深究过&#xff0c;还觉得PHP8支持了常驻内存的运行的错误理解&#xff0c;时至今日再来看这个就很清晰了。 另外&#xff0c;早几年对以上4个协议&#xff0c;我也没搞懂&#xff0c;时至今日&…

CSS3 遮罩

在网页设计中&#xff0c;我们经常需要实现一些特殊的视觉效果来增强用户体验。CSS3 遮罩&#xff08;mask&#xff09;允许我们通过控制元素的可见区域来创建各种精美的视觉效果。本文将带你全面了解 CSS3 遮罩的功能和应用。 什么是 CSS3 遮罩&#xff1f; CSS3 遮罩是一种…

ResNet残差神经网络的模型结构定义(pytorch实现)

ResNet残差神经网络的模型结构定义&#xff08;pytorch实现&#xff09; ResNet‑34 ResNet‑34的实现思路。核心在于&#xff1a; 定义残差块&#xff08;BasicBlock&#xff09;用 _make_layer 方法堆叠多个残差块按照 ResNet‑34 的通道和层数配置来搭建网络 import torch…

uniapp|商品列表加入购物车实现抛物线动画效果、上下左右抛入、多端兼容(H5、APP、微信小程序)

以uniapp框架为基础,详细解析商品列表加入购物车抛物线动画的实现方案。通过动态获取商品点击位置与购物车坐标,结合CSS过渡动画模拟抛物线轨迹,实现从商品图到购物车图标的动态效果。 目录 核心实现原理坐标动态计算抛物线轨迹模拟​动画元素控制代码实现详解模板层设计脚本…

谈AI/OT 的融合

过去的十几年间&#xff0c;工业界讨论最多的话题之一就是IT/OT 融合&#xff0c;现在&#xff0c;我们不仅要实现IT/OT 的融合&#xff0c;更要面向AI/OT 的融合。看起来不太靠谱&#xff0c;却留给我们无限的想象空间。OT 领域的专家们不要再当“九斤老太”&#xff0c;指责这…

USB传输模式

USB有四种传输模式: 控制传输, 中断传输, 同步传输, 批量传输 1. 中断传输 中断传输一般用于小批量, 非连续的传输. 对实时性要求较高. 常见的使用此传输模式的设备有: 鼠标, 键盘等. 要注意的是, 这里的 “中断” 和我们常见的中断概念有差异. Linux中的中断是设备主动发起的…

.NET10 - 尝试一下Open Api的一些新特性

1.简单介绍 .NET9中Open Api有了很大的变化&#xff0c;在默认的Asp.NET Core Web Api项目中&#xff0c;已经移除了Swashbuckle.AspNetCore package&#xff0c;同时progrom中也变更为 builder.Servers.AddOpenApi() builder.Services.MapOpenApi() 2025年微软将发布…

RabbitMQ 工作模式

RabbitMQ 一共有 7 中工作模式&#xff0c;可以先去官网上了解一下&#xff08;一下截图均来自官网&#xff09;&#xff1a;RabbitMQ 官网 Simple P&#xff1a;生产者&#xff0c;要发送消息的程序&#xff1b;C&#xff1a;消费者&#xff0c;消息的接受者&#xff1b;hell…

基于C++的多线程网络爬虫设计与实现(CURL + 线程池)

在当今大数据时代&#xff0c;网络爬虫作为数据采集的重要工具&#xff0c;其性能直接决定了数据获取的效率。传统的单线程爬虫在面对海量网页时往往力不从心&#xff0c;而多线程技术可以充分利用现代多核CPU的计算能力&#xff0c;显著提升爬取效率。本文将详细介绍如何使用C…

【日撸 Java 三百行】Day 11(顺序表(一))

目录 Day 11&#xff1a;顺序表&#xff08;一&#xff09; 一、关于顺序表 二、关于面向对象 三、代码模块分析 1. 顺序表的属性 2. 顺序表的方法 四、代码及测试 拓展&#xff1a; 小结 Day 11&#xff1a;顺序表&#xff08;一&#xff09; Task&#xff1a; 在《数…

软考 系统架构设计师系列知识点之杂项集萃(55)

接前一篇文章&#xff1a;软考 系统架构设计师系列知识点之杂项集萃&#xff08;54&#xff09; 第89题 某软件公司欲开发一个Windows平台上的公告板系统。在明确用户需求后&#xff0c;该公司的架构师决定采用Command模式实现该系统的界面显示部分&#xff0c;并设计UML类图如…

保持Word中插入图片的清晰度

大家有没有遇到这个问题&#xff0c;原本绘制的高清晰度图片&#xff0c;插入word后就变模糊了。先说原因&#xff0c;word默认启动了自动压缩图片功能&#xff0c;分享一下如何关闭这项功能&#xff0c;保持Word中插入图片的清晰度。 ①在Word文档中&#xff0c;点击左上角的…

Linux复习笔记(三) 网络服务配置(web)

遇到的问题&#xff0c;都有解决方案&#xff0c;希望我的博客能为你提供一点帮助。 二、网络服务配置 2.3 web服务配置 2.3.1通信基础&#xff1a;HTTP协议与C/S架构&#xff08;了解&#xff09; ​​HTTP协议的核心作用​​ Web服务基于HTTP/HTTPS协议实现客户端&#xff…

springboot旅游小程序-计算机毕业设计源码76696

目 录 摘要 1 绪论 1.1研究背景与意义 1.2研究现状 1.3论文结构与章节安排 2 基于微信小程序旅游网站系统分析 2.1 可行性分析 2.1.1 技术可行性分析 2.1.2 经济可行性分析 2.1.3 法律可行性分析 2.2 系统功能分析 2.2.1 功能性分析 2.2.2 非功能性分析 2.3 系统…

uniapp自定义导航栏搭配插槽

<uni-nav-bar dark :fixed"true" shadow background-color"#007AFF" left-icon"left" left-text"返回" clickLeft"back"><view class"nav-bar-title">{{ navBarTitle }}</view><block v-slo…

MFC listctrl修改背景颜色

在 MFC 中修改 ListCtrl 控件的行背景颜色&#xff0c;需要通过自绘&#xff08;Owner-Draw&#xff09;机制实现。以下是详细的实现方法&#xff1a; 方法一&#xff1a;通过自绘&#xff08;Owner-Draw&#xff09;实现 步骤 1&#xff1a;启用自绘属性 在对话框设计器中选…