Go语言切片一网打尽,别和Java语法傻傻分不清楚
2022/1/29 14:04:30
本文主要是介绍Go语言切片一网打尽,别和Java语法傻傻分不清楚,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
前言
我总想着搞清楚,什么样的技术文章才算是好的文章呢?因为写一篇今后自己还愿意阅读的文章并不容易,暂时只能以此为目标努力。
最近开始用Go刷一些题,遇到了一些切片相关的细节问题,这里做一些总结。切片的设计想法是由动态数组概念而来,为了开发者可以更加方便的使一个数据结构可以自动增加和减少。但是切片本身并不是动态数据或者数组指针。
切片的结构
type slice struct { array unsafe.Pointer // 一个指向底层数组的指针(切片中元素是存在这个指向的数组中) len int // 切片的长度:包含的元素个数 cap int // 切片的容量,len <= cap,如果len == cap,则添加元素会触发切片的扩容 }
长度为3,容量为5的int切片的图示如下,此时切片数组中可访问的部分只有下标0,1,2,超过部分不能访问。
声明和初始化
nil切片
声明nil切片,声明之后就会初始化(默认会用nil初始化),此时slice == nil成立,用于描述一个不存在的切片。
var slice []int // 此时 slice == nil 成立
空切片
声明并初始化空切片,表示一个空的集合,空切片指向地址不是nil。
slice := make([]int, 0) // 此时 slice == nil 不成立 slice := []int{}
无论是nil切片还是空切片在调用内置函数append、len和cap的效果都是一样的。
含有元素的切片
此时切片非空。
slice := []int{1, 2, 3, 4, 5} // 声明并初始化一个切片,len和cap都是5 slice := make([]int, 4) // 声明并初始化一个切片,第二个参数表示切片的长度len和容量cap都为4 slice := make([]int, 4, 6) // 声明并初始化一个切片,第二个参数表示len,第三个表示cap ----------------------------------------------------------- array := [4]int{1, 2, 3, 4} slice := array[1:2] // 对于array[i:j]来说,新切片的len=j-i,且cap=k-i(这里k是原数组的大小)
测试上面第四种初始化切片的方法:
func main() { array := [...]int{1, 2, 3, 4} slice := array[1:2] fmt.Printf("%p %d %d %v\n", &slice, len(slice), cap(slice), slice) } /* 输出: 0xc00000c030 1 3 [2] */
拷贝
使用 := 拷贝
注意:下面代码中newSlice切片是通过slice切片声明并初始化的,虽然两个切片打印的地址不同,但是切片的地址指针指向的数组是同一个。修改slice[0] = 100之后,newSlice[0]也变成100。这种规则适用于将切片作为参数传递给函数,在函数的内部使用的是传入切片的值拷贝(创建一块新的内存存放切片,但切片的地址指针指向的是同一个数组)
func main() { array := [...]int{1, 2, 3, 4} slice := array[1:2] newSlice := slice // 拷贝,newSlice由一块新的内存存放slice切片信息 /* 上面这种初始化newSlice的写法等价于: var newSlice []int newSlice = slice */ fmt.Printf("%p %d %d %v\n", &slice, len(slice), cap(slice), slice) fmt.Printf("%p %d %d %v\n", &newSlice, len(newSlice), cap(newSlice), newSlice) slice[0] = 100 fmt.Printf("%p %d %d %v\n", &slice, len(slice), cap(slice), slice) fmt.Printf("%p %d %d %v\n", &newSlice, len(newSlice), cap(newSlice), newSlice) } /* 输出: 0xc00000c030 1 3 [2] 0xc00000c048 1 3 [2] 0xc00000c030 1 3 [100] 0xc00000c048 1 3 [100] */
使用copy函数拷贝
copy函数的两个参数是两个切片(将第二个切片的值覆盖到第一个切片),二者地址指针指向两个不同的数组。
func main() { slice1 := []int{1, 2, 3, 4, 5} slice2 := []int{5, 4, 3} fmt.Printf("%d %d %p %v\n", len(slice1), cap(slice1), &slice1, slice1) fmt.Printf("%d %d %p %v\n", len(slice2), cap(slice2), &slice2, slice2) //copy(slice2, slice1) // 只会复制slice1的前3个元素到slice2中 copy(slice1, slice2) // 只会复制slice2的3个元素到slice1的前3个位置 fmt.Printf("%d %d %p %v\n", len(slice1), cap(slice1), &slice1, slice1) //fmt.Printf("%d %d %p %v\n", len(slice2), cap(slice2), &slice2, slice2) slice2[0] = 200 slice1[0] = 100 fmt.Printf("%d %d %p %v\n", len(slice1), cap(slice1), &slice1, slice1) fmt.Printf("%d %d %p %v\n", len(slice2), cap(slice2), &slice2, slice2) } /* 输出: 5 5 0xc00000c018 [1 2 3 4 5] 3 3 0xc00000c030 [5 4 3] 5 5 0xc00000c018 [5 4 3 4 5] 5 5 0xc00000c018 [100 4 3 4 5] copy函数的两个切片的地址指针分别指向两个不同的数组(修改值互不影响) 3 3 0xc00000c030 [200 4 3] */
扩容
扩容使用append方法。
len == cap 时添加元素
func main() { slice := []int{1, 2, 3} // 此时slice的len == cap == 3,append元素会触发扩容,扩容后切片地址指向新数组(具体扩容策略这里暂时不多深入) fmt.Printf("%p %d %d %v\n", &slice, len(slice), cap(slice), slice) newSlice := append(slice, 1) fmt.Printf("%p %d %d %v\n", &newSlice, len(newSlice), cap(newSlice), newSlice) slice[0] = 100 fmt.Printf("%p %d %d %v\n", &slice, len(slice), cap(slice), slice) fmt.Printf("%p %d %d %v\n", &newSlice, len(newSlice), cap(newSlice), newSlice) } /* 输出: 0xc00000c030 3 3 [1 2 3] 0xc00000c060 4 6 [1 2 3 1] // append一个元素之后,切片中指针指向一个扩容后新的数组(地址指针变化,这里打印出的是切片地址,无论是否扩容newSlice和slice内存地址必然是不同的,和二者拥有的地址指针不要搞混) 0xc00000c030 3 3 [100 2 3] // 修改slice[0]的元素为100 0xc00000c060 4 6 [1 2 3 1] // 但是newSlice[0]中的元素没有变化(这里才可以证明两个切片的地址指针指向两个不同的数组) */
len < cap 时添加元素
此时调用append方法添加一个元素1并不会创建新的数组,但是1会去覆盖掉array[2]。
func main() { array := [4]int{1, 2, 3, 4} slice := array[0:2] fmt.Printf("%p %d %d %v\n", &slice, len(slice), cap(slice), slice) newSlice := append(slice, 1) fmt.Printf("%p %d %d %v\n", &newSlice, len(newSlice), cap(newSlice), newSlice) slice[0] = 100 fmt.Printf("%p %d %d %v\n", &slice, len(slice), cap(slice), slice) fmt.Printf("%p %d %d %v\n", &newSlice, len(newSlice), cap(newSlice), newSlice) fmt.Println("array = ", array) } /* 输出: 0xc00000c030 2 4 [1 2] // 这里看到从数组中初始化的slice的len==2且cap==4 0xc00000c060 3 4 [1 2 1] // append之后,添加一个元素,len变为3(且append元素会覆盖底层数组,这里1覆盖了之前的3) 0xc00000c030 2 4 [100 2] // 注意,这里slice打印出来的len还是2,slice的len和cap与newSlice的len和cap是隔离的,尽管它们地址指针指向的是同一个数组,这里修改了slice[0]=100 0xc00000c060 3 4 [100 2 1] // newSlice[0]也被修改为100 array = [100 2 1 4] // 证明指向的array数组也被修改了 */
结束
快过年了,祝大家新年快乐,春招offer拿不停。
建了一个春秋招备战/内推/闲聊群,欢迎大家加入。
关注公众号【程序员白泽】,带你走近一个有点话痨的程序员/学生党。
这篇关于Go语言切片一网打尽,别和Java语法傻傻分不清楚的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23Springboot应用的多环境打包入门
- 2024-11-23Springboot应用的生产发布入门教程
- 2024-11-23Python编程入门指南
- 2024-11-23Java创业入门:从零开始的编程之旅
- 2024-11-23Java创业入门:新手必读的Java编程与创业指南
- 2024-11-23Java对接阿里云智能语音服务入门详解
- 2024-11-23Java对接阿里云智能语音服务入门教程
- 2024-11-23JAVA对接阿里云智能语音服务入门教程
- 2024-11-23Java副业入门:初学者的简单教程
- 2024-11-23JAVA副业入门:初学者的实战指南