01.切片的内部实现和基础功能

切片是围绕着动态数组的概念来构建的,它跟数组类似,是用于管理数据集合的一种数据结构。

数组一旦创建就不能更改其长度和类型,而切片就不同,切片可以按需自动增长和缩小,增长一般使用内置的 append 函数来实现,而缩小则是通过对切片再次进行切割来实现。

切片内部实现

切片对底层数组进行了抽象,并提供相关的操作方法,其内部包含3个字段:指向底层数组的指针、切片访问的元素的个数(即长度)、切片运行增长到的元素个数(即容量)。

02.切片的创建与初始化

切片的创建有两种方式:一种是使用 make 函数来创建,另一种是使用字面量方式创建。

// 使用 make 函数创建切片
// var 切片变量 = make([]类型, 长度, 容量)
var slice = make([]int, 5, 5)

make 函数声明切片时,第二个参数必填,但第三个参数可以不填,不写第三个参数时,其容量默认等于长度值,但是如果指定容量,那容量一定不能小于长度。

以下两种方式声明是等价的。

// 以下两种声明方式是等价的
// 方式1
var slice = make([]int, 5)
​
// 方式2
var slice = make([]int, 5, 5)

声明并初始化切片时,可以指定所有的元素,也可以只初始化部分元素,此时需要指定要初始化的元素索引。

// 声明并初始化切片
// 在声明切片时,指定切片所有元素
var slice = []int{1, 2, 3}
​
// 初始化部分元素
// 初始化索引为1的元素为1,索引2为6, 索引5为10
var slice = []int{1:1, 2:6, 5:10}

使用字面量声明切片时,有两种特殊情况:一是空切片,二是 nil 切片。

这两种情况创建出来的切片,其长度为0,是不能直接通过下标的方式来赋值的。

// 使用字面量声明切片
// 情况一:空切片
// 切片的元素值是切片类型的零值,即 int 0, string '', 引用类型 nil
var slice = []int{}
// 尝试赋值会报错:runtime error: index out of range [0] with length 0
slice[0] = 1
​
// 情况二:nil 切片
var slice []int
// 尝试赋值会报错:runtime error: index out of range [0] with length 0
slice[0] = 1

我们可以使用对切片进行再次切片来复制切片,例如:slice[start:end] 表示对切片 slice 进行从索引start 到 end的切割,start 和 end 表示 slice 切片的元素索引,如果 start 和 end 为空,则分布默认为 0 和 切片的长度。

// 原切片 slice
var slice = []int{1, 2, 3, 4, 5}
​
// 对slice进行切片生成新切片 newSlice
// 复制整个 slice 切片
var newSlice = slice[:]
​
// 复制 索引 1 到 3 的元素, 注意新切片不包含索引为 3 的元素
var newSlice = slice[1:3] // [2 3]
​
// 复制 slice 第一个元素 到索引为 2 的元素
// 不用写第一个索引
var newSlice = slice[:2] // [1 2]
​
// 复制 slice 索引为2 的元素 到 最后一个元素
// 不用写第二个索引
var newSlice = slice[2:]
​

还可以指定第三个参数如:slice[start:end:cap],第三个参数指定了新切片的容量。不指定cap时,默认值是等于slice的长度值。

var slice = []int{1, 2, 3, 4, 5}
​
// 以下两种方式相等
// 方式一
var newSlice = slice[2:4:5]
​
// 方式二, 第三个参数默认是 slice的长度
var newSlice = slice[2:4]

新切片的长度,容量的计算,如果使用 slice[i:j:k] 表示 对 slice进行从 i 到 j 进行切割,并指定新切片容量为k, 但是 k 不能大于 slice 的容量。

新切片的长度和容量计算公式如下:

长度 = j - i

容量 = k - i

// 原切片 slice, 长度为5, 容量为5,类型为 int
var slice = []int{1, 2, 3, 4, 5}
​
// 新切片 newSlice 长度=2 (即4-2), 容量=3 (即5-2)
var newSlice = slice[2:4]
​
// 尝试把新切片容量改为大于 slice 容量(5) 的值
// 运行时会报错:slice bounds out of range [::6] with capacity 5
var newSlice = slice[2:4:6]
​
// 可以使用内置函数 len, cap 查看切片的长度和容量
log.Println(len(newSlice), cap(newSlice))

对切片再次切片的时候,新切片和原来的切换会共享其底层数组,所以更改其中一个切片时,其它对底层数组的引用的切片也会受到影响。

var slice = []int{1, 2, 3, 4, 5} // [1 2 3 4 5]
var newSlice = slice[2:4] // [3 4]
​
// 将 newSlice 第一个元素改为 66
newSlice[0] = 66 // newSlice 为 [66 4]
// slice 也会被影响
log.Println(slice) // [1 2 66 4 5]

如何避免这种切片之间相互影响的情况呢?如果想要切片之间互相不影响,那需要改变切片的底层数组,可以使用 append 来实现。

var slice = []int{1, 2, 3, 4, 5} // [1 2 3 4 5]
// 解构 slice 元素 到一个新的空切片中,
// 因为空切片会基于新数组创建的,不会和 slice 共享底层数组
// 所以影响到 slice 的切片
var newSlice = append([]int{}, slice...)
​
// 再次更改 newSlice 的元素
newSlice[0] = 66 // [66 2 3 4 5]
// slice 不会被影响
log.Println(slice) // [1 2 3 4 5]

要理解以上的为什么切片之间为什么会相互影响的原因,需要知道切片在使用 append 进行扩容时,什么时候会共享原切片底层数组?什么时候会使用新数组来创建切片?

当创建一个新切片时,如果底层数组的容量还充足,那会共享原切片的底层数组;

如果底层数组的容量已经不足,则会新建重新创建一个新数组,然后在把老数组中的数据复制到新数组中。

而使用 append 时,底层数组容量不足时,不再以原来的算法进行扩容,而是容量会比切片长度多1,书本中的描述算法已经被优化。

书本中是:“如果元素数量小于1000,每次扩容时,都会以两倍的容量进行增长,如果长度大于1000,则以25%容量增长”,这个算法已经不再适用。

package main
import "log"
​
func main() {// len=2, cap=2var slice = []int{1, 2}// 不会创建新底层数组newSlice := slice[:1]
​// 底层数组容量(2)足够,不会创建新底层数组// 修改 newSlice, slice 不会被影响newSlice[0] = 100//  newSlice=[100], len=1, cap=2//log.Printf("newSlice=%v, len=%d, cap=%d", newSlice, len(newSlice), cap(newSlice))//  slice=[100 2], len=2, cap=2//log.Printf("slice=%v, len=%d, cap=%d", slice, len(slice), cap(slice))
​// 底层数组容量(2)足够, 不会创建新底层数组// slice 也会被影响newSlice = append(newSlice, 66)//  newSlice=[100 66], len=2, cap=2//log.Printf("newSlice=%v, len=%d, cap=%d", newSlice, len(newSlice), cap(newSlice))//  slice=[100 66], len=2, cap=2//log.Printf("slice=%v, len=%d, cap=%d", slice, len(slice), cap(slice))
​// 底层数组容量不足,会创建一个新的底层数组// slice 不会被影响newSlice = append(newSlice, 999)//  newSlice=[100 66 999], len=3, cap=4//log.Printf("newSlice=%v, len=%d, cap=%d", newSlice, len(newSlice), cap(newSlice))//  slice=[100 66], len=2, cap=2//log.Printf("slice=%v, len=%d, cap=%d", slice, len(slice), cap(slice))
​// 此时再次修改 newSlice, slice将不再被影响// 因为此时的 newSlice 和 slice 已经不再共享底层数组newSlice[0] = 777// newSlice=[777 66 999], len=3, cap=4log.Printf("newSlice=%v, len=%d, cap=%d", newSlice, len(newSlice), cap(newSlice))//  slice=[100 66], len=2, cap=2log.Printf("slice=%v, len=%d, cap=%d", slice, len(slice), cap(slice))
}

03.切片的增删改查

切片新增元素

1. 切片尾部新增元素

var slice []int
​
// 切片尾部增加元素
// 新增一个元素
slice = append(slice, 1)
​
// 新增多个元素
slice = append(slice, 1, 2)
​
// 新增多个元素, 切片作为参数,需要使用 ... 运算符来辅助解构切片
var newSlice = []int{1, 2, 3}
slice = append(slice, newSlice...)

2. 切片首部新增元素

// 切片首部增加元素
var slice = []int{1, 2}
​
// 首部增一个元素
slice = append([]int{5}, slice...)
// 首部增多个元素
var newSlice = []int{5, 6, 7}
slice = append(newSlice, slice...) 

3. 切片中间新增元素

// 切片中间某个位置插入元素
var slice = []int{1, 2, 3}
// 比如需要插入到元素索引i后, 则先以 i+1 为切割点,把 slice 切割成两半,
// 索引 i 前数据:slice[:i+1], 索引 i 后的数据: slice[i+1:]
// 然后再把 索引 i 后的数据: slice[i:]合并到需要插入的元素切片中如:append([]int{6, 7}, slice[i:]...)
// 最后再把合并后的切片合并到 索引 i 前数据:slice[:i]
// 如在元素索引1后增加元素
slice = append(slice[:2], append([]int{6, 7}, slice[2:]...)...)

切片删除、修改、查找元素

查找切片元素的时候只能访问切片长度以内的元素,超出长度时,会报错。

var slice = []int{1, 2, 3, 4, 5, 6}
// 从切片首部删除
slice = slice[1:]
​
// 从切片尾部删除
slice = slice[:len(slice) - 2]
​
// 从切片中间删除, 如从索引为i,删除2个元素(i+2)
slice = append(slice[:1], slice[3:]...)
​
// 修改元素
var slice = []int{1, 2, 3}
slice[1] = 6
​
// 查找元素
var slice = []int{1, 2, 3}
log.Println("slice[1]=", slice[1])// 试图访问超出其长度的元素就会报错
a := slice[4]
​log.Println(a) // runtime error: index out of range [4] with length 3

04.在函数间传递切片

元素虽然函数传参都是以值传递的方式,但是由于切片底层结构的原因,其含有指向底层数组的地址指针。

所以切片当做函数参数的时候,函数内更改切片时,共享其底层数组的其他切片也会受到影响。

如果不想影响到其他切片的数据,最好在函数内部使用 append 函数生成一个新的,不与参数切片共享底层数组的切片。

// 修改前,会影响外部切片
func changeSliceValue(slice2 []int) {// 直接使用切换会影响函数外部的切片slice2[1] = 666log.Println("slice2=", slice2)
}
// 注意切片类型要一致
var slice = []int{1, 2, 3}
changeSliceValue(slice)
log.Println("slice=", slice)
​
// 修改后, 不会影响外部切片
func changeSliceValue(slice2 []int) {// 直接使用切换会影响函数外部的切片newSlice := append([]int{}, slice2...)newSlice[1] = 666log.Println("newSlice=", newSlice)
}
// 注意切片类型要一致
var slice = []int{1, 2, 3}
changeSliceValue(slice)
log.Println("slice=", slice)

05.切片的迭代

可以使用 for range 以及 for 循环语句来迭代切片

var slice = []string{"Red", "Yellow", "Blue", "Green", "Gray"}
// for 循环迭代
for i:=0; i<len(slice); i++ {//log.Println(i, slice[i])log.Println(i, slice[i])
}
​
// for range 循环迭代
for i, color := range slice {log.Println(i, color)
}

06.多维切片

多个切片组合成多维切片,多维切片的类型和长度相等时,可以相互赋值。

// 声明
var slice = make([][]int, 2)// 声明 二维nil切片
var slice [][]int// 声明 空二维切片
var slice = [][]int{}// 字面量声明并初始化
var slice = [][]int{{1}, {1:1}}
// 将二维切片的元素赋值给其他切片
var slice2 = slice[1]
​
// 读取二维切片
log.Println(slice, slice[0], slice[1][1], slice2)

goland创建一个不限长度的字节切片_关于Go切片,看这篇就够了相关推荐

  1. goland创建一个不限长度的字节切片_Go语言入门必知教程-切片

    切片是一种灵活的和可扩展的数据结构,用于实现和管理数据集.切片由多个元素组成,所有元素都是相同类型的.切片是动态数组的一部分,可以根据需要进行增长和收缩.与数组一样,切片也可以索引.切片具有容量和长度 ...

  2. goland创建一个不限长度的字节切片_Go语言3 : 切片

    数组是内存连续存储的抽象,但是因为数组连续存储是需要指明大小的,并且一旦指明就不可以修改,因此各大语言都有内置的动态数组结构,如Java中的ArrayList,C++中的Vector,而在Go语言,可 ...

  3. php文本框限制字节,js限制文本框输入长度两种限制方式(长度、字节数)_基础知识...

    功能/特点: 1.实时显示可输入的字数(字节数) 2.两种限制方式(长度.字节数) 3.中文输入法下可正常使用,无BUG 4.同一页面可以使用多个,相互不干扰 limit.js function li ...

  4. 如何创建一个不确定长度的数组

    int size;//size表示数组长度 size=<表达式>://给size赋值 int * p=new int [size];//要定义不确定长度数组,必须动态分配,此处以定义int ...

  5. 用python创建一个从1到10的列表_【编测编学】零基础学python_10_列表(创建数值列表 )...

    创建数值列表 需要存储一组数字的原因有很多,例如,在游戏中,需要跟踪每个角色的位置,还可能需要跟踪玩家的几个最高得分.在数据可视化中,处理的几乎都是由数字(如温度.距离.人口数量.经度和纬度等)组成的 ...

  6. 2021前端面经-看这篇就够了(笔者靠这个拿到阿里和字节的offer)

    面试题梳理 梳理前端常见面试题及答案. 一.web 前端性能优化 性能评级工具(PageSpeed 或 YSlow) css CSS优化.提高性能的方法有哪些 多个css合并,尽量减少HTTP请求 将 ...

  7. 前端面经 - 看这篇就够了(笔者靠这个拿到阿里和字节的offer)

    转载自:alanyf https://juejin.cn/post/6948227795059212318 面试题梳理 梳理前端常见面试题及答案. 一.web 前端性能优化 性能评级工具(PageSp ...

  8. 最小长度电路板排列问题_射频电路板设计,这篇文章五大总结不可忽视!

    射频电路板设计由于在理论上还有很多不确定性,因此常被形容为一种"黑色艺术",但这个观点只有部分正确,RF电路板设计也有许多可以遵循的准则和不应该被忽视的法则. 不过,在实际设计时, ...

  9. 字节对齐看这篇就够了(内存对齐)

    前言 大家好,我是小昭,因为在不同硬件平台数据传输时,遇到关于字节对齐的问题,索性就做了总结,以下是我对字节对齐的理解和小结,如有疑问请联系我. 目录 结构体变量占用空间不同 为什么要字节对齐?(内存 ...

最新文章

  1. 从壹开始微服务 [ DDD ] 之一 ║ D3模式设计初探 与 我的计划书
  2. 为博客园添加github跳转链接
  3. Linux添加新硬盘、分区、格式化、自动挂载
  4. RK2908开机时间分析及优化
  5. ETL安装前的准备 - 数据库创建方法
  6. OpenGL相机控制之一
  7. setState是同步的还是异步的(都有)
  8. ajax更换内容执行函数,在ajax成功调用另一个ajax函数
  9. hive-sql中平方和开根号函数
  10. 支付宝转账银行卡/隐藏卡号
  11. IT行业英语自我介绍必备
  12. e3d教程做logo教程_AE-炫酷LED灯动画 LOGO片头制作(E3D插件)
  13. 如果写不出好的和弦就在洒满阳光的钢琴前一起吃布丁+与8有关的事儿
  14. 开源节流系列之工程施工篇
  15. 小行助学答题系统编程等级考试scratch三级真题2023年3月(含题库答题软件账号)
  16. 7. MyBatis多表查询 - 一对一 - 一对多 - 多对多
  17. 【ORA-00257:archiver error. Connect internal only, until freed;清理归档日志】
  18. 只有自我负责,才能真正增长自己的力量
  19. HNU-计算机系统-讨论课5
  20. 计算机硬件相关的论文,计算机硬件论文范文

热门文章

  1. php+yii框架,yii框架源码分析(一)
  2. python怎么写脚本执行adb命令_android – 如何使用Python执行adb命令?
  3. 56个民族的下拉菜单
  4. 使用holyholes实现边缘检测
  5. 2018年上半年信息系统项目管理师考试真题附答案解析(5)
  6. 房地产企业(集团)网站建设方案
  7. Oracle的TNS协议解析
  8. 字幕说--自媒体人必备的在线语音合成及同步字幕生成工具
  9. c语言程序设计神奇算式,神奇算式
  10. 全拼输入法失效的解决方法