Go slice切片详解和实战:make append copy

简介: 这篇文章介绍切片的生成make()、切片的追加append()、切片的复制copy()。对知识点进行详细介绍和应用实战。

这篇文章介绍切片的生成make()、切片的追加append()、切片的复制copy()。对知识点进行详细介绍和应用实战。


加深理解


  1. 切片的本质:切片的本质是一个框,框住了一块连续的内存
  2. 切片属于引用类型,真正的数据都是保存在底层数组里的
  3. 切片可以简单理解为是快捷方式,修改会互相影响
  4. 判断一个切片是否为空,使用len(s) == 0 判断,不能使用 s==nil 判断


生成切片 make


上需求:请定义一个长度为5,容量为10的整型切片。

上代码:


s1 := make([]int,5,10)
fmt.Printf("s1:%v len(s1):%d cap(s1):%d\n", s1, len(s1), cap(s1))


打印结果:


微信图片_20221112142223.jpg


分析:make()函数的第一个参数指定切片的数组类型,第二个参数指定切片的长度,第三个参数指定切片的容量。


更好的理解长度和容量


s1 := make([]int,5,10)
fmt.Printf("s1:%v len(s1):%d cap(s1):%d\n", s1, len(s1), cap(s1))
s2 := make([]int, 0, 10)
fmt.Printf("s2=%v len(s2)=%d cap(s2)=%d\n", s2, len(s2), cap(s2))


打印结果:


微信图片_20221112142234.jpg


分析: 我们可以发现定义切片时元素的个数和长度相关,因为长度就是元素的个数。

容量我们在下面介绍append()时,重点介绍一下。


切片引用类型实战


上代码


//切片
s3 := make([]int, 3, 3)
s3 = []int{1, 2, 3}
s4 := s3            //s3 s4都指向同一个底层数组
fmt.Println(s3, s4) //[1 2 3]  [1 2 3]
s3[0] = 1000
fmt.Println(s3, s4) //[1000 2 3] [1000 2 3]
fmt.Println("-----")
//数组
a3 := [3]int{1, 2, 4}
a4 := a3            //数组类型 a4会开辟新的内存空间
fmt.Println(a3, a4) //[1 2 4] [1 2 4]
a3[0] = 1000
fmt.Println(a3, a4) //[1000 2 4] [1 2 4]


打印结果:


微信图片_20221112142238.jpg


分析:通过上面的打印结果我们可以很直观的看出来,切片引用类型的本质,当切片修改时会互相影响;而数组作为值类型,不会互相影响。


切片的遍历


和数组一样,用fori或者for range进行遍历即可。


s3 := make([]int, 3, 3)
s3 = []int{1, 2, 3}
for i := 0; i < len(s3); i++ {
   fmt.Println(s3[i])
}
for i, v := range s3 {
   fmt.Println(i, v)
}


微信图片_20221112142241.jpg


append


首先,我们定义一个切片


s1 := []string{"北京", "上海", "大连", "佛山"}
fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1))


打印结果:


微信图片_20221112142244.jpg


分析:我们发现切片的长度和容量都是4


然后,我们使用append()函数追加一个元素


s1 := []string{"北京", "上海", "大连", "佛山"}
fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1))
s1 = append(s1, "唐山") //切片append()追加之后,
fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1))


打印结果:


微信图片_20221112142247.jpg


分析:长度由4变成5,我们很好理解;容量为什么会从4变成8呢?


这是Go语言对切片的自动扩容机制。append()追加元素,原来的底层数据容量不够时,go底层会把底层数组替换,是go语言的一套扩容策略


我后面会单独写一篇来讲自动扩容策略是如何实现的。欢迎大家持续关注我的专栏Go语言学习专栏


多次追加


多次追加的概念很好理解,就是多次调用append()

举个栗子:


s1 := []string{"北京", "上海", "大连", "佛山"}
fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1))
s1 = append(s1, "唐山") //切片append()追加之后,
fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1))
var s2 []string
s2 = append(s1, "雄安")
fmt.Printf("s2=%v len(s2)=%d cap(s2)=%d\n", s2, len(s2), cap(s2))


微信图片_20221112142250.jpg

打印结果:


分析: s1经过两次append追加元素,赋值给了s2


追加多个元素


当我们需要追加多个元素时,难道只能像上面这样多次调用append吗?

当然不是的。

举个栗子:


s1 := []string{"北京", "上海", "大连", "佛山"}
s3 := []string{"太原","石家庄"}
var s4 []string
s4 = append(s1,s3...) // ...表示拆开,将切片的值作为追加的元素
fmt.Printf("s4的值:%v",s4)


打印结果:


微信图片_20221112142254.jpg


注意:append的第二个参数,我们传入了一个切片,需要在切片后写死...,表示将切片切割,将切片的值作为追加到第一个参数中的元素。


复制切片


下面演示两种方式:


//定义切片s1
s1 := []int{1, 2, 3}
//第一种方式:直接声明变量 用=赋值
//s2切片和s1引用同一个内存地址
var s2 = s1
//第二种方式:copy
var s3 = make([]int, 3)
copy(s3, s1)            //使用copy函数将 参数2的元素复制到参数1
fmt.Println(s1, s2, s3) //都是[1 2 3]


打印结果:都返回[1 2 3]


微信图片_20221112142257.jpg


注意:var和:=不能同时使用,这种是错误的写法 :var s3 := make([]int, 5)

聪明的小伙伴们是不是提出疑问了呢?

既然结果一样,为什么要引出copy这个函数呢?

咱们接着往下看,就知道所以然了。


//定义切片s1
s1 := []int{1, 2, 3}
//第一种方式:直接声明变量 用=赋值
//s2切片和s1引用同一个内存地址
var s2 = s1
//第二种方式:copy
var s3 = make([]int, 3)
copy(s3, s1)            //使用copy函数将 参数2的元素复制到参数1
s1[0] = 11
fmt.Printf("s1:%v s2:%v s3:%v",s1, s2, s3) //s1和s2是[11 2 3] s3是[1 2 3]


打印结果:


微信图片_20221112142300.jpg


分析: 我们发现s1和s2是[11 2 3] s3是[1 2 3],说明copy方法是复制了一份,开辟了新的内存空间,不再引用s1的内存地址,这就是两者的区别。


删除元素


注意:删除切片中的元素 不能直接删除 可以组合使用分割+append的方式删除切片中的元素

举个栗子:比如切除s3中的元素2(下标为1的元素)


s3 := []int{1, 2, 3}
s3 = append(s3[:1], s3[2:]...) //第一个不用拆开 原因是一个作为被接受的一方  是把后面的元素追加到第一个
fmt.Println(s3)                


打印结果:


微信图片_20221112142303.jpg


注意:上面代码段中有我添加的注释:append()函数中第一个切片不用拆开,原因是一个作为被接受的一方,是把后面的元素追加到第一个切片中。


数组转切片


a1 := [...]int{1,2,3}
s1 := a1[:]
fmt.Printf("a1类型:%T\ns1类型:%T",a1,s1)


打印结果:

微信图片_20221112142306.jpg


实战演练


猜想一下下面程序的运行结果:


s1 := make([]int, 5, 10)
for i := 0; i < 10; i++ {
   s1 = append(s1, i)
}
fmt.Println(s1)


.

.

.

打印结果:


微信图片_20221112142310.jpg


分析: 我们静下心来逐步推导就ok了:


  1. s1 := make([]int, 5, 10) 生成的是切片: [0 0 0 0 0]
  2. for循环中每次将自增的i值追加到上面的切片中
  3. 所以最终打印的结果是:[0 0 0 0 0 0 1 2 3 4 5 6 7 8 9]


总结


这篇文章汇总了使用make生成切片、使用append追加切片元素、使用copy复制切片,开辟新的内存空间、使用切片分割和append来删除切片。


公众号:程序员升级打怪之旅

微信号:wangzhongyang1993


相关文章
|
3月前
|
Linux Go iOS开发
Go语言100个实战案例-进阶与部署篇:使用Go打包生成可执行文件
本文详解Go语言打包与跨平台编译技巧,涵盖`go build`命令、多平台构建、二进制优化及资源嵌入(embed),助你将项目编译为无依赖的独立可执行文件,轻松实现高效分发与部署。
|
4月前
|
数据采集 数据挖掘 测试技术
Go与Python爬虫实战对比:从开发效率到性能瓶颈的深度解析
本文对比了Python与Go在爬虫开发中的特点。Python凭借Scrapy等框架在开发效率和易用性上占优,适合快速开发与中小型项目;而Go凭借高并发和高性能优势,适用于大规模、长期运行的爬虫服务。文章通过代码示例和性能测试,分析了两者在并发能力、错误处理、部署维护等方面的差异,并探讨了未来融合发展的趋势。
391 0
|
4月前
|
存储 人工智能 Go
Go-Zero全流程实战即时通讯
Go-Zero 是一个功能丰富的微服务框架,适用于开发高性能的即时通讯应用。它具备中间件、工具库和代码生成器,简化开发流程。本文介绍其环境搭建、项目初始化及即时通讯功能实现,涵盖用户认证、消息收发和实时推送,帮助开发者快速上手。
354 0
|
3月前
|
存储 前端开发 JavaScript
Go语言实战案例-项目实战篇:编写一个轻量级在线聊天室
本文介绍如何用Go语言从零实现一个轻量级在线聊天室,基于WebSocket实现实时通信,支持多人消息广播。涵盖前后端开发、技术选型与功能扩展,助你掌握Go高并发与实时通信核心技术。
|
4月前
|
负载均衡 监控 Java
微服务稳定性三板斧:熔断、限流与负载均衡全面解析(附 Hystrix-Go 实战代码)
在微服务架构中,高可用与稳定性至关重要。本文详解熔断、限流与负载均衡三大关键技术,结合API网关与Hystrix-Go实战,帮助构建健壮、弹性的微服务系统。
560 1
微服务稳定性三板斧:熔断、限流与负载均衡全面解析(附 Hystrix-Go 实战代码)
|
4月前
|
安全 Go 开发者
Go语言实战案例:使用sync.Mutex实现资源加锁
在Go语言并发编程中,数据共享可能导致竞态条件,使用 `sync.Mutex` 可以有效避免这一问题。本文详细介绍了互斥锁的基本概念、加锁原理及实战应用,通过构建并发安全的计数器演示了加锁与未加锁的区别,并封装了一个线程安全的计数器结构。同时对比了Go中常见的同步机制,帮助开发者理解何时应使用 `Mutex` 及其注意事项。掌握 `Mutex` 是实现高效、安全并发编程的重要基础。
|
4月前
|
数据采集 Go API
Go语言实战案例:使用context控制协程取消
本文详解 Go 语言中 `context` 包的使用,通过实际案例演示如何利用 `context` 控制协程的生命周期,实现任务取消、超时控制及优雅退出,提升并发程序的稳定性与资源管理能力。
|
4月前
|
数据采集 Go API
Go语言实战案例:多协程并发下载网页内容
本文是《Go语言100个实战案例 · 网络与并发篇》第6篇,讲解如何使用 Goroutine 和 Channel 实现多协程并发抓取网页内容,提升网络请求效率。通过实战掌握高并发编程技巧,构建爬虫、内容聚合器等工具,涵盖 WaitGroup、超时控制、错误处理等核心知识点。
|
4月前
|
Go 开发者
Go语言实战案例:使用select监听多个channel
本文为《Go语言100个实战案例 · 网络与并发篇》第5篇,详解Go并发核心工具`select`的使用。通过实际案例讲解如何监听多个Channel、实现多任务处理、超时控制和非阻塞通信,帮助开发者掌握Go并发编程中的多路异步事件处理技巧。
|
4月前
|
数据采集 编解码 监控
Go语言实战案例:使用channel实现生产者消费者模型
本文是「Go语言100个实战案例 · 网络与并发篇」第4篇,通过实战案例详解使用 Channel 实现生产者-消费者模型,涵盖并发控制、任务调度及Go语言并发哲学,助你掌握优雅的并发编程技巧。

热门文章

最新文章