slice基础
//slice的结构
// runtime/slice.go
// 切片是一个结构体,指针字段指向底层一个数组的首元素地址
type slice struct {
array unsafe.Pointer //指向底层数组首元素地址(指针类型)
len int
cap int
}
//slice是引用类型, 只能和nil比较, 要使用必须先(make)初始化
func main() {
var arr []int
fmt.Println(arr == nil)
}
//true
// slice之间无法比较
func main() {
var arr []int
var arr2 []int
fmt.Println(arr == arr2)
}
//./main.go:10:18: invalid operation: arr == arr2 (slice can only be compared to nil)
//指针字段指向底层一个数组的首元素地址
// 数组的地址, 即为数组的首元素的地址
func main() {
arr := [...]int{0, 1, 2, 3}
fmt.Printf("%p
", &arr) //0xc00000c420
fmt.Printf("%p
", &arr[0]) //0xc00000c420
}
//指针字段指向底层一个数组的首元素地址
func main() {
arr := make([]int, 3, 5)
arr[0] = 10
arr[1] = 20
arr[2] = 30
fmt.Printf("%p
", arr)
fmt.Printf("%p
", &arr[0])
}
//0xc00001a120
//0xc00001a120
小结: &arr == &arr[0]
//slice是可变长的array, 它的len和cap属性可变.
make([]T, len, cap) // len参数必须, cap参数可选
// len: 用于限定可读写的元素数量.
// cap: 表示切片所引用数组片段的真实长度. append扩展cap长度
// 默认len==cap
func main() {
arr := make([]int, 3)
fmt.Println(arr, len(arr), cap(arr))
}
//[0 0 0] 3 3
// slice超出可读写范围len,报错
func main() {
arr := make([]int, 3, 5)
arr[0] = 10
arr[1] = 20
arr[2] = 30
arr[3] = 40
}
//panic: runtime error: index out of range [3] with length 3
func main() {
arr := make([]int, 3, 5)
arr[0] = 10
arr[1] = 20
arr[2] = 30
arr = append(arr, 30)
fmt.Println(arr, len(arr), cap(arr))
}
//[10 20 30 30] 4 5
//append函数doc
slice = append(slice, elem1, elem2)
slice = append(slice, anotherSlice...)
//字节序列
// As a special case, it is legal(合法的) to append a string to a byte slice, like this:
slice = append([]byte("hello "), "world"...)
- append函数
如果要扩容slice, 使用append函数(making slice a dynamic data structure). 当append时: - 如果cap够, append使用原数组(此原数组元素有被覆盖的风险, 因此 make([],3,3)即len==cap时安全.)
- 如果cap不够, append会新开辟一个backend array内存空间(原数组元素不会被覆盖). 并重置cap. 当cap<1000时, 成倍的增长cap; 当cap>1000,增长因子为1.25.
//slice扩容: 一次append多个元素
func main() {
var arr []int
arr = append(arr, 1, 2, 3)
fmt.Println(arr)
}
//[1 2 3]
//slice扩容: 合并两个slice
func main() {
arr:=[]int{1,2,3}
arr2:=[]int{4,5,6}
arr = append(arr, arr2...)
fmt.Println(arr)
}
//[1 2 3 4 5 6]
//类似的用法:
//例子: 函数传参
//不定参数
func test(i int, arr ...int) {
fmt.Printf("%T, %v", arr, arr)
}
func main() {
test(1, 2, 3) //不定参数
}
//[]int, [2 3]
//传数组
func test(arr ...int) {
fmt.Printf("%T, %v", arr, arr)
}
func main() {
arr := []int{1, 2, 3}
test(arr...)
}
//[]int, [1 2 3]
// nil slice(指向nil)
func main() {
var arr []int
arr[0] = 1
fmt.Println(arr)
}
//panic: runtime error: index out of range [0] with length 0
// nil slice(指向nil)
func main() {
arr := *(new([]int))
arr[0] = 1
}
//panic: runtime error: index out of range [0] with length 0
// empty slice(有内存地址分配)
func main() {
arr := []int{}
arr[0] = 1
fmt.Println(arr)
}
//panic: runtime error: index out of range [0] with length 0
// empty slice(有内存地址分配)
func main() {
arr := make([]int, 0)
arr[0] = 1
fmt.Println(arr)
}
//panic: runtime error: index out of range [0] with length 0
func main() {
var a[]int
b:= []int{}
println(a==nil,b==nil)
}
//true false
// 向nil slice append元素
func main() {
var arr []int
arr = append(arr, 1)
fmt.Println(arr, len(arr), cap(arr))
}
//[1] 1 1
//向epmty slice append元素
func main() {
arr:=[]int{}
arr = append(arr, 1)
fmt.Println(arr, len(arr), cap(arr))
}
//[1] 1 1
小结:
以上两种情况使用append都可以将 切片 化身为“真正”的 slice
append会调用mallocgc开辟内存
//从数组/切片reslice
// 从数组/切片获取
arr = [i,j,k]
len = j-i
cap = k-i
// 一个例子
x:= [...]int{0,1,2,3,4,5,6,7,8,9}
操作 得到的切片 len cap 备注
-------------+--------------------------+----+------+------------------------------
x[:] [0 1 2 3 4 5 6 7 8 9] 10 10 x[0:len(x)]
x[2:5] [2 3 4] 3 8
x[2:5:7] [2 3 4] 3 5
x[4:] [4 5 6 7 8 9] 6 6 x[4:len(x)]
x[:4] [0 1 2 3] 4 10 x[0:4]
x[:4:6] [0 1 2 3] 4 6 x[0:4:6]
-
使用 arr = [i,j], 注 这里的默认cap,是原始arr的长度-i
-
使用arr = [i,j,k]
-
使用arr = [i,j,k], 设置j=k, 即len=cap的好处: 修改切片内容时, 避免影响其他切片.
//从slice中删除元素
func main() {
// 从切片中删除元素
a := []int{30, 31, 32, 33, 34, 35, 36, 37}
// 要删除索引为2的元素
a = append(a[:2], a[3:]...)
fmt.Println(a) //[30 31 33 34 35 36 37]
}
slice是引用类型
//slice的拷贝
//slice结构占内存大小(byte)
func main() {
fmt.Println(unsafe.Sizeof([]int{}))
}
//24
//slice在64bit系统上占24个字节(struct 内存对齐)
type slice struct {
array unsafe.Pointer //8byte
len int //8byte
cap int //8byte
}
unsafe.Pointer = 1 word
在 Go 中,指针就是 uintptr 类型。同样地,基于操作系统的体系结构,它将映射为 uint32 或者 uint64。
int = 1 word
//word大小根据操作系统被映射为4byte或8byte
32bit: 1word = 4byte
64bit: 1word = 8byte
小结: 切片数据包含在与切片关联的底层数组里, 拷贝切片时, 不会涉及底层数组数组的拷贝, 仅拷贝24bytes
//指定索引初始化
func main() {
b := []int{1, 2, 5: 10}
fmt.Println(b)
}
//[1 2 0 0 0 10]
切片是一个结构体, 切片在64bit系统下占用24bytes. 与切片关联的数据存放在底层数组里,不属于切片本身.
所以将切片赋值到任意变量时, 对底层数组的大小都不会有影响, 赋值时只会复制切片本身, 不会涉及底层数组里数据.
- slice函数参数传递
Go 语言的函数参数传递,只有值传递,没有引用传递。
函数形参是一个局部变量, 调用函数时, 会将变量拷贝一份, 赋值给函数形参.
这里slice的拷贝, 仅仅是拷贝slice的结构体, 不会涉及与slice关联的底层数组的数据.
// 函数foo接收一个整型切片,并返回这个切片
func foo(slice []int) []int {
...
return slice
}
func main(){
// 分配包含100万个整型值的切片
slice := make([]int, 1e6)
// 将slice传递到函数foo
slice = foo(slice)
}
- copy: 将slice关联的底层数组克隆一份副本出来赋给新arr
func main() {
// copy()复制切片
a := []int{1, 2, 3, 4, 5}
c := make([]int, 5, 5)
copy(c, a) //使用copy()函数将切片a中的元素复制到切片c
fmt.Println(a) //[1 2 3 4 5]
fmt.Println(c) //[1 2 3 4 5]
c[0] = 1000 //互不干扰
fmt.Println(a) //[1 2 3 4 5]
fmt.Println(c) //[1000 2 3 4 5]
}
- 在两个切片对象间复制数据,允许指向同一底层数组,允许目标区间重叠。最终所复制长度以较短的切片长度(len)为准。
copy([1,2,3], [4,5]) // [4,5,3]
copy([1,2,3], [4,5,6,7]) // [4,5,6]
func main() {
s:= []int{0,1,2,3,4,5,6,7,8,9}
s1:=s[5:8]
n:=copy(s[4:],s1) // 在同一底层数组的不同区间复制
fmt.Println(n,s)
s2:=make([]int,6) // 在不同数组间复制
n=copy(s2,s)
fmt.Println(n,s2)
}
- 还可直接从字符串中复制数据到[]byte。
func main() {
b:=make([]byte,3)
n:=copy(b, "abcde")
fmt.Println(n,b) //3 [97 98 99]
}
slice的遍历
- 由于slice数据存储在与之关联的底层数组里, 因此元素内存地址连续
func main() {
arr := []int{1, 2, 3}
fmt.Printf("%p
", &arr[0])
fmt.Printf("%p
", &arr[1])
fmt.Printf("%p
", &arr[2])
}
//0xc00000c420
//0xc00000c428
//0xc00000c430
- for range遍历是对元素引用和元素值的拷贝(副本), 而不是 元素地址的拷贝
func main() {
arr := []int{1, 2, 3}
for k, v := range arr {
fmt.Printf("v ptr: %p, elem prt: %p
", &v, &arr[k])
}
}
//v ptr: 0xc0000140c0, elem prt: 0xc00000c420
//v ptr: 0xc0000140c0, elem prt: 0xc00000c428
//v ptr: 0xc0000140c0, elem prt: 0xc00000c430
- 通过for range初始化,为什么输出的的值都指向users的第3项?
type user struct {
name string
age int
}
func main() {
//初始化users
users := []user{
{"m1", 1},
{"m2", 2},
{"m3", 3},
}
//初始化m
m := map[int]*user{}
for k, v := range users {
//1. 为v开辟地址空间 0xc0000044a0
// fmt.Printf("%p
", &v) //0xc0000044a0
//2. 将users的每一项值的副本,放到这个地址空间里
m[k] = &v //v本身是一个固定的内存地址空间
}
//遍历m
for _, v := range m {
fmt.Println(v.name, v.age)
}
}
//m3 3
//m3 3
//m3 3
多级指针模型
func main() {
a := 10
p := &a
pp := &p
fmt.Println(&a)
fmt.Println(p)
fmt.Println(pp)
}
//0xc00008a008
//0xc00008a008
//0xc000082018