深度解析Go语言中的Slice切片

深度解析Go语言中的Slice切片

  • 一、 简介
  • 二、数据结构
  • 三、初始化
  • 四、内容截取
  • 五、切片扩容
  • 六、元素删除
  • 七、切片拷贝


一、 简介

go中的切片,在某种程度上相当于别的语言中的“数组”。不同点在于切片的长度和容量是可变的,在使用过程中可以进行扩容。

二、数据结构

type slice struct {array unsafe.Pointerlen   intcap   int
}

这就是切片定义的底层源代码,非常简洁

array :指向切片引用的底层数组,由Go运行时使用unsafe.Pointer管理,允许切片中的任何类型元素。

len:这是切片的长度,代表它包含的元素数量。

cap:这是切片的容量,即在需要分配新的底层数组之前,切片可以容纳的元素的最大数量。

由此我们不难发现,切片内部如果储存数据,还是靠指向底层数组的指针实现的,所以,如果传递切片,那么进行的就是引用传递操作了

三、初始化

初始化可以有以下形式

	// 声明但不初始化var a []int// 基于 make 进行初始化 len = cap = 10b := make([]int, 10)// 基于 make 进行初始化 len = 10 cap = 20c := make([]int, 10, 20)// 直接赋值 len = cap = 10d := []int{1,2, 3, 4, 5, 6, 7, 8, 9, 10}

PS:

  1. cap 必须大于len,否则会报错
  2. 如果len<cap,则访问超出len的元素会报错——数组越界
  3. 指定长度但是并未赋值,此时数组长度内的元素全部为该类型的零值
  4. 只定义但未声明时,此时变量为空指针nil

源代码:

func makeslice(et *_type, len, cap int) unsafe.Pointer {mem, overflow := math.MulUintptr(et.Size_, uintptr(cap))if overflow || mem > maxAlloc || len < 0 || len > cap {// 注意:当有人使用make([]T, bignumber)时,产生'len超出范围'的错误,// 而不是'cap超出范围'的错误。'cap超出范围'也是对的,但由于cap只是隐式提供的,// 所以说len更清楚。// 参见 golang.org/issue/4085。mem, overflow := math.MulUintptr(et.Size_, uintptr(len))if overflow || mem > maxAlloc || len < 0 {panicmakeslicelen()}panicmakeslicecap()}return mallocgc(mem, et, true)
}

解释:

  • 用来计算所需内存的大小
mem, overflow := math.MulUintptr(et.Size_, uintptr(cap))
  • 检查是否有溢出、内存超限或无效的长度和容量
if overflow || mem > maxAlloc || len < 0 || len > cap
  • 内存超限就直接抛出错误
  • 调用mallocgc方法进行内存分配

四、内容截取

可以使用下面这种方式对切片进行内容截取

	s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}// s1: [2 3 4 5 6 7 8 9]s1 := s[1:]// s2: [1 2 3 4 5 6 7 8]s2 := s[:len(s)-1]// s3: [2 3 4 5 6 7 8]s3 := s[1 : len(s)-1]

PS:其实不管进行什么截取操作,本质上都没有创造新的数组,底层的数组仍然是初始的那一个没有变,只是改变了起始指针的位置,len以及cap的值。

五、切片扩容

func growslice(oldPtr unsafe.Pointer, newLen, oldCap, num int, et *_type) slice {oldLen := newLen - num// 如果启用了竞态检测,则进行内存读取范围检测if raceenabled {callerpc := getcallerpc()racereadrangepc(oldPtr, uintptr(oldLen*int(et.Size_)), callerpc, abi.FuncPCABIInternal(growslice))}// 如果启用了内存清理检测,则进行内存读取检测if msanenabled {msanread(oldPtr, uintptr(oldLen*int(et.Size_)))}// 如果启用了地址清理检测,则进行内存读取检测if asanenabled {asanread(oldPtr, uintptr(oldLen*int(et.Size_)))}// 如果新长度小于0,则抛出异常if newLen < 0 {panic(errorString("growslice: len out of range"))}// 如果元素类型的大小为0,则返回一个新的切片,其指针为nil,长度和容量为newLenif et.Size_ == 0 {return slice{unsafe.Pointer(&zerobase), newLen, newLen}}// 计算新的容量newcap := oldCapdoublecap := newcap + newcapif newLen > doublecap {newcap = newLen} else {const threshold = 256if oldCap < threshold {newcap = doublecap} else {for 0 < newcap && newcap < newLen {newcap += (newcap + 3*threshold) / 4}if newcap <= 0 {newcap = newLen}}}// 根据元素类型的大小,计算内存大小,并检查是否溢出var overflow boolvar lenmem, newlenmem, capmem uintptrswitch {case et.Size_ == 1:lenmem = uintptr(oldLen)newlenmem = uintptr(newLen)capmem = roundupsize(uintptr(newcap))overflow = uintptr(newcap) > maxAllocnewcap = int(capmem)case et.Size_ == goarch.PtrSize:lenmem = uintptr(oldLen) * goarch.PtrSizenewlenmem = uintptr(newLen) * goarch.PtrSizecapmem = roundupsize(uintptr(newcap) * goarch.PtrSize)overflow = uintptr(newcap) > maxAlloc/goarch.PtrSizenewcap = int(capmem / goarch.PtrSize)case isPowerOfTwo(et.Size_):var shift uintptrif goarch.PtrSize == 8 {shift = uintptr(sys.TrailingZeros64(uint64(et.Size_))) & 63} else {shift = uintptr(sys.TrailingZeros32(uint32(et.Size_))) & 31}lenmem = uintptr(oldLen) << shiftnewlenmem = uintptr(newLen) << shiftcapmem = roundupsize(uintptr(newcap) << shift)overflow = uintptr(newcap) > (maxAlloc >> shift)newcap = int(capmem >> shift)default:lenmem = uintptr(oldLen) * et.Size_newlenmem = uintptr(newLen) * et.Size_capmem, overflow = math.MulUintptr(et.Size_, uintptr(newcap))capmem = roundupsize(capmem)newcap = int(capmem / et.Size_)}// 检查是否溢出,以防止在32位架构上触发段错误if overflow || capmem > maxAlloc {panic(errorString("growslice: len out of range"))}// 分配内存,并根据情况清理内存var p unsafe.Pointerif et.PtrBytes == 0 {p = mallocgc(capmem, nil, false)memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)} else {p = mallocgc(capmem, et, true)if lenmem > 0 && writeBarrier.enabled {bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(oldPtr), lenmem-et.Size_+et.PtrBytes)}}// 将旧切片的数据移动到新的内存位置memmove(p, oldPtr, lenmem)// 返回新的切片return slice{p, newLen, newcap}
}

主要包含以下内容:

  • 检查新长度是否合法,如果不合法则抛出异常。
  • 计算新的容量,如果新长度超过当前容量的两倍,则直接使用新长度作为新容量;否则,根据一定的规则逐步增加容量,直到满足需求。
  • 分配新的内存空间,并将旧切片的数据复制到新的内存空间。
  • 返回一个新的切片,其底层数组指向新分配的内存,长度和容量更新为新的值。
    PS :
    倘若老容量小于 256,则直接采用老容量的2倍作为新容量;倘若老容量已经大于等于 256,则在老容量的基础上扩容 1/4 的比例并且累加上 192 的数值,持续这样处理,直到得到的新容量已经大于等于预期的新容量为止

六、元素删除

删除其实本质上跟截取是一样的

	s := []int{0, 1, 2, 3, 4}// [1,2,3,4]s = s[1:]
	s := []int{0, 1, 2, 3, 4}// [0,1,2,3]s = s[0 : len(s)-1]

七、切片拷贝

切片拷贝有两种方式
一种是普通的简单拷贝,就是引用传递

s := []int{0, 1, 2, 3, 4}
s1 := s

另一种是深度拷贝,创建出一个和 slice 容量大小相等的独立的内存区域,并将原 slice 中的元素一一拷贝到新空间中

s := []int{0, 1, 2, 3, 4}
s1 := make([]int, len(s))
copy(s1, s)

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

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

相关文章

Android 使用kotlin Retrofit2 + Dagger2完成网络请求跟依赖注入组合使用

文章目录 &#xff08;一&#xff09;引入依赖&#xff08;二&#xff09;基本概念Dagger中的基本概念&#xff1a;Retrofit介绍 &#xff08;三&#xff09;Dagger2 Module 和 Provides 和 Component Inject&#xff08;四&#xff09;Retrofit2 创建数据类Bean跟Service服务&…

探索Java反射:解密动态性与灵活性

前言 Java反射是一项强大而灵活的技术&#xff0c;它使得程序能够在运行时获取类的信息、调用类的方法、访问类的字段等。本篇博客将深入探讨Java反射的原理、应用场景以及使用技巧&#xff0c;带你解密Java反射的奥秘。 什么是Java反射&#xff1f; 在传统的Java编程中&…

LabVIEW调用国产硬件DLL的稳定性问题及解决方案

在LabVIEW中调用国内公司提供的硬件DLL时&#xff0c;尽管可以运行&#xff0c;但常出现不稳定和bug问题&#xff0c;且厂家临时修改的版本未经长期测试。为确保稳定性和质量&#xff0c;需要制定系统化的测试和反馈机制、建立严格的版本控制、与厂家协作优化、并进行深入的自测…

C语言刷题(数组)

1. 编写程序利用数组实现将一个数插入到一个有序的数列中&#xff0c;要求插入后仍有序。 C语言代码 #include <stdio.h> int main(){ int n 0; printf("请输入有序数组元素的个数&#xff1a;\n"); scanf("%d",&n); //定义并输入数组 …

3. MySQL 数据表的基本操作

文章目录 【 1. MySQL 创建数据表 】【 2. MySQL 查看表 】2.1 查看表的属性DESCRIBE/DESC 以表格的形式展示表属性SHOW CREATE TABLE 以SQL语句的形式展示表属性 2.2 查看表的内容 【 3. MySQL 修改数据表结构 】3.1 修改表名3.2 修改表字符集3.3 添加字段在末尾添加字段在开头…

LLMs Can’t Plan, But Can Help Planning in LLM-Modulo Frameworks

更多精彩内容&#xff0c;请关注微信公众号&#xff1a;NLP分享汇 原文链接&#xff1a;LLMs Can’t Plan, But Can Help Planning in LLM-Modulo Frameworks 你是怎么理解LLM的规划和推理能力呢&#xff0c;来自亚利桑那州立大学最近的一篇论文&#xff0c;对LLM的规划、推理…

RESTful API开发:Flask库设计用户认证接口的6个要点

在当今的Web开发世界里&#xff0c;RESTful API已然成为应用程序间数据交互的标准方式。它们简洁、灵活&#xff0c;使得前后端分离更加顺畅。而Flask&#xff0c;作为一款轻量级且功能强大的Python Web框架&#xff0c;无疑是构建RESTful API的理想工具。然而&#xff0c;要确…

Java面试题:如何在Java中实现线程间的通信?请列举几种常见的方式

在Java中&#xff0c;线程间的通信主要涉及到线程间的数据交换和协调。以下是几种常见的线程间通信方式&#xff1a; 共享对象&#xff1a; 线程可以通过共享对象的实例变量或方法参数来进行通信。这种方式需要特别注意线程安全&#xff0c;通常需要同步代码块或使用锁来避免并…

ios 新安装app收不到fcm推送

&#x1f3c6;本文收录于「Bug调优」专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&&…

Python库之Playwright简介、安装、使用方法详细攻略

Python库之Playwright简介、安装、使用方法详细攻略 引言 在自动化测试领域&#xff0c;Playwright是一个强大的库&#xff0c;它支持无头浏览器自动化&#xff0c;允许开发者在多种浏览器上进行网页自动化操作。Playwright由微软开发&#xff0c;支持Chromium、Firefox和Web…

拼图游戏完整思路(全代码演示)

主界面 小练习1&#xff1a; 一、三个界面的设置1&#xff1a;创建窗体 1、将三个主界面分开为三个类&#xff0c;每个类都去继承JFrame这个类&#xff0c;使得每个类都可以使用创建页面功能 2、对每个类进行空参构造&#xff0c;在空参构造里面进行窗体属性的赋值 3、创建一个…

苍穹外卖--sky-take-out(二)3-5

sky-take-out&#xff08;一&#xff09;1-2https://blog.csdn.net/kussm_/article/details/138614737?spm1001.2014.3001.5501 第三天 公共字段填充--利用AOP 问题提出 这些字段属于公共字段 &#xff1a;在新增员工或者新增菜品分类时需要设置创建时间、创建人、修改时间…

蓝桥杯软件测试-十五届模拟赛2期题目解析

十五届蓝桥杯《软件测试》模拟赛2期题目解析 PS 需要第十五界蓝桥杯模拟赛2期功能测试模板、单元测试被测代码、自动化测试被测代码请加&#x1f427;:1940787338 备注&#xff1a;15界蓝桥杯省赛软件测试模拟赛2期 题目1&#xff1a;功能测试题目 1&#xff08;测试用例&…

[极速版]写个linux探测自己机器ip地址的tool(基于shell + sshpass)

适用情况&#xff1a;上级路由ssh or teamviewer访问下级路由的机器&#xff0c;但下级路由不支持查看IP 自行完成端口映射or DMZ整机映射 apt-get install sshpass#!/bin/bash mkdir log for i in $(seq 2 255) dosshpass -p tmp ssh -E err.log -o StrictHostKeyCheckingno …

【解决】Tree prefab at index 8 is missing.

开发平台&#xff1a;Unity 2020 版本以上   问题描述 翻译&#xff1a;树预制体集合中第8位预制体丢失。   解决方法&#xff1a;修复丢失树资产 关联 Unity Terrier 组件使用&#xff0c;前往 树绘制工作区&#xff0c;检查 “树资产” 引用是否丢失&#xff1f;删除或重…

【面试题-013】MyBatis 中,`#` 和 `$` 符号区别

在 MyBatis 中&#xff0c;# 和 $ 符号用于参数替换和表达式。它们在 SQL 语句中用于防止 SQL 注入&#xff0c;并允许动态地插入参数值。 # 符号&#xff08;预编译参数&#xff09;: #{parameter} 用于预编译参数。在 SQL 语句中&#xff0c;#{parameter} 会被 MyBatis 解析…

【C/C++】C++类的六个特殊成员函数,附亲测实例

在C中&#xff0c;类的特殊成员函数是指那些由编译器自动生成的函数&#xff0c;它们在特定情况下会被调用&#xff0c;以支持类的某些操作。这些特殊成员函数包括&#xff1a; 默认构造函数&#xff08;Default Constructor&#xff09;&#xff1a; 当没有提供任何构造函数时…

【C++】C++程序的四个区和智能指针的实现

这篇文章介绍下 C 程序的四个区&#xff0c;以及一个智能指针的简单实现。 起因 最近在公司审查代码的时候&#xff0c;coverity 对以下代码&#xff1a; T fun() {Obj obj;//代码逻辑 }报出了 obj 占用空间过大&#xff0c;有可能栈溢出的问题。 以前从来没有考虑过C的代码…

双指针练习:盛水最多的容器

题目链接&#xff1a;11.盛水最多的容器 题目描述&#xff1a; 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可…

【多模态】34、LLaVA-v1.5 | 微软开源,用极简框架来实现高效的多模态 LMM 模型

文章目录 一、背景二、方法2.1 提升点2.2 训练样本 三、效果3.1 整体效果对比3.2 模型对于 zero-shot 形式的指令的结果生成能力3.3 模型对于 zero-shot 多语言的能力3.4 限制 四、训练4.1 数据4.2 超参 五、代码 论文&#xff1a;Improved Baselines with Visual Instruction …