• [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
    ", &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
    

    [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
    ", &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
    

  • 相关阅读:
    招聘.Net中高级软件研发工程师
    布局和救火
    UITableView详解(转)
    iOS开发那些事--性能优化–内存泄露问题的解决(转)
    LeeCode(PHP) 2.add-two-numbers
    LeeCode(PHP) 1.Two Sum
    PHP实现 序列帧拆分
    PHPExcel导出大量数据超时及内存错误解决方法(转)
    laravel路由 实现短连接生成及跳转(php 301重定向)
    从扑克牌中随机抽取5张牌,判断是不是一个顺子,即这5张牌是不是连续(面试题)
  • 原文地址:https://www.cnblogs.com/iiiiiher/p/12178432.html
Copyright © 2020-2023  润新知