go 切片(slice)原理及用法注意事项

news2024/10/2 14:35:58

切片(slice)定义

go语言中的slice是一种数据结构,其定义为一个结构体,如下所示;

type SliceHeader struct {
	Data uintptr // 指向底层数组的指针
	Len  int // 切片的长度
	Cap  int // 切片的容量
}

切片与数组

  • 切片的底层数据存储结构是 数组
  • 切片较为灵活,能动态扩容,而数组是定长的,长度确定后无法更改;
  • 数组中的 数据长度是数组类型的一部分; 在参数定义传参时较为局限;而 切片没有此问题;

切片与底层数组的关系及影响

  • 根据切片的结构体可以看出 切片的数据存储在数组上,而切片和底层数组之间是用 指针相关联;
  • 就是因为 这个指针关联 的特性会造成 切片间数据的相互影响,因为其操作的都是同一个底层数组;
package main

import "fmt"

func main() {
	s := []int{1, 2, 3, 4}
	s1 := s[1:3]
	fmt.Println(s, s1) // [1 2 3 4] [2 3]
	s1[0] = 22
	fmt.Println(s, s1) //s1的操作影响到了s的结果 [1 22 3 4] [22 3]
	s[2] = 33
	fmt.Println(s, s1) //s的操作影响到了s1的结果 [1 22 33 4] [22 33]
}

切片的扩容过程及切片间的影响

  • 切片的扩容发生在底层数组长度不够存放新数据时,才触发扩容,每次扩容大小的机制在go语言不同版本有所不同,基本 都是先扩容到原来的2倍(原容量不大时,较大时扩容倍数小些),然后再进行内存对齐后获得最终的扩容大小;
  • 扩容会额外预分配一定的空间,防止每次新增数据都触发扩容, 每次内存的copy太消耗资源;
  • 扩容 过程为 新建一个计算好大小的数组,将原切片中的数据(不是原数组的数据)复制到新数组中,然后将这次新增的数据添加进去,再 将新数组的地址赋值给 切片结构体的 数组指针字段;
  • 基于如上扩容特点,可知 扩容后 切片间的操作不再互相影响,如下示例所示;

package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

//
//  @Description: 获取切片对应底层数组的指针
//  @param s:
//  @return unsafe.Pointer:
//
func BottomLayerArrayPoint(s *[]int) unsafe.Pointer {
	hdr := (*reflect.SliceHeader)(unsafe.Pointer(s))
	return unsafe.Pointer(hdr.Data)
}

func main() {
	s := []int{1, 2, 3, 4}
	s1 := s[1:3]
	fmt.Println(s, s1) // [1 2 3 4] [2 3]
	s1[0] = 22
	fmt.Println(s, s1) //s1的操作影响到了s的结果 [1 22 3 4] [22 3]
	s[2] = 33
	fmt.Println(s, s1) //s的操作影响到了s1的结果 [1 22 33 4] [22 33]

	fmt.Println("扩容前底层数组的容量", cap(s1), "s1:", s1, "s1底层数组的指针", BottomLayerArrayPoint(&s1), "s1底层数组的值", *(*[3]int)(BottomLayerArrayPoint(&s1)))
	// 对s1新增数据,底层数组长度不够,触发扩容,创建新的数组,并将s1的数组指针指向新的数组,所以 后续 对 s1的操作不会再影响到s;因为2者的底层数据不同了;
	s1 = append(s1, 5, 6)
	fmt.Println("扩容后底层数组的容量", cap(s1), "s1:", s1, "s1底层数组的指针", BottomLayerArrayPoint(&s1), "s1底层数组的值", *(*[6]int)(BottomLayerArrayPoint(&s1)))
	s1[0] = 222
	// 发现s1的操作不会再影响到s;
	fmt.Println(s, s1)
}

在这里插入图片描述

append操作时切片和底层数组的执行过程

append函数分析

func append(slice []Type, elems ...Type) []Type
  • 特别注意: 此函数入参有一个slice,出参返回一个新的slice,也就是 这个新的slice的结构体中的 指向底层数组的指针可能会换成新的,且 容量,长度字段都可能会变化;
  • 另外, append入参slice是 值传递,也就是 函数内部 会新建一个slice结构体,只是这个结构体每个字段的值和入参slice相同而已,但是 也因为这个特点 导致 执行底层数组的指针也相同,导致 append操作可能会操作相同的底层数组(除非触发扩容);
  • 至于 将出参返回的新slice赋值给 入参切片变量(s=append(s,1)) 还是新的切片变量(s1=append(s,1)),就会产生不同的结果;

append是否触发扩容的不同结果

  • 触发扩容时, 新建的数组与 原数组没任何关系
  • 未触发扩容时,对原数组的操作 会影响其他切片

是否扩容的示例代码

package main

import "fmt"

func main() {
	s := []int{5, 7}
	//s := []int{5}
	fmt.Println("s值", s, "s容量", cap(s))
	s = append(s, 9)
	fmt.Println("s值", s, "s容量", cap(s))
	y := append(s, 12)
	fmt.Println("y值", y, "y容量", cap(y))
	y3 := append(s, 15, 15, 15)
	fmt.Println("y3值", y3, "y3容量", cap(y3))
	y4 := append(s, 16)
	fmt.Println("y4值", y4, "y4容量", cap(y4))
	fmt.Println("s值与容量", s, cap(s), ";y值与容量", y, cap(y), ";y3值与容量", y3, cap(y3), ";y4值与容量", y4, cap(y4))
}

代码执行结果

在这里插入图片描述

切片作为函数入参的执行过程及注意事项

  • 切片作为入参时,也是值传递方式,也就是 函数内部 会新建一个slice结构体,只是这个结构体每个字段的值和入参slice相同而已,但是 也因为这个特点 导致 执行底层数组的指针也相同,导致对数组每个元素的操作会影响到外部变量; append操作则可能会操作相同的底层数组(除非触发扩容);

示例代码


package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

//
//  @Description: 获取切片对应底层数组的指针
//  @param s:
//  @return unsafe.Pointer:
//
func BottomLayerArrayPoint(s *[]int) unsafe.Pointer {
	hdr := (*reflect.SliceHeader)(unsafe.Pointer(s))
	return unsafe.Pointer(hdr.Data)
}

func main() {
	s := make([]int, 1, 1)
	s[0] = 1
	//s := []int{1, 1}
	fmt.Println("s值为", s, "s地址为", unsafe.Pointer(&s), "s底层数组地址为", BottomLayerArrayPoint(&s),"s底层数组值为",*(*[2]int)(BottomLayerArrayPoint(&s)), "s容量为", cap(s))
	f(s)
	fmt.Println("s值为", s, "s地址为", unsafe.Pointer(&s), "s底层数组地址为", BottomLayerArrayPoint(&s),"s底层数组值为",*(*[2]int)(BottomLayerArrayPoint(&s)), "s容量为", cap(s))
}

func f(s []int) {
	fmt.Println("函数内的s相关 在改变切片元素值前","s值为", s, "s地址为", unsafe.Pointer(&s), "s底层数组地址为", BottomLayerArrayPoint(&s),"s底层数组值为",*(*[2]int)(BottomLayerArrayPoint(&s)), "s容量为", cap(s))
	// 改变切片中 元素的值
	for i := range s {
		s[i] += 1
	}
	fmt.Println("函数内的s相关 在改变切片元素值后","s值为", s, "s地址为", unsafe.Pointer(&s), "s底层数组地址为", BottomLayerArrayPoint(&s),"s底层数组值为",*(*[2]int)(BottomLayerArrayPoint(&s)), "s容量为", cap(s))
	s = append(s, 3)
	fmt.Println("函数内的s相关 在append新数据后","s值为", s, "s地址为", unsafe.Pointer(&s), "s底层数组地址为", BottomLayerArrayPoint(&s),"s底层数组值为",*(*[2]int)(BottomLayerArrayPoint(&s)), "s容量为", cap(s))
}

代码执行结果

在这里插入图片描述

其他注意事项

遍历slice时修改slice

s := []int{1, 2, 3}
//方法一: 修改失败,v是s元素的拷贝,并不会影响到元素v本身
for _, v := range s {
	v = v + 1
}
//方法二: 修改成功
for i, v := range s {
	sli[i] = v + 1
}

切片的容量

package main

import "fmt"

func main() {
	a := []int{1, 2, 3, 4, 5}
	//容量为 包括索引为1到尾部的所有数据个数
	b := a[1:2]
	fmt.Println(b, cap(b)) // [2] 4   
	// 容量为 包括索引1到 不包括索引为3的数据个数
	c := a[1:2:3]
	fmt.Println(c, cap(c)) // [2] 2
}

总结

  • 切片存储数据是在底层数组中,而切片和底层数组用 指针关联,因为这个特性会造成 切片间 扩容前的操作相互影响;
  • 函数参数类型为切片时,是值引用传递,函数内部新建的切片进行赋值时,由于 切片和底层数组时指针关联,同样会造成 切片的扩容前的操作会影响到 函数外的切片;
  • append方法 返回的是新slice,扩容前会影响入参的slice结果,扩容后,不再影响入参的slice结果;
  • 只要理解了 切片和底层数组直接的指针关联关系,遇到问题 就看是否扩容 就好解决;

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

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

相关文章

vue2使用v-viewer实现图片预览ImagePreview

追溯&#xff1a; View UI Plus 是 View Design 设计体系中基于 Vue.js 3 的一套 UI 组件库&#xff0c;里面有个组件ImagePreview可以实现“图片预览”。 使用ImagePreview组件&#xff0c;报错&#xff1a; [Vue warn]: Unknown custom element: <ImagePreview> - d…

odoo15 标题栏自定义

odoo15 标题栏自定义 如何显示为自定义呢 效果如下: 代码分析: export class WebClient extends Component {setup() {this.menuService = useService("menu");this.actionService = useService("action");this.title = useService("title&…

在Docker 上完成对Springboot+Mysql+Redis的前后端分离项目的部署(全流程,全截图)

本文章全部阅读大约2小时&#xff0c;包含一个完整的springboot vue mysqlredis前后端分离项目的部署在docker上的全流程&#xff0c;比较复杂&#xff0c;请做好心理准备&#xff0c;遇到问题可留言或则私信 目录 1 安装Docker&#xff0c;以及简单使用参照 2 Docker部署m…

HOT100--(3)无重复字符的最长子串

点击查看题目详情 大思路&#xff1a; 创建哈希表&#xff0c;元素类型为<char, int>&#xff0c;分别是字符与其对应下标 用哈希表来存储未重复的子串&#xff0c;若有重复则记录下当前子串最大值maxhashsize 并且开始以相同方法记录下一子串 遍历完成以后&#xff0c…

Android OpenCV(七十三):吊打高斯模糊的StackBlur Android 实践

前言 OpenCV 4.7.0 2022年12月28日Release,ChangeLog中提到 Stackblur algorithm implementation. Stackblur是一种高斯模糊的快速近似,由Mario Klingemann发明。其计算耗时不会随着kernel size的增大而增加,专为大kernel size的模糊滤波场景量身定制。 使用建议:当kerne…

[RDMA-高级计算机网络report] Congestion Control for Large-Scale RDMA Departments

本文主要解决的问题是在RoCEv2体系中&#xff0c;基于优先级的拥塞控制PFC是一种粗粒度的机制。 它在端口&#xff08;或端口加优先级&#xff09;级别上运行&#xff0c;并且不区分流。PAUSE机制是基于每个端口&#xff08;和优先级&#xff09;的&#xff0c;而不是基于每个流…

mysql数据库之索引使用原则

一、最左前缀法则。 1、如果索引使用了多列&#xff08;联合索引&#xff09;&#xff0c;要遵守最左前缀法则。最左前缀法则指的是查询从索引的最左列开始&#xff0c;并且不跳过索引中的列。 如果跳跃到某一列&#xff0c;索引将部分失效&#xff08;后面的字段索引失效&am…

springboot启动时遇见的版本不同、无法启动、自动停止问题解决方案

Springboot项目启动失败初来乍到&#xff0c;听说springboot很好用&#xff0c;很简便&#xff0c;于是爱搞事情的我就打算试试&#xff0c;因为最近在找工作&#xff0c;很多软件开发的也要求springboot的使用&#xff0c;于是我就开启了springboot的学习之旅&#xff0c;打算…

Vue3 企业级项目实战:认识 Spring Boot

Vue3 企业级项目实战 - 程序员十三 - 掘金小册Vue3 Element Plus Spring Boot 企业级项目开发&#xff0c;升职加薪&#xff0c;快人一步。。「Vue3 企业级项目实战」由程序员十三撰写&#xff0c;2744人购买https://s.juejin.cn/ds/S2RkR9F/ 越来越流行的 Spring Boot Spr…

人工智能及其应用(蔡自兴)期末复习

人工智能及其应用&#xff08;蔡自兴&#xff09;期末复习 相关资料&#xff1a; 人工智能期末复习 人工智能复习题 人工智能模拟卷 人工智能期末练习题 1 ⭐️绪论 人工智能&#xff1a;人工智能就是用人工的方法在机器&#xff08;计算机&#xff09;上实现的智能&#xff0…

攻不下dfs不参加比赛(八)

标题 为什么练dfs题目重点为什么练dfs 相信学过数据结构的朋友都知道dfs(深度优先搜索)是里面相当重要的一种搜索算法,可能直接说大家感受不到有条件的大家可以去看看一些算法比赛。这些比赛中每一届或多或少都会牵扯到dfs,可能提到dfs大家都知道但是我们为了避免眼高手低有…

2.2 数据库的常用操作

文章目录1.分类2.创建数据库3.删除数据库4.查看所有数据库5.备份数据库6.数据库维护7.数据库使用与结构7.1 数据库的使用7.2 数据库结构虽然我们已经安装了可视化视图软件&#xff0c;但前期为了熟悉管理命令行的操作&#xff0c;我们暂时先在管理命令窗口进行操作&#xff1a;…

关于.bashrc和setup.bash的理解

在创建了ROS的workspace后&#xff0c;需要将workspace中的setup.bash文件写入~/.bashrc 文件中&#xff0c;让其启动&#xff1a; source /opt/ros/melodic/setup.bash这句话的目的就是在开新的terminal的时候&#xff0c;运行这个setup.bash&#xff0c;而这个setup.bash的作…

【2373. 矩阵中的局部最大值】

来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 给你一个大小为 n x n 的整数矩阵 grid 。 生成一个大小为 (n - 2) x (n - 2) 的整数矩阵 maxLocal &#xff0c;并满足&#xff1a; maxLocal[i][j] 等于 grid 中以 i 1 行和 j 1 列为中心的 3 …

搭建私人《我的世界》服务器,使用Cpolar内网穿透更简单

文章目录1.前言2.本地服务器搭建2.1 设置环境变量2.2 进行《我的世界》服务器端设置2.3 测试和使用3.本地MC服务器的内网穿透3.1.Cpolar云端设置3.2.Cpolar本地设置3.3.测试和使用4.结语1.前言 要说去年游戏圈的重磅大瓜&#xff0c;想必网易和暴雪的分家必能上榜。虽然两家大…

元宇宙对体育运动意味着什么?

欢迎来到Hubbleverse &#x1f30d; 关注我们 关注宇宙新鲜事 &#x1f4cc; 预计阅读时长&#xff1a;8分钟 本文仅代表作者个人观点&#xff0c;不代表平台意见&#xff0c;不构成投资建议。 Facebook将其品牌重塑为“Meta”&#xff0c;“元”的概念推向了主流&#xff…

如何退出PPT文件的“只读模式”?

PPT文件设置了“只读模式”&#xff0c;打开文件就会弹出对话框&#xff0c;提示【输入密码以修改或以只读方式打开】。 如果有密码&#xff0c;输入密码后可以正常修改编辑PPT&#xff0c;但下次再打开时仍旧会出现提示框&#xff1b;如果没有密码&#xff0c;选择【只读】可…

第十五届(2022年)山东省职业院校技能大赛高职组信息安全管理与评估竞赛试题

第十五届&#xff08;2022年&#xff09;山东省职业院校技能大赛高职组信息安全管理与评估 竞赛试题 第一阶段竞赛项目试题 根据信息安全管理与评估技术文件要求&#xff0c;第一阶段为网络平台搭建与网络安全设备配置与防护。本文件为信息安全管理与评估项目竞赛-第一阶段试题…

【正点原子FPGA连载】第二十一章AXI DMA环路测试 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

1&#xff09;实验平台&#xff1a;正点原子MPSoC开发板 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id692450874670 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html 第二十一章AXI D…

全网资料最全Java数据结构与算法-----算法分析

算法分析 研究算法的最终目的就是如何花更少的时间&#xff0c;如何占用更少的内存去完成相同的需求&#xff0c;并且也通过案例演示了不同算法之间时间耗费和空间耗费上的差异&#xff0c;但我们并不能将时间占用和空间占用量化&#xff0c;因此&#xff0c;接下来我们要学习…