golang 的数组与切片有两大区别:

  • 初始化:数组需要明确指定大小,切片不需要
  • 函数传递:数组是值传递,切片是地址传递

初始化

数组

a := [...]int{1,2,3}   
a := [3]int{1,2,3}

切片

a:= []int{1,2,3}  
a := make([]int, 5) 
a := make([]int, 5, 10)

slice的数据结构:

go源码slice的数据结构定义:

type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}

slice 有三个字段:

  • 指向真实 array 地址的指针 ptr
  • slice 的长度 len
  • 容量 cap

特性

通过例子说明 slice 和 array 的一些特性。

函数传递

数组需要明确指定大小,切片不需要。数组是值传递,切片是地址传递

	a := [...]int{1, 2, 3, 4, 5, 6}
	fmt.Println("star deal array, orginal data is:")
	fmt.Println(a)
	aMaxIndex := len(a) - 1
	fmt.Printf("aMaxIndex:%d\r", aMaxIndex)
	for i, e := range a {
		if i == aMaxIndex {
			a[0] += e
			fmt.Printf("index is 0, val is :%d\r", a[0])
		} else {
			a[i+1] += e
			fmt.Printf("index is:%d ,val is :%d\r", i+1, a[i+1])
		}
	}
	fmt.Println("deal result is:")
	fmt.Println(a)

	s := []int{1, 2, 3, 4, 5, 6}
	fmt.Println("star deal slice, orginal data is:")
	fmt.Println(s)
	sMaxIndex := len(s) - 1
	fmt.Printf("aMaxIndex:%d\r", sMaxIndex)
	for i, e := range s {
		if i == sMaxIndex {
			s[0] += e
			fmt.Printf("index is 0, val is :%d\r", s[0])
		} else {
			s[i+1] += e
			fmt.Printf("index is:%d ,val is :%d\r", i+1, s[i+1])
		}
	}
	fmt.Println("deal result is:")
	fmt.Println(s)

输出:

star deal array, orginal data is:
[1 2 3 4 5 6]
aMaxIndex:5
index is:1 ,val is :3
index is:2 ,val is :5
index is:3 ,val is :7
index is:4 ,val is :9
index is:5 ,val is :11
index is 0, val is :7
deal result is:
[7 3 5 7 9 11]
star deal slice, orginal data is:
[1 2 3 4 5 6]
aMaxIndex:5
index is:1 ,val is :3
index is:2 ,val is :6
index is:3 ,val is :10
index is:4 ,val is :15
index is:5 ,val is :21
index is 0, val is :22
deal result is:
[22 3 6 10 15 21]

这个是比较复杂的值传递和地址传递的例子,可以看到:

  • array在循环时每次拿到的值(e)是传递数组的值
  • slice在循环时每次拿到的值(e)是传递地址的值

所以最后a 和 s 的值会不一样。

slice append的时候内存地址的改变情况

通过一个例子:

package main

import (
   "fmt"
   "unsafe"
)

func main() {
   //说先定义一个切片,只限定长度为1
   s := make([]int, 1)

   //打印出slice的长度,容量以及内存地址
   fmt.Printf("len :%d cap:%d array ptr :%v \n", len(s), cap(s), *(*unsafe.Pointer)(unsafe.Pointer(&s)))

   for i := 0; i < 5; i++ {
      s = append(s, i)
      fmt.Printf("len :%d cap:%d array ptr :%v \n", len(s), cap(s), *(*unsafe.Pointer)(unsafe.Pointer(&s)))
   }
   //打印出slice
   fmt.Println("array:", s)
}

输出:

len :1 cap:1 array ptr :0xc042062080 
len :2 cap:2 array ptr :0xc0420620c0 
len :3 cap:4 array ptr :0xc0420600e0 
len :4 cap:4 array ptr :0xc0420600e0 
len :5 cap:8 array ptr :0xc0420880c0 
len :6 cap:8 array ptr :0xc0420880c0 
array: [0 0 1 2 3 4]

可以看出来在append的过程中,内存地址有些是一样的,有些是不一样的,容量也是如此。

每次cap改变的时候指向array内存的指针都在变化。因为当在使用 append 的时候,如果 cap==len ,这个时候就会新开辟一块更大内存,然后把之前的数据复制过去。

最后一句输出slice元素的时候为什么会多了一个0呢?是因为make初始化时,都会初始化成对应数据类型的原值。

cap的扩大规律

go 在 append 的时候放大 cap 是有规律的。在 cap 小于1024的情况下是每次扩大到 2 * cap ,当大于1024之后就每次扩大到 1.25 * cap

package main

import (
    "fmt"
    "unsafe"
)

func main() {
    //说先定义一个切片,只限定长度为1
    s := make([]int, 1)

    //打印出slice的长度,容量以及内存地址
    fmt.Printf("len :%d cap:%d array ptr :%v \n", len(s), cap(s), *(*unsafe.Pointer)(unsafe.Pointer(&s)))

    for i := 1; i < 1024*2; i++ {
        s = append(s, i)
        fmt.Printf("len :%d cap:%d array ptr :%v \n", len(s), cap(s), *(*unsafe.Pointer)(unsafe.Pointer(&s)))
    }
    //打印出slice
    fmt.Println("array:", s)
}

输出太多,截一部分:

...
len :1019 cap:1024 array ptr :0xc000184000 
len :1020 cap:1024 array ptr :0xc000184000 
len :1021 cap:1024 array ptr :0xc000184000 
len :1022 cap:1024 array ptr :0xc000184000 
len :1023 cap:1024 array ptr :0xc000184000 
len :1024 cap:1024 array ptr :0xc000184000 
len :1025 cap:1280 array ptr :0xc000188000 
len :1026 cap:1280 array ptr :0xc000188000 
len :1027 cap:1280 array ptr :0xc000188000 
len :1028 cap:1280 array ptr :0xc000188000
...