Go 切片笔记
本文最后更新于:20 天前
什么是切片(slice)
slice和数组(array)很类似,可以用下标的方式进行访问,如果越界,就会产生panic,但是它比数组更加的灵活,可以自动的进行扩容
切片本质
切片是由指针,长度,容量组成,切片并不是数组或者数组指针,它是通过内部指针和相关属性引用数组片段,来实现变长的方案
指针
:指向底层数组
长度
:表示切片可用元素的个数,也就是会用下标对slice进行访问时候,下标不能超过的长度
容量
: 底层数组的元素个数,容量>=长度,在底层数组不进行扩容的情况下,容量也是slice可以扩张的最大限度
切片特点
一个slice 是一个轻量级的数据结构,提供了访问数组子序列元素的功能
底层引用了一个数组对象,指针指向第一个slice元素对象的底层数组元素的地址
注意
:底层数组是可以被多个 slice 同时指向的,因此对一个 slice 的元素进行操作是有可能影响到其他 slice ,
两个slice不能用==比较
切片和数组的区别
slice切片底层是数组,slice是对数组的封装,它描述了一个数组的片段,俩者都可以用下标来访问元素
数组是固定长度的,长度定义好后,不能更改
切片非常灵活,它可以动态扩容,切片的类型和长度无关
切片的创建
直接声明: var slice []int
字面量: slice1 := []int{1,2,3}
make: slice1 :=make([]int,3,5)
new: slice1 := *new([]int)
切片或者数组截取: slice1 := array1[1:4] or slice1 := slice2[1:3]
直接声明
第一种直接声明创建的slice 是nil slice ,它的长度和容量都为0,和nil 比较的结果为true
package main
import "fmt"
func main() {
var s1 []int
fmt.Println(s1 == nil)
}
//result
true
空切片
silce := make( []int , 0 )
slice := []int{ }
注意
:空切片和 nil 切片的区别在于,空切片指向的地址不是nil,指向的是一个内存地址,但是它没有分配任何内存空间,即底层元素包含0个元素
字面量
s2 := []int{1,2,3,5:10}
s3 := []int{1, 2, 3, 4, 5, 6}
s4 := []int{} //创建空切片
s5 := []string{99: 100} //初始化第100个元素
fmt.Println(s2,len(s2),cap(s2))
//
[1 2 3 0 0 10] 6 6 //s2
//唯一值得注意的是上面的代码例子中使用了索引号,直接赋值 ,这样其他未注明的元素则默认 0 值
make
make
函数需要传入三个参数:切片类型,长度,容量。当然,容量可以不传,默认和长度相等
如果使用字面量的方式创建切片,大部分的工作就都会在编译期间完成,但是当我们使用 make
关键字
创建切片时,很多工作都需要运行时的参与;调用方必须在 make
函数中传入一个切片的大小以及可选的容量
package main
import "fmt"
func main() {
slice := make([]int, 5, 10) // 长度为5,容量为10
slice[2] = 2 // 索引为2的元素赋值为2
fmt.Println(slice)
}
数组切片和切片的切片
var array = [10]int{1, 2, 3, 4, 5, 6} //定义一个数组
var s6 = array[1:4] //[2,3,4] 左闭右开
var s7 = array[4:] //[5,6,0,0,0,0]
var s8 = array[2:4:6] //data[low, high, max] low表示索引开始处闭区间,high表示len开区间,max表示容量开区间 结果分析 [3,4] -> len=2,cap=4
slice := []int{1, 2, 3, 4, 5, 6} //定义一个切片
s10 := slice[:4] //beginIndex如果为空则表示从0开始
s11 := slice[4:] //endIndex如果为空则表示到数组最后一个元素
var 12 = slice[2:4:6] //data[low, high, max] low表示索引开始处闭区间,high表示len开区间,max表示容量开区间
append追加元素
append会返回新的slice,append返回值必须使用否则编译器会报错
slice := append(slice, elem1, elem2) //可以传入多个元素
slice := append(slice, slice_other...) //可以传入一个切片 切片后面要加三个点 ...
复制切片
package main
import "fmt"
func main() {
slice1 := []string{"a","n"}
out := slice1[:]
out1 := slice1
fmt.Printf("out=%v,p=%p\n",out,&out)
fmt.Printf("out1=%v,p=%p",out1,&out1)
}
//output
out=[a n],p=0xc00000c0a0
out1=[a n],p=0xc00000c0c0
copy切片
由于 Value 是值拷贝的,并非引用传递,所以直接改 Value 是达不到更改原切片值的目的的
func main() {
array := []int{10, 20, 30, 40}
slice := make([]int, 6)
n := copy(slice, array)
fmt.Println(n,slice)
}
for- range
for循环会对slice元素值一次拷贝到item。更改item中的值不会改变原slice的元素值
slice := []int{1,2,3}
for _, item := range slice {
item++
}
fmt.Println(slice)
//output: [1,2,3]
函数传参
函数传slice是引用传参,修改被调函数的值,调用函数的slice也会改变。
func main() {
slice := []int{1,2,3}
test(slice)
fmt.Println(slice)
}
func test(a []int) {
a[1] = 100
}
//output [1,100,3]
切片坑和困惑
- 切片做函数参数是传引用
- append扩容问题,append 函数会创建一个新的底层数组,拷贝已存在的值和将要被附加的新值
package main
import "fmt"
func s(s []string) { //切片是引用传参
s[0] = "bds:234"
}
func main() {
s1 :=[]string{"123"}
s(s1)
fmt.Println(s1)
}
//output
[0 1 1]
[bds:234]
/*
切片做函数参数的时候,是使用传引用(也就是传地址)
相当于是指针指向的内存地址这个引用,由于指向的是同一块内存地址,
所以在函数内部通过s[0] = "bds:234" 修改切片,最后修改成功
*/
package main
import "fmt"
func myAppend(s []int) []int {
y := s[:1]
for _,v := range s {
y = append(y,v)
}
return y
}
func main() {
s := []int{1,2,3,4,5}
newS := myAppend(s)
fmt.Println(s)
fmt.Println(newS)
}
//output:
[1 1 1 1 1]
[1 1 1 1 1 1]
package main
import "fmt"
func Add2Slice(s []int, t int) []int{
s[0]++
s1 := append(s, t)
//fmt.Println("s1",s1)
s[0]++
return s1
}
func main() {
a := []int{0, 1, 2, 3}
c := Add2Slice(a, 4)
fmt.Println(c)
fmt.Println("a", a)
b := Add2Slice(a, 5)
fmt.Println(b)
d := []int{1}
nd := append(d, 3)
fmt.Printf("d=%v,P = %p\n", d, &d)
fmt.Printf("nd=%v,P = %p ", nd, &nd)
}
//output
[1 1 2 3 4]
a [2 1 2 3]
[3 1 2 3 5]
d=[1],P = 0xc0000a6080
nd=[1 3],P = 0xc0000a60a0
群里热心大佬分享一个考题
我的思路:
s2 = s1 此时 是相同的内存地址 相当于复制拷贝一份
append 操作了 s2 按照go的扩容规则,内存地址改变,指针指向随之发生改变
进入函数s = append(s,0) 操作s1 时候, s1被扩容,地址发生改变,所以后面s[i]++操作的是扩容后新地址切片 所以s1 还是 12
slice 形参是传引用 相当于指针变量进行复制一份,但是指针指向的内存地址是相同的 ,所以后面操作s[0]++ 相当于通过修改了内存地址里面的变量值,所以会s[i]++生效
进入函数s = append(s,0) 操作s2的时候,根据扩容规则,容量满足,地址没有发生改变,所有后面操作的是原地址切片,值s[i]++ s2 变成234
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!