Go——切片

1. 特点

        slice并不是数组或数组指针。它通过内部指针和相关属性引用数组片段,以实现变长方案。

  • 切片:切片是数组的一个引用,因此切片是引用类型。但自身是结构体,值拷贝传递。
  • 切片的长度可以改变,因此,切片是一个可变数组。
  • 切片的遍历方式和数组一样,可以用len()求长度。表示可用元素的数量,读写操作不能超过该限制。
  • cap可以求出slice最大扩张容量,不能超出数组限制。0 <= len(slice) <= len(array)。其中array是slice引用的数组。
  • 切片的定义:var 变量名 []类型,比如:var str []string  var arr []int。
  • 如果slice等于nil,那么len,cap结果都等于0。

2. 切片源码

        切片对应的数据结构定义位于runtime/slice.go文件中,其定义如下:

type slice struct {array unsafe.Pointerlen   intcap   int
}
  • array:指针,指向引用数组对应位置。
  • len:可用元素个数。
  • cap:容量,可放元素个数。

        我们在进行切片赋值,传参,截断时,其实是复制一个slice结构体,只不过底层数组是同一个。这就导致了无论是在复制的切片中修改值,还是修改形参切片值,都会修改到原来的切片和引用的数组。

        总的来说:切边底层有一个数组(make创建的切片,底层也会先创建一个数组),切片是数组的引用。

3. 创建切片

4. 切片初始化

package mainimport "fmt"//全局变量
var arr = [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
var slice0 []int = arr[2:8]
var slice1 []int = arr[0:6]            //可以简写为arr[:6]
var slice2 []int = arr[5:10]           //可以简写为arr[5:]
var slice3 []int = arr[0:len(arr)]     //可以简写为arr[:]
var slice4 []int = arr[0 : len(arr)-1] //去掉最后一个元素func main() {fmt.Printf("全局变量arr %v\n", arr)fmt.Printf("全局变量slice0 %v\n", slice0)fmt.Printf("全局变量slice1 %v\n", slice1)fmt.Printf("全局变量slice2 %v\n", slice2)fmt.Printf("全局变量slice3 %v\n", slice3)fmt.Printf("全局变量slice4 %v\n", slice4)fmt.Println("--------------------------")arr2 := [...]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}slice5 := arr2[2:8]slice6 := arr2[0:6]         //可以简写为arr[:6]slice7 := arr2[5:10]        //可以简写为arr[5:]slice8 := arr2[0:len(arr)]  //可以简写为arr[:]slice9 := arr2[:len(arr)-1] //去掉最后一个元素fmt.Printf("局部变量arr %v\n", arr2)fmt.Printf("局部变量slice5 %v\n", slice5)fmt.Printf("局部变量slice6 %v\n", slice6)fmt.Printf("局部变量slice7 %v\n", slice7)fmt.Printf("局部变量slice8 %v\n", slice8)fmt.Printf("局部变量slice9 %v\n", slice9)
}

 5. 通过make来创建切片

var slice []type = make([]type, len)
var slice []type = make([]type, len, cap)
slice := make([]type, len)
slice := make([]type, len, cap)

  • 切片的内存布局 

  • 读写操作实际目标是底层数组,只需要注意索引号的差别。
package mainimport "fmt"func main() {data := [...]int{0, 1, 2, 3, 4, 5}s := data[2:4]s[0] += 100s[1] += 200fmt.Println(s)fmt.Println(data)
}

  • 可直接创建slice对象,自动分配底层数组
package mainimport "fmt"func main() {var s1 []int = []int{0, 1, 2, 3, 8: 100} //通过初始化表达式构造,可使用索引号fmt.Println(s1, len(s1), cap(s1))s2 := make([]int, 6, 8) //使用make创建,指定len和capfmt.Println(s2, len(s2), cap(s2))s3 := make([]int, 6) //使用make创建,省略cap,相当于cap=lenfmt.Println(s3, len(s3), cap(s3))
}

  • 使用make动态创建slice,避免了数组必须用常量的麻烦。还可以用指针直接访问底层数组,退化成普通数组操作。 
package mainimport "fmt"func main() {s := []int{1, 2, 3, 4}p := &s[1] //获得底层数组元素指针*p += 100fmt.Println(s)
}

  •  至于[][]T,是指元素类型为[]T
package mainimport "fmt"func main() {data := [][]int{[]int{1, 2, 3},[]int{10, 20, 30, 40},[]int{100, 200},}fmt.Println(data)
}

  • 可以直接修改struct array/slice成员 
package mainimport "fmt"func main() {data := [5]struct {x int}{}s := data[:]s[1].x = 10s[2].x = 20fmt.Println(data)fmt.Printf("%p, %p\n", &data, &data[0])
}

6. 用append内置函数操作切片(切片追加)

        append:向slice尾部添加数据,返回新的slice对象。

package mainimport ("fmt"
)func main() {var s []int //s是nil,没有分配内存fmt.Println(s)s = append(s, 1) //append会先为s分配内存,再存放数据fmt.Println(s)var a []int = []int{1, 2, 3}fmt.Printf("slice a: %v\n", a)b := []int{4, 5, 6}fmt.Printf("slice b: %v\n", b)//append向slice尾部添加数据c := append(a, b...)fmt.Printf("slice c: %v\n", c)d := append(a, 10)fmt.Printf("slice d: %v\n", d)e := append(a, 100, 200, 300)fmt.Printf("slice e: %v\n", e)//append返回的新的slice对象fmt.Printf("a:%p, c:%p, d:%p, e:%p", &a, &c, &d, &e)
}

8. slice扩容

  • 超出元slice.cap限制,就会重新分配底层数组,即便原数组并未填满。  

        从下面现象可以看出: 

  • 当数据没有超出slice的cap时,如果引用存在的数组变量(不是make创建的),底层数组使用的是存在的数组变量。修改切片的值,就会修改数组的值。
  • 当数据超出slice的cap时,会重新分配底层数组,修改切片的值,不会修改存在数组变量的值,而是修改切片底层数组的值。
package mainimport "fmt"func main() {var data = [...]int{1, 2, 3, 4, 10: 0}s := data[:2:3]fmt.Printf("data:%p, data[0]:%p\n", &data, &data[0]) //data:0xc0000103f0, data[0]:0xc0000103f0//slice中数据保存的是数组对应下标的地址,使用索引访问,相当于对指针解引用,在取地址,实际取的是数组中的地址fmt.Printf("s:%p, s[0]:%p\n", &s, &s[0]) //s:0xc000008048, s[0]:0xc0000103f0//未扩容前修改值s[0] = 11fmt.Println(s, data)s = append(s, 100, 200)fmt.Println(s, data)fmt.Printf("s[0]:%p, data[0]:%p\n", &s[0], &data[0])//扩容后修改值s[0] = 10fmt.Println(s, data)
}

  • slice中cap重新分配规律 

        从下面输出可以看出,slice扩容通常是以两倍原来容量重新分配底层数组。

        建议一次性分配足够大的空间,以减少内存分配和数据复制的开销,或者初始化足够长的len属性,改用索引号进行操作。

        及时释放不再使用的slice对象,避免持有过期数组,造成GC无法回收。

package mainimport "fmt"func main() {s := make([]int, 0, 2)c := cap(s)for i := 0; i < 50; i++ {s = append(s, i)if c < cap(s) {fmt.Printf("cap: %d->%d\n", c, cap(s))c = cap(s)}}
}

9. 切片拷贝

        函数copy在两个slice间复制数据,复制长度以len小的为准。两个slice可指向同一底层数组,允许元素区间重叠(本来创建切片不同的slice也可以指向同一个底层数组)。

        应及时将所需数据copy到较小的slice,以便释放超大号底层数组内存。

10. slice遍历

        slice遍历实际和数组遍历一样。

package mainimport "fmt"func main() {data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}s1 := data[:]for index, value := range s1 {fmt.Printf("s1[%d] = %d\n", index, value)}fmt.Println("-----------------------------")for i := 0; i < len(s1); i++ {fmt.Printf("s1[%d] = %d\n", i, s1[i])}
}

11. 切片调整大小

        由于底层数组相同,且数组空间连续。所以可以像下面赋值。

12. 数组和切片的内存分布

        底层数组不一定要声明出来,比如:make的切片,或者 s := []int{1, 2, 3}

13.  字符串和切片 

 string底层就是一个byte的数组,因此,也可以进行切片操作。

package mainimport "fmt"func main() {str := "hello world"s := str[:5]fmt.Println(s)s1 := str[6:]fmt.Println(s1)
}

  • 修改字符串 

string本身是不可修改的,要修改string,需要先转成[]byte或[]rune,进行修改,再转成string。

英文字符串

package mainimport "fmt"func main() {str := "hello world"//字符串不能修改// s1 := str[:8]// s1[6] = 'G'// str[6] = 'G'//转成[]bytebt := []byte(str) //中文字符需要用[]rune(str)//修改[]bytebt[6] = 'G'bt = bt[:8]bt = append(bt, '!')str = string(bt)fmt.Println(str)
}

含中文字符串

package mainimport "fmt"func main() {str := "你好 世界 hello world"//字符串不能修改// s1 := str[:8]// s1[6] = 'G'// str[6] = 'G'rn := []rune(str)//修改[]bytern[3] = '够'rn[4] = '浪'rn = rn[:12]rn = append(rn, '好')//再转回字符串str = string(rn)fmt.Println(str)
}

  • 数组或切片转字符串 
strings.Replace(strings.Trim(fmt.Sprint(array_or_slice), "[]"), "", ",", -1)

 golang slice data[:6:8]两个冒号的理解:

        对于data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

        常规slice,data[6:8],从data数组索引6到索引7,长度len为2,最大可扩充长度cap为4(slice底层数组为data,指针指向索引6位置,cap从索引6位置到data最后,即6-9,所以cap为4)。

        另一种写法:data[:6:8]每一个数字前面都有冒号,slice内容为data索引从0到6,长度len为6,最大扩充项cap设置为8。

        a[x:y:z]切片内容[x:y](前闭后闭),切片长度:y-x,切片容量:z-x

package mainimport "fmt"func main() {data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}s1 := data[6:8]fmt.Println(len(s1), cap(s1))//s2 := data[6: :8] 中间必须要有数字s2 := data[:6:8]fmt.Println(len(s2), cap(s2))fmt.Println(s2)
}

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

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

相关文章

Elasticsearch实战:索引阻塞 —— 数据保护的终极武器

文章目录 1、索引阻塞的种类2、什么时候使用阻塞&#xff1f;场景1&#xff1a;进行系统维护场景。场景2&#xff1a;保护数据不被随意更改场景。场景3&#xff1a;优化资源使用的场景。场景4&#xff1a;遵守安全规则场景。 3、添加索引阻塞API4、解除设置 API5、小结6、参考 …

Transformer位置编码(Position Embedding)理解

本文主要介绍4种位置编码&#xff0c;分别是NLP发源的transformer、ViT、Sw-Transformer、MAE的Position Embedding 一、NLP transformer 使用的是1d的绝对位置编码&#xff0c;使用sincos将每个token编码为一个向量【硬编码】 Attention Is All You Need 在语言中&#xff0…

RPG Maker MV 踩坑八 仿新仙剑战斗物品指令菜单

仿新仙剑战斗物品指令菜单 遇到的坑坑一坑二解决方法 遇到的坑 上次做的额外战斗指令菜单和物品战斗指令菜单&#xff0c;突然发现一个大问题&#xff0c;漏风了&#xff01;&#xff01;&#xff01; 其实就是将底部漏出来了&#xff0c;这样整个UI就不完整了&#xff0c;算是…

微服务day04(上)-- RabbitMQ学习与入门

1.初识MQ 1.1.同步和异步通讯 微服务间通讯有同步和异步两种方式&#xff1a; 同步通讯&#xff1a;就像打电话&#xff0c;需要实时响应。 异步通讯&#xff1a;就像发邮件&#xff0c;不需要马上回复。 两种方式各有优劣&#xff0c;打电话可以立即得到响应&#xff0c;但…

深度学习 | 神经网络

一、神经网络原理 1、神经元模型 虽然叫个神经元&#xff0c;但骨子里还是线性模型。 2、神经网络结构 顾名思义就是由很多个神经元结点前后相连组成的一个网络。虽然长相上是个网络&#xff0c;但是本质上是多个线性模型的模块化组合。 在早期也被称为 多层感知机 Multi-Layer…

Visual Studio 2013 - 调试模式下根据内存地址查看内存

Visual Studio 2013 - 调试模式下根据内存地址查看内存 1. 查看内存References 1. 查看内存 调试 -> 窗口 -> 内存 -> 内存1-4 References [1] Yongqiang Cheng, https://yongqiang.blog.csdn.net/

【质押空投】公链Zkasino

据深潮TechFlow报道&#xff0c;游戏公链Zkasino HyperChain宣布上线质押挖矿活动&#xff0c;参与者可通过将ETH跨链到Zkasino链的方式来获取ZKAS代币。本次活动总共将分配25%的总代币供应量给参与者。质押挖矿时间将通过倒计时的方式来限制参与人数&#xff0c;以保护早期质押…

Datawhale 零基础入门数据挖掘-Task1 赛题理解

一、 赛题理解 Tip:此部分为零基础入门数据挖掘的 Task1 赛题理解 部分&#xff0c;为大家入门数据挖掘比赛提供一个基本的赛题入门讲解&#xff0c;欢迎后续大家多多交流。 赛题&#xff1a;零基础入门数据挖掘 - 二手车交易价格预测 地址&#xff1a;零基础入门数据挖掘 -…

【代码分享】四十七种测试函数(关注可免费获取)

智能优化算法测试函数简介 智能优化算法测试函数是为了在优化算法研究和开发中测试算法性能的规范问题集合。这些测试函数模拟了真实世界优化问题的不同方面,包括局部最小值、最大值、全局最优解,以及多种复杂性如高维度、非线性、不连续等。优化算法,如遗传算法、粒子群优…

蓝桥杯之动态规划冲刺

文章目录 动态规划01背包小练一下01背包网格图上的DP完全背包 最长公共字符串最长递增子序列 动态规划 动态规划&#xff1a;确定好状态方程&#xff0c;我们常常是确定前 当状态来到 i 时&#xff0c;前 i 个物体的状态是怎么样的&#xff0c;我们并不是从一个点去考虑&#x…

数据库基本介绍及编译安装mysql

目录 数据库介绍 数据库类型 数据库管理系统&#xff08;DBMS&#xff09; 数据库系统 DBMS的工作模式 关系型数据库的优缺点 编译安装mysql 数据库介绍 数据&#xff1a;描述事物的的符号纪录称为数据&#xff08;Data&#xff09; 表&#xff1a;以行和列的形式组成…

mysql迁移达梦数据库 Java踩坑合集

达梦数据库踩坑合集 文章目录 安装达梦设置大小写不敏感Spring boot引入达梦驱动&#xff08;两种方式&#xff09;将jar包打入本地maven仓库使用国内maven仓库&#xff08;阿里云镜像&#xff09; 达梦驱动yml配置springboot mybatis-plus整合达梦,如何避免指定数据库名&…

常用负载均衡详解

一、介绍 在互联网场景下&#xff0c;负载均衡&#xff08;Load Balance&#xff09;是分布式系统架构设计中必须考虑的一个环节&#xff0c;它通常是指将负载流量&#xff08;工作任务、访问请求&#xff09;平衡、分摊到多个操作单元&#xff08;服务器、组件&#xff09;上去…

Vue 计算属性和watch监听

1.1.计算属性 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title><!-- 引入vue.js --><script src"node_modules/vue/dist/vue.js"></script> </h…

如何在尽量不损害画质的前提下降低视频占内存大小?视频格式科普及无损压缩软件推荐

大家好呀&#xff0c;相比大家都有对视频画质和体积的追求和取舍&#xff0c;那么&#xff0c;如何才能在不牺牲画质的前提下&#xff0c;尽可能的将视频大小降低到极致呢&#xff1f; 首先我们要了解视频的构成&#xff0c;要想降低视频的体积大小&#xff0c;我们可以从以下几…

FITS:一个轻量级而又功能强大的时间序列分析模型

AI预测相关目录 AI预测流程&#xff0c;包括ETL、算法策略、算法模型、模型评估、可视化等相关内容 最好有基础的python算法预测经验 EEMD策略及踩坑VMD-CNN-LSTM时序预测对双向LSTM等模型添加自注意力机制K折叠交叉验证optuna超参数优化框架多任务学习-模型融合策略Transform…

MySQL 多表查询强化练习

环境准备 create table dept(id int PRIMARY KEY,dname VARCHAR(50),loc VARCHAR(50) ); insert into dept values (10,研发部,北京), (20,学工部, 上海), (30,销售部,广州 ), (40,财务部,深圳);create table job(id int PRIMARY KEY,jname VARCHAR(20),descripition VARCHAR(…

【web世界探险家】HTML5 探索与实践

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 | 《数据结构与算法》 | 《C生万物》 |《MySQL探索之旅》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更新的动力❤️ &…

129740-002 是ABB生产的模块吗?

ABB 129740-002是一款智能模拟量输入输出IO模块。 这款模块的主要功能是进行模拟信号与数字信号之间的转换。具体来说&#xff0c;它可以将模拟信号转换为数字信号&#xff0c;也可以将数字信号转换回模拟信号。这一特性使其在工业应用中具有重要作用&#xff0c;尤其是在过程…

Cesium新版修改源码后,编译不生效问题

最新版本的cesium源码在编译时默认使用node_models下的cesium/engine&#xff0c;从而导致咱们修改项目中的源码并不会生效 解决方式 &#xff1a; 进入到实际的源码位置&#xff0c;执行npm link 在返回到源码的根目录下执行 npm link ./packages/engine