# Slice

### 从你会的东西开始

站在我的角度来看, 正是因为Slice太好用了, 所以很多人最后忘记了有数组的存在了, 的确是啊, 大家都搞`[]string+append`的方式来用, 好像确实找不到什么理由来使用数组. 如何创建出slice\&array? 指明/不指明size就可以:

* 创建一个数组:
  * arr := \[3]string{}
  * arr := \[...]string{}
* 创建一个切片:
  * sli := \[]string{}
  * sli := make(\[]string, 10)
* 数组变切片:
  * sli := arr\[:]

## slice(指针)/array(实体)

![](https://2040718526-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MC_ucxnzAClkrgsGdFP%2Fsync%2F55443ba109660b5185fa28529decfce8e25fac00.png?generation=1598675086000392\&alt=media)

我们从最简单的开始想象, 如果想要存一组元素, 最简单的方式是什么? 是不是就是搞一小块内存出来存着就好咯? 没有指针, 没有滑头, 就一组数字, 存就完事儿

![](https://2040718526-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MC_ucxnzAClkrgsGdFP%2Fsync%2Fda5780b688199c4bda5c64baae2f9ac072f74ad2.png?generation=1598675087687286\&alt=media)

也正是因为**数组 = 实体, 没有指针等滑头**, 结合Golang函数传值(复制)的特点, 我们也能想象出上面会发生什么, 一个数组在传递的过程中, 他的值被拷贝了一份

![](https://2040718526-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MC_ucxnzAClkrgsGdFP%2Fsync%2Ffa55686f4249cfa2543706201a68aee0d0843c38.png?generation=1598675084970854\&alt=media)

与之相对的是slice并没有实体, 它只有一个指针, 因此在函数传递参数的时候传指针, 修改的是同一份内容.

### 切片到底是什么? 不欢迎玄学!

```go
func main() {
        arr := [5]int{1,2,3,4,5}
        sli := arr[:]
        fmt.Printf("Addr[arr]=%p \n",arr[0])
        fmt.Printf("Addr[sli]=%p \n",sli[0])        

        arr[2] = 69
        println(sli[2])
}
/*
        输出:
                Addr[arr] = 0xc0000a4000
                Addr[sli] = 0xc0000a4000
                slice[2]  = 69
*/
```

来玩个游戏, 我们先创建一个数组, 然后通过"\[:]"的方式创建出Slice, 好玩的:

* 那么他俩的地址是? **是同一个地址**
* 那么修改数组, 对应的slice会怎样? **会跟着一起改**

![](https://2040718526-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MC_ucxnzAClkrgsGdFP%2Fsync%2F03b4d53b94c7060aa561e7f443a93387dca3ccf5.png?generation=1598675088702515\&alt=media)

狗吧? 现在你知道为什么形参为`[3]int{}`的函数不能接受`[]int{}`的实参了吧? 因为一个是指针一个是实体嘛(偶偶, 这下我完全搞懂了!~~完全没懂~~)

### slice的初始化

![](https://2040718526-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MC_ucxnzAClkrgsGdFP%2Fsync%2Fcdceaee6402a7ee9e7751b76fd126aa6f92c1e3a.png?generation=1598675086953853\&alt=media)

继续玩! 之前在写Map的时候这么玩过一次, 这招能帮我们看到到底执行了什么函数, OK, 这里的make被翻译成了`runtime.makeslice`:

```
func makeslice(...) unsafe.Pointer {
        return malloc(...)
}
```

make函数被编译器翻译成了makeslice函数,这个函数通过malloc, 以unsafe.Pointer的方式, 返回一个指向对应内存段的指针 (简单的理解就是`void*`, 后面再cast成实际的类型)

```go
// runtime/slice.go+13
type slice stuct {
    array unsafe.Pointer
    len   int
    cap   int
}
```

我也不是嘴嗨, 上面这就是runtime里的定义, 想要创建一个slice需要先预估大小后创建一个数组指针(unsafe), 然后塞到slice结构体里才能变成你天天用的slice. 平时的len,cap什么的都有在搞吧?~~(压力马斯内!)~~ 原理就是检查slice结构体里的变量

### 数组是不是一无是处...

```
func main() {
        arr := [5]{1,2,3,4,5}
        _ = arr[10]
}
```

也不是一无是处, 数组的一个特点就是定长, 这使得编译阶段的越界检查成为可能, 尝试通过go build(不运行, 只编译)一下上面的片段, 你会发现连编都编不过

那么Slice呢, slice因为是指针也不定长因此编译期的越界管不到, 怎么办呢? 只能运行期内panic咯

### Append Slice

```go
slice = append(slice,v)         // 覆盖, 追加
a_new_slice := append(slice,v)  // 不覆盖, 覆盖 + 深拷贝
```

思考以下问题: append返回什么? 答案很有趣, slice因为是指针, 所以也要考虑深拷贝浅拷贝问题, 这道题的答案是: 取决于你返回值是不是覆盖变量

* 如果覆盖, 则:
  * 判断slice下数组容量是否足够, 是否需要扩容(growslice)
  * 然后将这个数组重新给slice
* 如果不覆盖, 则:
  * 判断slice下数组容量是否足够, 是否需要扩容(growslice)
    * 如果需要扩容, 并分配一个新内存\[深拷贝]
    * 如果不需要扩容, 则新老两个指向同一个数组\[浅拷贝]

```go
// 覆盖的情况
slice := []int{0}       // &slice[0] = 0xc000018110 cap=1
slice = append(slice,1) // &slice[0] = 0xc000018130 cap=2
slice = append(slice,2) // &slice[0] = 0xc000014240 cap=4
slice = append(slice,3) // &slice[0] = 0xc000014240 cap=4
```

初始化的slice只有1容量, 按照加倍增长的原则, 变成2/变成4, 每次扩容, 底层的那个数组就会重新分配一次, 因此地址也在跟着变, 最后在append2/3的时候因为容量够了, 所以不会重新分配内存

```go
// 不覆盖 + 需要扩容
slice := []int{1,2,3}           // &slice = 0xc0000ae020
a_new_slice := append(slice,4)  // &new   = 0xc0000ac030

// 不覆盖, 不扩容
slice := make([]int, 0, 10).    // &slice = 0xc00001a0a0
a_new_slice := append(slice,4)  // &new.  = 0xc00001a0a0
```

**那么回到一个经典问题, 如果我改slice, new\_slice会不会一起改呢?** 看看上面两种情况下, 两个slice的地址.

* 扩容了分配了新数组, 不会一起改, 底层数组都不一样么
* 没扩容也没分配新数组, 一起改, 因为指向的是同一个底层数组

非常鬼畜对吧? 怎么办呢? 我现在就是想要一个新slice, 不要跟之前的有任何关联, 请指明使用深拷贝: make+copy
