[go]slice

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\n", &arr)    //0xc00000c420
	fmt.Printf("%p\n", &arr[0]) //0xc00000c420
}
//指针字段指向底层一个数组的首元素地址
func main() {
	arr := make([]int, 3, 5)
	arr[0] = 10
	arr[1] = 20
	arr[2] = 30

	fmt.Printf("%p\n", arr)
	fmt.Printf("%p\n", &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

[go]struct内存对齐

//指定索引初始化

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\n", &arr[0])
	fmt.Printf("%p\n", &arr[1])
	fmt.Printf("%p\n", &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\n", &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\n", &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

posted @ 2020-01-10 22:35  mmaotai  阅读(200)  评论(0编辑  收藏  举报