Go 知识slice

Go 知识slice

  • 1. 什么是slice
  • 2. slice 基础
    • 2.1 定义
  • 2.2 实现原理
    • 2.2.1 make 创建
    • 2.2.2 切片 创建
  • 2.3 操作
      • 2.3.1 append 追加
      • 2.3.2 表达式切片
      • 2.3.3 扩展表达式
      • 2.3.4 扩容
      • 2.3.5 拷贝
  • 3. 测试一下
    • 3.1 len && cap
    • 3.2 append && 扩容
    • 3.3 切片表达式

1. 什么是slice

slice 是动态数组,依托数组实现,可以方便的进行扩容和传递,实际中比数组使用更加频繁。

2. slice 基础

2.1 定义

  • 变量声明
    var s []int
  • 字面量
    s1 := []int{},s2 := []int{1,2,3}
  • 内置函数make
    s1 := make([]int, 12) 指定长度 len
    s2 := make([]int, 10, 20) 指定长度 len 和空间 cap
  • 切片
    array := [5]int{1, 2, 3, 4, 5}
    s1 := array[0:2] // 从数组切片 [0,2) 长度2,下标 0, 1
    s2 := s1[0:1] // 从slice切片 [0,1) 长度 1 ,下标 0
    s3 := s2[:] // 从slice 切片,如果开始位置等于0,结束位置等于len,那么可以省略
    

2.2 实现原理

slice 的定义在src/runtime/slice.go里面
在这里插入图片描述

可以看到 slice 有 len 和 cap 参数,里面记录了 array 数组的长度和空间。
所以 len(slice) 和 cap(slice) 的时间复杂度都是O(1)

2.2.1 make 创建

使用 make 创建会申请分配数组空间,然后将len和cap初始化赋值。
如果 make 没有指定 cap ,只指定了 len ,那么cap = len 。

2.2.2 切片 创建

使用切片创建的时候,slice 和原始数组共用底层函数。

array := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s := array[5:7]

在这种情况下 s 的底层数组 array = &array[0+init],len=2,cap=len(array) - init=5
为什么 slice 的cap=10呢?
因为 slice 和 数组是共用底层数据的,此时计数的单位是从数组的0开始计数,但是操作 slice 的时候,会进行初始偏移量的处理。
比如 s[0] = array[0+5]
len(s) = 2
cap(s) = 10 -5
在这里插入图片描述

这是最危险的情况,此时如果给 s 进行追加元素,会修改 array 的元素。

array := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
ts := array[5:7]
fmt.Println(array)
fmt.Println(ts)
ts = append(ts, -1)
fmt.Println(array)
fmt.Println(ts)

使用程序验证
在这里插入图片描述

如何解决这种问题呢?
答案是指定cap,这样在追加元素的时候,就会触发扩容,这样 slice 和数组就分离了
上述代码可以修改为:

array := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
//ts := array[5:7]
//fmt.Printf("len=%d,cap=%d\n", len(ts), cap(ts))
//fmt.Println(array)
//fmt.Println(ts)
//ts = append(ts, -1)
//fmt.Println(array)
//fmt.Println(ts)nts := array[5:7:7]
fmt.Println(array)
fmt.Println(nts)
nts = append(nts, -1)
fmt.Println(array)
fmt.Println(nts)

在这里插入图片描述

2.3 操作

2.3.1 append 追加

s := make([]int, 0) // 初始化长度为0的切片,注意空间不一定为0
s = append(s, 1) // 添加一个元素,长度为1
s = append(s, 2, 3, 4) // 添加多个元素
s = append(s, []{5, 6, 7}...} // 添加切片 

当切片空间不足的时候,会先扩容在追加.
原理:
首先 append 的函数定义

func append(slice []Type, elems ...Type) []Type

可以看出 append 接收一个切片和不定数量的元素,返回切片,需要注意的是切片的元素类型和需要添加的元素类型要相同。
而添加切片的时候,用到了切片展开 ...
用于将一个切片展开为一个个的元素。

2.3.2 表达式切片

表达式切片的格式 slice[low:high]
如果是数组0<=low<=gigh<=len(array)
如果是slice0<=low<=high<=cap(slice)

arr := [5]int{1, 2, 3, 4, 5}
s1 := arr[:] // 从数据转为 slice 省略了开始和结尾
s2 := arr[0:len(arr)] // 从数组转为 slice 指定了开始和结束
s3 := arr[2:4] // 从数组切片 由2开始到4结束,[2,4) => [3, 4] 取下标为 2, 3 的元素
s4 := arr[:4] // 从数组切片 由0开始到4结束,[0,4) => [1, 2, 3, 4] 取下标为 0, 1, 2, 3 的元素
s5 := arr[3:] // 从数组切片 由3开始到最后一个元素结束,[3, len(arr)) => [4, 5] 取下标为 3, 4 的元素

需要注意的是,表达式切片后产生的切片底层数组是共享的,所以非常容易出错。

2.3.3 扩展表达式

扩展表达式是为了解决新旧变量共用底层数组,导致相互影响的问题。只要限制了新slice的空间容量的限制,那么当修改的时候,还是会修改底层数组,也就是原数组的对应的元素也会发生变化,当时当发生扩容的时候,就不会完全修改超出预期的元素。
扩展表达式的格式slice[low:high:cap]需要注意的是,cap是在原数组或者slice的基础上计算的cap值。
0<=low<=high<=cap<=cap(slice)
比如

array := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
nts := array[5:7:7]
fmt.Println(array)
fmt.Println(nts)
fmt.Printf("len=%d, cap=%d\n", len(nts), cap(nts))
nts = append(nts, -1)
fmt.Println(array)
fmt.Println(nts)
fmt.Printf("len=%d, cap=%d\n", len(nts), cap(nts))

在这里插入图片描述

2.3.4 扩容

在slice扩容的过程中,会重新申请一个更大容量的底层数组,然后将len拷贝过去,将新的cap赋值。
扩容的规则:

  • 如果原slice的cap<=1024,那么新slice的newCap = oldCap*2
  • 如果原slice的cap > 1024,那么新slice的newCap = oldCap*1.25

2.3.5 拷贝

在前面说到,使用表达式切片,会导致底层数组共享的问题,使用扩展表达式,只是解决了新slice的越界修改的问题,最基本的数据隔离还是没有实现,修改新slice的数据,还是会影响到原slice的数据。
那么,如何解决呢,只能是主动显示的拷贝,产生新的底层数组,实现新旧slice的数据隔离。
使用内置函数copy就能实现拷贝,但是需要注意,拷贝的过程中不会主动扩容。
意思是指假设新的slice的容量是5,旧的slice的容量是3,那么只会拷贝3个元素,实际上这种场景也到符合预期。
但是当newCap=5,oldCap=10,那么只会拷贝5个元素,并不会进行扩容。

3. 测试一下

3.1 len && cap

func TestSliceCap(t *testing.T) {var arr [10]intvar sl = arr[5:6]sl = append(sl, 7)fmt.Printf("len=%d\n", len(sl))fmt.Printf("cap=%d\n", cap(sl))
}

len=2
cap=5

3.2 append && 扩容

func TestSliceCap(t *testing.T) {s1 := []int{1, 2}s2 := s1s2 = append(s2, 3)add(s1)add(s2)fmt.Println(s1, s2)
}
func add(s []int) {s = append(s, 0)for i := range s {s[i]++}
}

在这里插入图片描述

解析:
s1 是slice
s2 等于s1,此时s1和s2相同
s2 = append(s2, 3) 发生了扩容,此时s2和s1是两个底层数组
add(s1)此时将s1传入了add函数,在add函数中进行append,进行了扩容,此时add函数内的s表示的底层数组也和s1不同了
add函数对newS1进行了+1操作,但是因为add函数没有返回,所以s1函数还是旧的底层数组,也就是[1 2]
add(s2)将s2传入了add函数,但是s2在s1的基础上进行了扩容,因为s1的cap<=1024,所以s2的cap=cap(s1)*2=4,
当进行append的时候,此时还不需要扩容,所以newS2=s2,add函数对元素+1也就修改了s2,也就是s2是[2 3 4 1]
打印的时候因为s2的len(s2)=3,所以没有打印s2[3]
相当于我们越界修改了len(s2)之外的数据,但是对于程序,s2[3]是不可见的.

3.3 切片表达式

func TestSliceExpress(t *testing.T) {orderLen := 5order := make([]int, 2*orderLen)lowOrder := order[:orderLen:orderLen]highOrder := order[orderLen:][:orderLen:orderLen]fmt.Printf("len(low)=%d, cap(low)=%d\n", len(lowOrder), cap(lowOrder))fmt.Printf("len(high)=%d,cap(high)=%d\n", len(highOrder), cap(highOrder))
}

在这里插入图片描述

上述方式可以实现将切片一分为二,并且不会发生越界问题,怎么实现的呢?
首先创建order,len=10,cap=10
因为orderLen=5,所以lowOrder采用扩展表达式切片:lowOrder,[0,5),而且指定cap=5,这样lowOrder就不会出现越界问题,当len=5并且需要扩容的时候,会进行拷贝。
highOrder相当于进行了两次切片:
第一次 order[orderLen:]表示从5开始切分,剩余的元素都要,但是没有设置cap
第二次 在第一次的基础上,进行了指定长度,但是因为第一次产生的变量中已经存在了init,所以第二次是从0开始。需要注意,上述所有操作都是基于order这个slice进行的,没有产生新的底层数组。
为了便于理解,我们加一个中间变量:

func TestSliceExpress(t *testing.T) {orderLen := 5order := make([]int, 2*orderLen)lowOrder := order[:orderLen:orderLen]midOrder := order[orderLen:]highOrder := midOrder[:orderLen:orderLen]fmt.Printf("len(low)=%d, cap(low)=%d\n", len(lowOrder), cap(lowOrder))fmt.Printf("len(mid)=%d,cap(mid)=%d\n", len(midOrder), cap(midOrder))fmt.Printf("len(high)=%d,cap(high)=%d\n", len(highOrder), cap(highOrder))
}
lowOrder = &order[0], len=5, cap=5
midOrder = &order[5], len=5, cap=5
highOrder = &midOrder[0] = &order[5], len=5, cap=5

在这里插入图片描述

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

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

相关文章

Vue2移动端项目使用$router.go(-1)不生效问题记录

目录 1、this.$router.go(-1) 改成 this.$router.back() 2、存储 from.path&#xff0c;使用 this.$router.push 3、hash模式中使用h5新增的onhashchange事件做hack处理 4、this.$router.go(-1) 之前添加一个 replace 方法 问题背景 &#xff1a; 在 Vue2 的一个移动端开发…

Docker安装与启动

Docker概述 Docker是一个快速交付应用、运行应用的技术&#xff1a; 可以将程序及其依赖、运行环境一起打包为一个镜像&#xff0c;可以迁移到任意Linux操作系统运行时利用沙箱机制形成隔离容器&#xff0c;各个应用互不干扰启动、移除都可以通过一行命令完成&#xff0c;方便…

AttributeError: module ‘numpy‘ has no attribute ‘float‘解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

YOLOv5改进系列(27)——添加SCConv注意力卷积(CVPR 2023|即插即用的高效卷积模块)

【YOLOv5改进系列】前期回顾: YOLOv5改进系列(0)——重要性能指标与训练结果评价及分析 YOLOv5改进系列(1)——添加SE注意力机制

【Docker】安装Nginx容器并部署前后端分离项目

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的专栏《Docker实战》。&#x1f3af;&#x1f3af; &…

爬虫requests+综合练习

Day2 - 1.requests第一血_哔哩哔哩_bilibili requests作用&#xff1a;模拟浏览器发请求 requests流程&#xff1a;指定url -> 发起请求 -> 获取响应数据 -> 持续化存储 爬取搜狗首页的页面数据 import requests# 指定url url https://sogou.com # 发起请求 resp…

Three.JS教程1 环境搭建、场景与相机

Three.JS教程1 环境搭建、场景与相机 一、Three.JS简介二、环境搭建1. 开发准备2. 安装 three.js3. 新建文件index.htmlmain.js 4. 关于附加组件5. 启动 三、创建场景1. 场景的概念2. 相机的概念3. 相机的几个相关概念&#xff08;1&#xff09;视点&#xff08;Position&#…

【redis13】集群前奏:sentinel模式

1.哨兵sentinel引入背景 我们现在来思考一个问题&#xff1a;如何实现服务的高可用。我们首先想到至少要满足两个要求&#xff1a;1.服务端能够实现主从自动切换&#xff1b;2.对于客户端来说&#xff0c;如果发生了主从切换&#xff0c;则能够自动连接到最新的master节点。 我…

S/MIME电子邮件证书申请指南

近年来&#xff0c;邮件安全问题日益突出&#xff0c;电子邮件成为诈骗、勒索软件攻击的重灾区。恶意邮件的占比屡创新高&#xff0c;邮件泄密事件更是比比皆是。在如此严峻的网络安全形势下&#xff0c;使用S/MIME电子邮件证书进行邮件收发是当今最佳的邮件安全解决方案之一。…

【PICO】【Unity】【VR】如何对打包后的PICO项目有效Debug

【背景】 PICO项目打包后再运行就看不到Console了。当然,会有各类专业的Debug工具。 有一类Debug的工具是Preview形式下展示Debug信息,但是发现Preview成功不见得打包也成功。 打包后也会有一些Debug工具,不过这里我给出自己的简单解决办法。 【解决方案】 Unity Console…

Java毕业设计-基于jsp+servlet的大学生学业规划咨询服务平台管理系统-第84期

获取源码资料&#xff0c;请移步从戎源码网&#xff1a;从戎源码网_专业的计算机毕业设计网站 项目介绍 基于jspservlet的大学生学业规划咨询服务平台管理系统&#xff1a;前端 jsp、jquery、ajax&#xff0c;后端 servlet、jdbc&#xff0c;角色分为管理员、学生&#xff1b…

Linux——进程等待

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、为什么要进程等待二、进程等待的方法1、wait方法2、waitpid方法 三、获取子进程status 一…

pxe高效批量网络装机 以及安装教程

系统装机的三种引导模式 1.pe 2光驱 3.网卡 打开本机桌面 可以看见背景图片 查看配置文件内容 文件时引导选项的功能 pxe原理&#xff1a; 先根据dhcp找到IP地址、和引导程序的地址&#xff0c;还提供客户机tftp地址&#xff0c;因为tftp是小文件&#xff0c;容量小&#…

【占用网络】FlashOcc:基于2D卷积的占用预测模型

前言 FlashOcc是一个它只需2D卷积就能实现“占用预测模型”&#xff0c;具有快速、节约内存、易部署的特点&#xff0c;偏工程方向的工作。 它首先采用2D卷积提取图形信息&#xff0c;生成BEV特征。然后通过通道到高度变换&#xff0c;将BEV特征提升到3D空间特征。 对于常规…

攻防世界——Shuffle

32bit打开 main函数F5 下班

SpringBoot+Email发送邮件

引言 邮件通知是现代应用中常见的一种通信方式&#xff0c;特别是在需要及时反馈、告警或重要事件通知的场景下。Spring Boot提供了简单而强大的邮件发送功能&#xff0c;使得实现邮件通知变得轻而易举。本文将研究如何在Spring Boot中使用JavaMailSender实现邮件发送&#xf…

Unity中URP下的SimpleLit的 Lambert漫反射计算

文章目录 前言一、Lambert漫反射计算11、MixRealtimeAndBakedGI 函数有三个重载2、3号 调用了 2号3、1号调用了 SubtractDirectMainLightFromLightmap函数4、我们重点来看 Lambert漫反射的实现部分5、其余部分 二、Lambert漫反射计算21、LightingLambert 前言 在之前的文章中&…

Python小项目:还在为备份烦恼?这个tkinter项目帮你解决!

文章目录 1 引言2 Tkinter概览3 设计备份软件的界面4 文件夹选择逻辑5 备份方案介绍5.1 完全备份5.2 增量备份5.3 镜像备份 完整代码&#xff1a; import tkinter as tk from tkinter import filedialog, messagebox import os import shutil import filecmpdef choose_source(…

SpringBoot 3.1.7 集成Sentinel

一、背景 我的项目需要引入限流&#xff0c;降级&#xff0c;熔断框架&#xff0c;由于 Spring Cloud 2022.0.4 已经不再支持 Hystrix&#xff0c;Spring Cloud 提供了替代方案&#xff0c;如 Resilience4j&#xff0c;可以使用它来替换 Hystrix。但是网上搜了一下国内Resilie…

burp靶场--文件上传

burp靶场–文件上传 https://portswigger.net/web-security/file-upload/lab-file-upload-remote-code-execution-via-web-shell-upload 1.文件上传 1、原理&#xff1a;文件上传漏洞是指Web服务器允许用户将文件上传到其文件系统&#xff0c;而不充分验证文件的名称、类型、…