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.hqwc.cn/news/417863.html

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

相关文章

Spring第六天(注解开发第三方Bean)

注解开发管理第三方Bean 显然&#xff0c;我们无法在第三方Bean中写入诸如service这样的注解&#xff0c;所以&#xff0c;Spring为我们提供了Bean这一注解来让我们通过注解管理第三方Bean 第二种导入方式由于可读性太低&#xff0c;故只介绍第一种导入方式&#xff0c;这里我…

外包干了5个月,技术退步明显...

先说一下自己的情况&#xff0c;大专生&#xff0c;18年通过校招进入武汉某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落! 而我已经在一个企业干了四年的功能测…

内网安全管理系统(保密管理系统)

在当今信息化的时代&#xff0c;企业的内网已经成为其核心资产的重要组成部分。 随着企业的快速发展和信息化程度的提升&#xff0c;内网安全问题日益凸显&#xff0c;如何保障内网的安全和机密信息的保密性&#xff0c;已经成为企业亟待解决的问题。 内网安全管理系统(保密管…

第11章 GUI Page507 步骤三十五:处理应用退出事件

为wxFrame&#xff0c;生成一个EVT_CLOSE事件响应函数&#xff1a; 实现如下&#xff1a; 运行效果&#xff1a;关闭时&#xff0c;会弹出对话框询问是否保存

(蓝桥杯每日一题)love

问题描述 马上就要到七夕情人节了&#xff0c;小蓝在这天想要心爱得男神表白&#xff0c;于是她写下了一个长度为n仅由小写字母组成的字符串。 她想要使这个字符串有 1314个 love 子序列但是马虎的小蓝却忘记了当前已经有多少个子序列为 love。 请你帮小蓝计算出当前字符串有多…

初识MQ-同步异步

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、同步通讯二、异步通讯2.1.异步调用方案2.2.异步的优缺点 三、什么时MQ3.1 mq和broker3.2几种mq的优缺点对比 总结 前言 一、同步通讯 同步调用问题&#…

Spring Boot框架中Controller层API接口如何支持使用多个@RequestBody注解接受请求体参数

一、前言 众所周知&#xff0c;在Spring Boot框架中&#xff0c;Controller层API接口编码获取请求体参数时&#xff0c;在参数上会使用RequestBody注解&#xff1b;如果一次请求中&#xff0c;请求体参数携带的内容需要用多个参数接收时&#xff0c;能不能多次使用RequestBody…

【从零到一AIGC源码解析系列1】文本生成图片Stable Diffusion的diffusers实现

目录 1. 如何使用 StableDiffusionPipeline 1.1环境配置 1.2 Stable Diffusion Pipeline 1.3生成非正方形图像 2. 如何使用 diffusers 构造自己的推理管线 关注公众号【AI杰克王】 Stable Diffusion是由CompVis、StabilityAl和LAION的研究人员和工程师创建的文本到图像潜在…

《Linux高性能服务器编程》笔记01

Linux高性能服务器编程 本文是读书笔记&#xff0c;如有侵权&#xff0c;请联系删除。 参考 Linux高性能服务器编程源码: https://github.com/raichen/LinuxServerCodes 豆瓣: Linux高性能服务器编程 文章目录 Linux高性能服务器编程第05章 Linux网络编程基础API5.1 socket…

【MySQL】——关系数据库标准语言SQL(大纲)

&#x1f383;个人专栏&#xff1a; &#x1f42c; 算法设计与分析&#xff1a;算法设计与分析_IT闫的博客-CSDN博客 &#x1f433;Java基础&#xff1a;Java基础_IT闫的博客-CSDN博客 &#x1f40b;c语言&#xff1a;c语言_IT闫的博客-CSDN博客 &#x1f41f;MySQL&#xff1a…

从零开始配置vim(Windows版)

事情是这样的&#xff0c;之前linux下vim用习惯了...然后就给自己win下vscode也装了个vim插件&#xff0c;用下来还是感觉不顺手&#xff0c;并且处理太多文本时有明显卡顿&#xff0c;于是乎自己配了下win版的vim。 不过好像也并不是从零开始的...初始基础版的.vimrc有copy他们…

【前端设计】流光按钮

欢迎来到前端设计专栏&#xff0c;本专栏收藏了一些好看且实用的前端作品&#xff0c;使用简单的html、css语法打造创意有趣的作品&#xff0c;为网站加入更多高级创意的元素。 css body{height: 100vh;display: flex;justify-content: center;align-items: center;background…