当前位置:   article > 正文

Go语言切片Slice_go 切片初始化

go 切片初始化

1、Go语言切片Slice

Go 语言切片是对数组的抽象。

Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切

片(“动态数组”),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

1.1 定义切片

你可以声明一个未指定大小的数组来定义切片:

var identifier []type
  • 1

切片不需要说明长度。

或使用 make() 函数来创建切片:

var slice1 []type = make([]type, len)
// 也可以简写为
slice1 := make([]type, len)
  • 1
  • 2
  • 3

也可以指定容量,其中 capacity 为可选参数。

make([]T, length, capacity)
  • 1

这里 len 是数组的长度并且也是切片的初始长度。

1.2 切片初始化

s :=[] int {1,2,3 }
  • 1

直接初始化切片,[] 表示是切片类型,{1,2,3} 初始化值依次是 1,2,3,其 cap=len=3。

s := arr[:]
  • 1

初始化切片 s,是数组 arr 的引用。

s := arr[startIndex:endIndex]
  • 1

将 arr 中从下标 startIndex 到 endIndex-1 下的元素创建为一个新的切片。

s := arr[startIndex:] 
  • 1

默认 endIndex 时将表示一直到arr的最后一个元素。

s := arr[:endIndex] 
  • 1

默认 startIndex 时将表示从 arr 的第一个元素开始。

s1 := s[startIndex:endIndex]
  • 1

通过切片 s 初始化切片 s1。

s :=make([]int,len,cap) 
  • 1

通过内置函数 make() 初始化切片 s,[]int 标识为其元素类型为 int 的切片。

1.3 len() 和 cap() 函数

切片是可索引的,并且可以由 len() 方法获取长度。

切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。

package main

import "fmt"

func main() {
	var numbers = make([]int, 3, 5)
	printSlice(numbers)
}

func printSlice(x []int) {
	// len=3 cap=5 slice=[0 0 0]
	fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

1.4 空(nil)切片

一个切片在未初始化之前默认为 nil,长度为 0。

package main

import "fmt"

func main() {
	var numbers []int
	// len=0 cap=0 slice=[]
	printSlice(numbers)
	if numbers == nil {
		// 切片是空的
		fmt.Printf("切片是空的")
	}
}

func printSlice(x []int) {
	fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

1.5 切片截取

可以通过设置下限及上限来设置截取切片 [lower-bound:upper-bound]。

package main

import "fmt"

func main() {
	/* 创建切片 */
	numbers := []int{0, 1, 2, 3, 4, 5, 6, 7, 8}
	// len=9 cap=9 slice=[0 1 2 3 4 5 6 7 8]
	printSlice(numbers)
	/* 打印原始切片 */
	// numbers == [0 1 2 3 4 5 6 7 8]
	fmt.Println("numbers ==", numbers)
	/* 打印子切片从索引1(包含) 到索引4(不包含)*/
	// numbers[1:4] == [1 2 3]
	fmt.Println("numbers[1:4] ==", numbers[1:4])
	/* 默认下限为 0*/
	// numbers[:3] == [0 1 2]
	fmt.Println("numbers[:3] ==", numbers[:3])
	/* 默认上限为 len(s)*/
	// numbers[4:] == [4 5 6 7 8]
	fmt.Println("numbers[4:] ==", numbers[4:])
	numbers1 := make([]int, 0, 5)
	// len=0 cap=5 slice=[]
	printSlice(numbers1)
	/* 打印子切片从索引  0(包含) 到索引 2(不包含) */
	number2 := numbers[:2]
	// len=2 cap=9 slice=[0 1]
	// cap=9-0=9(0为start的下标)
	printSlice(number2)
	/* 打印子切片从索引 2(包含) 到索引 5(不包含) */
	number3 := numbers[2:5]
	// len=3 cap=7 slice=[2 3 4]
	// cap=9-2=7(2为start的下标)
	printSlice(number3)
}

func printSlice(x []int) {
	fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

我们可以看出切片,实际的是获取数组的某一部分,len切片<=cap切片<=len数组,切片由三部分组成:指向底

层数组的指针、len、cap。

1.6 append() 和 copy() 函数

如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。

下面的代码描述了从拷贝切片的 copy 方法和向切片追加新元素的 append 方法。

package main

import "fmt"

func main() {
	var numbers []int
	// len=0 cap=0 slice=[]
	printSlice(numbers)
	/* 允许追加空切片 */
	numbers = append(numbers, 0)
	// len=1 cap=1 slice=[0]
	printSlice(numbers)
	/* 向切片添加一个元素 */
	numbers = append(numbers, 1)
	// len=2 cap=2 slice=[0 1]
	printSlice(numbers)
	/* 同时添加多个元素 */
	numbers = append(numbers, 2, 3, 4)
	// len=5 cap=6 slice=[0 1 2 3 4]
    // len(list)+len([params])为奇数
    // cap=len(list)+len([params])+1=2+3+1=6
	printSlice(numbers)
	/* 创建切片 numbers1 是之前切片的两倍容量*/
	numbers1 := make([]int, len(numbers), (cap(numbers))*2)
	/* 拷贝 numbers 的内容到 numbers1 */
	copy(numbers1, numbers)
	// len=5 cap=12 slice=[0 1 2 3 4]
	printSlice(numbers1)
}

func printSlice(x []int) {
	fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

合并多个数组:

package main

import "fmt"

func main() {
	var arr1 = []int{1, 2, 3}
	var arr2 = []int{4, 5, 6}
	var arr3 = []int{7, 8, 9}
	var s1 = append(append(arr1, arr2...), arr3...)
	// s1: [1 2 3 4 5 6 7 8 9]
	fmt.Printf("s1: %v\n", s1)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

使用 copy 函数要注意对于 copy(dst, src),要初始化 dst 的 size,否则无法复制。

// 错误示例
package main

import "fmt"

func main() {
	dst := make([]int, 0)
	src := []int{1, 2, 3}
	copy(dst, src)
	// len=3 cap=3 slice=[1 2 3]
	printSlice(src)
	// len=0 cap=0 slice=[]
	printSlice(dst)
}

func printSlice(x []int) {
	fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
// 正确示例
package main

import "fmt"

func main() {
	// 令size=3
	dst := make([]int, 3)
	src := []int{1, 2, 3}
	copy(dst, src)
	// len=3 cap=3 slice=[1 2 3]
	printSlice(src)
	// len=3 cap=3 slice=[1 2 3]
	printSlice(dst)
}

func printSlice(x []int) {
	fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

1.7 切片截取的长度问题

我们基于原数组或者切片创建一个新的切片后,那么新的切片的大小和容量是多少呢?

这里有个公式,对于底层数组容量是 k 的切片 slice[i:j] 来说:

长度: j-i
容量: k-i
  • 1
  • 2

实例:

package main

import "fmt"

func main() {
	numbers := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	// len=11  cap=11   slice=[0 1 2 3 4 5 6 7 8 9 10]
	printSlice(numbers)
	// [1 2]
	fmt.Printf("%d\n", numbers[1:3])
	// [2 3 4 5 6]
	fmt.Printf("%d\n", numbers[2:7])
	// [0 1 2]
	fmt.Printf("%d\n", numbers[:3])
	// [4 5 6 7 8 9 10]
	fmt.Printf("%d\n", numbers[4:])
	number1 := make([]int, 0, 5)
	number2 := numbers[:3]
	// len=0  cap=5   slice=[]
	printSlice(number1)
	// len=3  cap=11   slice=[0 1 2]
	// cap=11-0=11
	printSlice(number2)
	number3 := numbers[2:5]
	// len=3  cap=9   slice=[2 3 4]
	// capacity为9是因为number3的ptr指向第2个元素,后面还剩2,3,4,5,6,7,8,9,10, 所以 cap=9
	// cap=11-2=9
	printSlice(number3)
	number4 := numbers[3:8]
	// len=5  cap=8   slice=[3 4 5 6 7]
	// cap=11-3=8
	printSlice(number4)
}

func printSlice(x []int) {
	fmt.Printf("len=%d  cap=%d   slice=%v\n", len(x), cap(x), x)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

1.8 append函数长度问题

package main

import "fmt"

func main() {
	numbers := []int{0, 1}
	// numbers的容量:  2
	fmt.Println("numbers的容量: ", cap(numbers))
	// numbers的长度:  2
	fmt.Println("numbers的长度: ", len(numbers))
	numbers = append(numbers, 2, 3, 4)
	// numbers的容量:  6
	fmt.Println("numbers的容量: ", cap(numbers))
	// numbers的长度:  5
	fmt.Println("numbers的长度: ", len(numbers))
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

当 numbers = [0, 1] 时,append(numbers, 2, 3, 4) 为什么 cap 从 2 变成 6?

append(list, [params]) 最终cap的计算:

1、当同时添加多个元素时:

len(list)+len([params])为偶数: cap=len(list)+len([params])
len(list)+len([params])为奇数: cap=len(list)+len([params])+1
  • 1
  • 2

即 cap 始终为偶数。

2、当一个一个添加元素时:

len(list)+1<=cap: cap=cap
len(list)+1>cap:  cap=2*cap
  • 1
  • 2

即 cap 总是呈 2 倍的增加(也是偶数)。

通过 append() 函数向数组中添加元素,首先 cap 会以二倍的速度增长,如果发现增长 2 倍以上的容量可以满足扩

容后的需求,那么 cap*2,否则就会看扩容后数组的 length 是多少 cap=length+1。

每次cap改变的时候指向array内存的指针都在变化,当在使用 append 的时候,如果 cap==len 了这个时候就会新

开辟一块更大内存,然后把之前的数据复制过去(实际go在append的时候放大cap是有规律的,在 cap 小于1024

的情况下是每次扩大到 2 * cap ,当大于1024之后就每次扩大到 1.25 * cap)。

通过查看$GOROOT/src/runtime/slice.go源码:

// cap为需要的容量,即新申请的容量
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
   newcap = cap
} else {
   const threshold = 256
   if old.cap < threshold {
      newcap = doublecap
   } else {
      // Check 0 < newcap to detect overflow
      // and prevent an infinite loop.
      for 0 < newcap && newcap < cap {
         // Transition from growing 2x for small slices
         // to growing 1.25x for large slices. This formula
         // gives a smooth-ish transition between the two.
         newcap += (newcap + 3*threshold) / 4
      }
      // Set newcap to the requested cap when
      // the newcap calculation overflowed.
      if newcap <= 0 {
         newcap = cap
      }
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

1、首先判断,如果新申请容量(cap)大于2倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap)。

2、否则,如果旧切片的长度小于阈值,则最终容量(newcap)就是旧容量(old.cap)的两倍,即

(newcap=doublecap)。

3、否则,如果旧切片长度大于等于阈值,则最终容量(newcap)从旧容量(old.cap)开始循环增加为原来的1.25

倍,即newcap += (newcap + 3*threshold) / 4,直到最终容量(newcap)大于等于新申请的容量(cap),

即(newcap >= cap)。

4、如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)。

package main

import "fmt"

func main() {
	var numbers []int
	// len=0 cap=0 slice=[]
	printSlice(numbers)
	/* 允许追加空切片 */
	numbers = append(numbers, 0)
	// len=1 cap=1 slice=[0]
	printSlice(numbers)
	/* 向切片添加一个元素 */
	numbers = append(numbers, 1)
	// len=2 cap=2 slice=[0 1]
	printSlice(numbers)
	/* 注意cap容量的变化 */
	numbers = append(numbers, 2)
	// len=3 cap=4 slice=[0 1 2]
	printSlice(numbers)
	numbers = append(numbers, 3)
	// len=4 cap=4 slice=[0 1 2 3]
	printSlice(numbers)
	// 可以看出,容量不够时,cap会自动扩容到2倍
	numbers = append(numbers, 4)
	// len=5 cap=8 slice=[0 1 2 3 4]
	printSlice(numbers)
}

func printSlice(x []int) {
	fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

1.9 切片是引用传值

在做函数调用时,slice 按引用传递,array 按值传递。

package main

import "fmt"

func main() {
	changeSliceTest()
}

func changeSliceTest() {
	arr1 := []int{1, 2, 3}
	arr2 := [3]int{1, 2, 3}
	arr3 := [3]int{1, 2, 3}
	// before change arr1,  [1 2 3]
	fmt.Println("before change arr1, ", arr1)
	// slice 按引用传递
	changeSlice(arr1)
	// after change arr1,  [9999 2 3]
	fmt.Println("after change arr1, ", arr1)
	// before change arr2,  [1 2 3]
	fmt.Println("before change arr2, ", arr2)
	// array 按值传递
	changeArray(arr2)
	// after change arr2,  [1 2 3]
	fmt.Println("after change arr2, ", arr2)
	// before change arr3,  [1 2 3]
	fmt.Println("before change arr3, ", arr3)
	// 可以显式取array的指针
	changeArrayByPointer(&arr3)
	// after change arr3,  [6666 2 3]
	fmt.Println("after change arr3, ", arr3)
}

func changeSlice(arr []int) {
	arr[0] = 9999
}

func changeArray(arr [3]int) {
	arr[0] = 6666
}

func changeArrayByPointer(arr *[3]int) {
	arr[0] = 6666
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

1.10 切片内部结构

struct Slice
{   
    byte*    array;       // actual data
    uintgo    len;        // number of elements
    uintgo    cap;        // allocated number of elements

};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

第一个字段表示 array 的指针,是真实数据的指针。第二个是表示 slice 的长度,第三个是表示 slice 的容量。

所以 unsafe.Sizeof(切片) 永远都是 24。

当把 slice 作为参数,本身传递的是值,但其内容就 byte* array,实际传递的是引用,所以可以在函数内部修

改。但如果对 slice 本身做 append,而且导致 slice 进行了扩容,实际扩容的是函数内复制的一份切片,对于函数

外面的切片没有变化。

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	slice_test := []int{1, 2, 3, 4, 5}
	// 24
	fmt.Println(unsafe.Sizeof(slice_test))
	// main:[]int{1, 2, 3, 4, 5},5,5
	fmt.Printf("main:%#v,%#v,%#v\n", slice_test, len(slice_test), cap(slice_test))
	// slice_value:[]int{1, 100, 3, 4, 5, 6},6,10
	slice_value(slice_test)
	// main:[]int{1, 100, 3, 4, 5},5,5
	fmt.Printf("main:%#v,%#v,%#v\n", slice_test, len(slice_test), cap(slice_test))
	// slice_ptr:[]int{1, 100, 3, 4, 5, 7},6,10
	slice_ptr(&slice_test)
	// main:[]int{1, 100, 3, 4, 5, 7},6,10
	fmt.Printf("main:%#v,%#v,%#v\n", slice_test, len(slice_test), cap(slice_test))
	// 24
	fmt.Println(unsafe.Sizeof(slice_test))
}

func slice_value(slice_test []int) {
	slice_test[1] = 100                // 函数外的slice确实有被修改
	slice_test = append(slice_test, 6) // 函数外的不变
	fmt.Printf("slice_value:%#v,%#v,%#v\n", slice_test, len(slice_test), cap(slice_test))
}

func slice_ptr(slice_test *[]int) { // 这样才能修改函数外的slice
	*slice_test = append(*slice_test, 7)
	fmt.Printf("slice_ptr:%#v,%#v,%#v\n", *slice_test, len(*slice_test), cap(*slice_test))
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

slice 的底层是数组指针,所以 slice a 和 s 指向的是同一个底层数组,所以当修改 s时,a 也会被修改;修改a时,

s也会被修改。

实例1:

package main

import "fmt"

func main() {
	// len=3, cap=3
	s := []int{1, 2, 3}
	a := s
	s[0] = 888
	// [888 2 3] 3 3
	fmt.Println(a, len(a), cap(a))
	// [888 2 3] 3 3
	fmt.Println(s, len(s), cap(s))
	// append添加的元素对a不生效
	s = append(s, 4)
	// [888 2 3 4] 4 6
	fmt.Println(s, len(s), cap(s))
	// [888 2 3] 3 3
	fmt.Println(a, len(a), cap(a))
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

实例2:

package main

import "fmt"

func main() {
	var array = []int{1, 2, 3, 4, 5}
	// len=5 cap=5 slice=[1 2 3 4 5]
	printSlice(array)
	slice := array[1:]
	// len=4 cap=4 slice=[2 3 4 5]
	printSlice(slice)
	array[1] = 100
	// len=4 cap=4 slice=[100 3 4 5]
	printSlice(slice)
	// len=5 cap=5 slice=[1 100 3 4 5]
	printSlice(array)
	slice[2] = 1000
	// len=5 cap=5 slice=[1 100 3 1000 5]
	printSlice(array)
	// len=4 cap=4 slice=[100 3 1000 5]
	printSlice(slice)
}

func printSlice(x []int) {
	fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

1.11 三个参数的切片

做 slice 截取时建议用两个参数,尤其是从底层数组进行切片操作时,因为这样在进行第一次 append 操作时,会

给切片重新分配空间,这样减少切片对数组的影响。

s = s[low : high : max] 切片的三个参数的切片截取的意义为 low 为截取的起始下标(含), high 为窃取

的结束下标(不含 high),max 为切片保留的原切片的最大下标(不含 max);即新切片从老切片的 low 下标元

素开始,len = high - low, cap = max - low;high 和 max 一旦超出在老切片中越界,就会发生 runtime

err,slice out of range。另外如果省略第三个参数的时候,第三个参数默认和第二个参数相同,即 len = cap。

实例:

package main

import "fmt"

func main() {
	s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	s = s[1:9:10]
	// [1 2 3 4 5 6 7 8]
	fmt.Println(s)
	// 8
	fmt.Println(len(s))
	// 9
	fmt.Println(cap(s))
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

修改 max 值,发生越界错误:

package main

import "fmt"

func main() {
	s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	// 修改 max 值为 13
	s = s[1:9:13]
	fmt.Println(s)
	fmt.Println(len(s))
	fmt.Println(cap(s))
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
// 报错
panic: runtime error: slice bounds out of range [::13] with capacity 10

goroutine 1 [running]:
  • 1
  • 2
  • 3
  • 4

1.12 append会让切片和与它相关的切片脱钩,但地址不变

&会让原切片发生改变,切片也会让原切片发生改变,append不会发生改变:

package main

import (
	"fmt"
)

func main() {
	a := []int{1, 2, 3, 4}
	b := a
	// part1  a len=4 cap=4 slice=[1 2 3 4]
	printSlice(a, "part1  a")
	// part1  b len=4 cap=4 slice=[1 2 3 4]
	printSlice(b, "part1  b")
	fmt.Printf("\n")

	a[0] = 9
	// part2  a len=4 cap=4 slice=[9 2 3 4]
	printSlice(a, "part2  a")
	// part2  b len=4 cap=4 slice=[9 2 3 4]
	printSlice(b, "part2  b")
	fmt.Printf("\n")

	// [9 2 3 4 5]
	a = append(a, 5)
	a[0] = 1
	// part3  a len=5 cap=8 slice=[1 2 3 4 5]
	printSlice(a, "part3  a")
	// part3  b len=4 cap=4 slice=[9 2 3 4]
	printSlice(b, "part3  b")
	fmt.Printf("\n")

	c := a
	d := &a
	// part4  a len=5 cap=8 slice=[1 2 3 4 5]
	printSlice(a, "part4  a")
	// part4  c len=5 cap=8 slice=[1 2 3 4 5]
	printSlice(c, "part4  c")
	// part4  *d len=5 cap=8 slice=[1 2 3 4 5]
	printSlice(*d, "part4  *d")
	fmt.Printf("\n")

	// [1 2 3 4 5 6]
	a = append(a, 6)
	// part5  a len=6 cap=8 slice=[1 2 3 4 5 6]
	printSlice(a, "part5  a")
	// part5  c len=5 cap=8 slice=[1 2 3 4 5]
	printSlice(c, "part5  c")
	// part5 *d len=6 cap=8 slice=[1 2 3 4 5 6]
	printSlice(*d, "part5 *d")
}

func printSlice(x []int, y string) {
	fmt.Printf("%v len=%d cap=%d slice=%v\n", y, len(x), cap(x), x)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

猜测脱钩的情况是由于切片底层数组扩张(创建了新数组替换旧数组)导致:

package main

import "fmt"

func main() {
	x := make([]int, 4)
	a := x[:2]
	a[0] = 1
	// [1 0]
	fmt.Println(a)
	// [1 0 0 0]
	fmt.Println(x)
	// [1 0 2]
	a = append(a, 2)
	a[0] = 0
	// [0 0 2]
	fmt.Println(a)
	// [0 0 2 0]
	fmt.Println(x)
	// 切片a的地址:0xc00000e1a0
	fmt.Printf("切片a的地址:%p\n", a)
	// 切片x的地址:0xc00000e1a0
	fmt.Printf("切片x的地址:%p\n", x)
	fmt.Println()
	y := make([]int, 4)
	b := y
	b[0] = 1
	// [1 0 0 0]
	fmt.Println(b)
	// [1 0 0 0]
	fmt.Println(y)
	// [1 0 0 0 2]
	b = append(b, 2)
	b[0] = 0
	// [0 0 0 0 2]
	fmt.Println(b)
	// [1 0 0 0]
	fmt.Println(y)
	// 切片b的地址:0xc000014240
	fmt.Printf("切片b的地址:%p\n", b)
	// 切片y的地址:0xc00000e1e0
	fmt.Printf("切片y的地址:%p\n", y)
	fmt.Println()
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

脱钩:b:=a,修改a的值b的值不会改变,正常情况下是要改变的。

cap(b)<len(a) 的情况下会发生脱钩,但是在 cap(b)≥len(a) 时,append并不能使切片脱钩。

通过 b:=a 引用的方式,当 cap(b) < len(a),修改 a[i] 的值并不会改变 b[i] 的值,发生了脱钩;但是如果

cap(b) ≥ len(a) 时,修改 a[i] 的值就会改变 b[i] 的值,没有发生脱钩;要想使两个切片同步改变,最好的

方式是使用切片指针来实现,也就是上面的 *d

package main

import (
	"fmt"
)

func main() {
	a := []int{1, 2, 3, 4}
	b := a
	// part1  a len=4 cap=4 slice=[1 2 3 4]
	printSlice(a, "part1  a")
	// part1  b len=4 cap=4 slice=[1 2 3 4]
	printSlice(b, "part1  b")
	fmt.Printf("\n")

	a[0] = 9
	// part2  a len=4 cap=4 slice=[9 2 3 4]
	printSlice(a, "part2  a")
	// part2  b len=4 cap=4 slice=[9 2 3 4]
	printSlice(b, "part2  b")
	fmt.Printf("\n")

	a = append(a, 5)
	a[0] = 1
	// part3  a len=5 cap=8 slice=[1 2 3 4 5]
	printSlice(a, "part3  a")
	// part3  b len=4 cap=4 slice=[9 2 3 4]
	printSlice(b, "part3  b")
	fmt.Printf("\n")
}

func printSlice(x []int, y string) {
	fmt.Printf("%v len=%d cap=%d slice=%v\n", y, len(x), cap(x), x)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

在 part3 中,通过 a[0]=1 修改了 a[0] 的值,但是 b[0] 的值并没有改变;通过 a=append(a,5) 增加了一个数

据,b 切片没有增加数据;这只是在 b 的 cap 比较小的情况下才会出现的情况;如果 b 的 cap 足够大呢?

将代码修改成:

package main

import (
	"fmt"
)

func main() {
	a := make([]int, 4, 10)
	// part1  a len=4 cap=10 slice=[0 0 0 0]
	printSlice(a, "part1  a")
	a[0] = 1
	a[1] = 2
	a[2] = 3
	a[3] = 4
	b := a
	// part1  a len=4 cap=10 slice=[1 2 3 4]
	printSlice(a, "part1  a")
	// part1  b len=4 cap=10 slice=[1 2 3 4]
	printSlice(b, "part1  b")
	fmt.Printf("\n")

	a[0] = 99
	// part2  a len=4 cap=10 slice=[99 2 3 4]
	printSlice(a, "part2  a")
	// part2  b len=4 cap=10 slice=[99 2 3 4]
	printSlice(b, "part2  b")
	fmt.Printf("\n")

	a = append(a, 5)
	a[0] = 100
	// part3  a len=5 cap=10 slice=[100 2 3 4 5]
	printSlice(a, "part3  a")
	// part3  b len=4 cap=10 slice=[100 2 3 4]
	printSlice(b, "part3  b")
	fmt.Printf("\n")

}

func printSlice(x []int, y string) {
	fmt.Printf("%v len=%d cap=%d slice=%v\n", y, len(x), cap(x), x)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

修改后的代码中,将 a, b 的 cap 都设置成 10;

在 part3 部分,通过 a[0] 修改 a[0] 值,b[0] 值也会跟着修改;通过 a=append(a, 5) 可以给 a 增加数据项,但

是 b 的数据项并没有增加。

1.13 nil切片和空切片

make([]int,0)var a []int建的切片是有区别的,前者的切片指针有分配,后者的内部指针为nil。

package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

func main() {
	var a []int
	b := make([]int, 0)
	if a == nil {
		// a is nil
		fmt.Println("a is nil")
	} else {
		fmt.Println("a is not nil")
	}
	//虽然b的底层数组大小为0,但切片并不是nil
	if b == nil {
		fmt.Println("b is nil")
	} else {
		// b is not nil
		fmt.Println("b is not nil")
	}
	//使用反射中的SliceHeader来获取切片运行时的数据结构
	as := (*reflect.SliceHeader)(unsafe.Pointer(&a))
	bs := (*reflect.SliceHeader)(unsafe.Pointer(&b))
	// len=0,cap=0,type=0
	fmt.Printf("len=%d,cap=%d,type=%d\n", len(a), cap(a), as.Data)
	// len=0,cap=0,type=824634810008
	fmt.Printf("len=%d,cap=%d,type=%d\n", len(b), cap(b), bs.Data)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

1.14 多个切片引用同一个底层数组引发的混乱

切片可以由数组创建,一个底层数组可以创建多个切片,这些切片共享底层数组,使用append 扩展切片过程中可

能修改底层数组的元素,间接地影响其他切片的值,也可能发生数组复制重建,共用底层数组的切片,由于其行为

不明朗,不推荐使用。

多个切片共享一个底层数组,其中一个切片的 append 操作可能引发如下两种情况。

(1)、append追加的元素没有超过底层数组的容量,此种 append 操作会直接操作共享的底层数组,如果其他切

片有引用数组被覆盖的元素,则会导致其他切片的值也隐式地发生变化。

(2)、append追加的元素加上原来的元素如果超出底层数组的容 ,则此种 append 操作会重新申请新数组,并将

原来数组值复制到新数组。

由于有这种二义性,所以在使用切片的过程中应该尽量避免多个切面共享底层数组, 可以使用copy进行显式的复

制。

package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

func main() {
	a := []int{0, 1, 2, 3, 4, 5, 6}
	b := a[0:4]
	as := (*reflect.SliceHeader)(unsafe.Pointer(&a))
	bs := (*reflect.SliceHeader)(unsafe.Pointer(&b))
	//a、b共享底层数组
	// a=[0 1 2 3 4 5 6],len=7,cap=7,type=824633803328
	fmt.Printf("a=%v,len=%d,cap=%d,type=%d\n", a, len(a), cap(a), as.Data)
	// b=[0 1 2 3],len=4,cap=7,type=824633803328
	fmt.Printf("b=%v,len=%d,cap=%d,type=%d\n", b, len(b), cap(b), bs.Data)
	b = append(b, 10, 11, 12)
	//a、b继续共享底层数组,修改b会影响共享的底层数组,间接影响a
	// a=[0 1 2 3 10 11 12],len=7,cap=7
	fmt.Printf("a=%v,len=%d,cap=%d\n", a, len(a), cap(a))
	// b=[0 1 2 3 10 11 12],len=7,cap=7
	fmt.Printf("b=%v,len=%d,cap=%d\n", b, len(b), cap(b))
	//len(b)=7,底层数组容量是7,此时需要重新分配数组,并将原来数组值复制到新数组
	b = append(b, 13, 14)
	as = (*reflect.SliceHeader)(unsafe.Pointer(&a))
	bs = (*reflect.SliceHeader)(unsafe.Pointer(&b))
	//可以看到a和b指向底层数组的指针已经不同了
	// a=[0 1 2 3 10 11 12],len=7,cap=7,type=824633803328
	fmt.Printf("a=%v,len=%d,cap=%d,type=%d\n", a, len(a), cap(a), as.Data)
	// b=[0 1 2 3 10 11 12 13 14],len=9,cap=14,type=824633786592
	fmt.Printf("b=%v,len=%d,cap=%d,type=%d\n", b, len(b), cap(b), bs.Data)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/206358
推荐阅读
相关标签
  

闽ICP备14008679号