Go 2025-09-27 · 15 min 阅读

Go Slice介绍


go语言中的切片对标于其他编程语言中通俗意义上的“数组”. 切片中的元素存放在一块内存地址连续的区域,使用索引可以快速检索到指定位置的元素;切片长度和容量是可变的,在使用过程中可以根据需要进行扩容.

slice操作,进行slice内容的截取,形如s[a:b]的格式,其中a b代表切片的索引index,左闭右开; 在对切片slice执行截取操作时,本质上是一次引用传递操作,因为不论如何截取,底层复用的都是同一块内存空间中的数据,只不过,截取动作会创建出一个新的slice header实例.

切片扩容, 当slice当前的长度len与容量cap相等时,下一次append操作就会引发一次切片扩容.

slice的问题:

  1. 初始化切片s长度和容量均为10在s的基础上追加append一个元素,切片s的内容、长度以及容量分别是什么?
    func Test_slice(t *testing.T){
     s := make([]int,10)
     s = append(s,10)
     t.Logf("s: %v, len of s: %d, cap of s: %d",s,len(s),cap(s))
    }
    

    输出,s: [0 0 0 0 0 0 0 0 0 0 10], len of s: 11, cap of s: 20, make([]int, 10)其len和容量cap均为10,且前10个元素是已经切实被分配过的(零值)。在切片原容量小于256的情况下,扩容时会采用原容量的2倍作为新的容量,于是在新切片中,长度增加为11,而容量则翻倍变成20.

  2. 初始化切片s长度为0,容量为10,在s的基础上追加append一个元素,切片s的内容、长度以及容量分别是什么?
    func Test_slice(t *testing.T){
     s := make([]int,0,10)  
     s = append(s,10)
     t.Logf("s: %v, len of s: %d, cap of s: %d",s,len(s),cap(s))
    }
    

    输出,slice_test.go:15: s: [10], len of s: 1, cap of s: 10,make([]int, 0, 10) 的方式使得切片长度0,容量为10,实际上还有长度为10的缓存空间,append的时,切片长度从0增加为1,容量则维持为10不变。

  3. 初始化切片s长度为10,容量为11,在s的基础上追加append一个元素,切片s`的内容、长度以及容量分别是什么?
    func Test_slice(t *testing.T){
     s := make([]int,10,11)
     s = append(s,10)
     t.Logf("s: %v, len of s: %d, cap of s: %d",s,len(s),cap(s))
    }
    

    输出,slice_test.go:22: s: [0 0 0 0 0 0 0 0 0 0 10], len of s: 11, cap of s: 11,由于容量大于长度,因此仍有足够的空间,这次append操作不会引发扩容。

  4. 初始化切片s长度为10,容量为12,截取切片s index = 8往后的内容赋给s1,长度以及容量分别是什么?
    func Test_slice(t *testing.T){
     s := make([]int,10,12)  
     s1 := s[8:]
     t.Logf("s1: %v, len of s1: %d, cap of s1: %d",s1,len(s1),cap(s1))
    }
    

    输出, slice_test.go:29: s1: [0 0], len of s1: 2, cap of s1: 4,截取操作会以s[8]作为内存空间的起点,截取所得新切片s1的长度和容量强依赖于原切片s的长度和容量,并在此基础上减去头部8个未使用到的单位。

  5. s长度为10,容量为12,截取切片s index为[8,9)范围内的元素赋给切片s1,长度以及容量分别是什么?
    func Test_slice(t *testing.T){
     s := make([]int,10,12)  
     s1 := s[8:9]
     t.Logf("s1: %v, len of s1: %d, cap of s1: %d",s1,len(s1),cap(s1))
    }
    

    输出slice_test.go:36: s1: [0], len of s1: 1, cap of s1: 4,我们需要注意虽然s[8:9]的截取操作限定了s1的右边界,但这只是长度意义上的,对于容量,s1仍然和s保持强关联性。

  6. 初始化切片s长度为10,容量为12,截取切片s index = 8往后的内容赋给s1,修改s1[0]的值,修改是否会影响到s? 此时s的内容是什么?
    func Test_slice(t *testing.T){
     s := make([]int,10,12)  
     s1 := s[8:]
     s1[0] = -1
     t.Logf("s: %v",s)
    }
    

    输出slice_test.go:44: s: [0 0 0 0 0 0 0 0 -1 0],s1是在s基础上截取得到的,属于一次引用传递,底层共用同一片内存空间,其中s[x]等价于s1[x+8]. 因此修改了s1[0]会直接影响到s[8]。

  7. 初始化切片s长度为10,容量为12,请问,访问s[10]是否会越界?
    func Test_slice(t *testing.T){
     s := make([]int,10,12)  
     s = s[10]
     // 求问,此时数组访问是否会越界
    }
    

    会发生panic,切片长度为10,容量为12. 容量是物理意义上的,但长度是逻辑意义上的,判断是否越界以逻辑意义为准,因此index = 10已经越界。

  8. 初始化切片s长度为10,容量为12,截取s中index = 8后面的内容赋给s1,在s1的基础上追加[]int{10,11,12} 3个元素,经过上述操作时候,访问s[10]是否会越界?
    func Test_slice(t *testing.T){
     s := make([]int,10,12)  
     s1 := s[8:]
     s1 = append(s1,[]int{10,11,12}...)
     v := s[10]
     // ...
     // 求问,此时数组访问是否会越界
    }
    

    会发生panic,在s的基础上截取产生了s1,此时s1和s会拥有两个独立的slice header。s1扩容后,会被迁移到新的空间地址,此时s1已经和s做到真正意义上的完全独立,意味着修改s1不再会影响到s s继续维持原本的长度值10和容量值12,因此访问s[10]会panic。

  9. 初始化切片s长度为10,容量为12,截取切片s index = 8往后的内容赋给s1,在方法changeSlice中,对s1[0]进行修改,经过上述操作之后,s的内容是什么?
    func Test_slice(t *testing.T){
     s := make([]int,10,12)  
     s1 := s[8:]
     changeSlice(s1)
     t.Logf("s: %v",s)
    }
    func changeSlice(s1 []int){
      s1[0] = -1
    }
    

    输出slice_test.go:58: s: [0 0 0 0 0 0 0 0 -1 0],切片在传递时属于引用传递,且s1[0]和s[8]指向同一个元素. 因此在局部方法中,修改了s1[0]会直接影响到s[8]的内容.

  10. 初始化切片s长度为10,容量为12,截取切片s index = 8往后的内容赋给s1,在方法changeSlice中,对s1进行apend追加操作,s以及s1的内容、长度和容量分别是什么? ```go func Test_slice(t *testing.T){ s := make([]int,10,12)
    s1 := s[8:] changeSlice(s1) // slice_test.go:69: s: [0 0 0 0 0 0 0 0 0 0], len of s: 10, cap of s: 12 t.Logf(“s: %v, len of s: %d, cap of s: %d”,s, len(s), cap(s)) // slice_test.go:70: s1: [0 0], len of s1: 2, cap of s1: 4 t.Logf(“s1: %v, len of s1: %d, cap of s1: %d”,s1, len(s1), cap(s1)) }

func changeSlice(s1 []int){ s1 = append(s1, 10) }

虽然切片是引用传递,但是在方法调用时,传递的会是一个新的`slice header`,因此在局部方法`changeSlice`中,虽然对`s1`进行了`append`操作,但这这会在局部方法中这个独立的`slice header`中生效,不会影响到原方法`Test_slice`当中的`s`和`s1`的长度和容量。

11. 初始化切片`s`,内容为`[]int{0,1,2,3,4}`,截取`s`中`index = 2`前面的内容(不含`s[2]`),并在此基础上追加`index = 3`后面的内容,经过上述操作后,`s`的内容、长度和内容分别是什么?此时访问`s[4]`是否会越界?
```go
func Test_slice(t *testing.T){
    s := []int{0,1,2,3,4}
    s = append(s[:2],s[3:]...)
	// slice_test.go:82: s: [0 1 3 4], len: 4, cap: 5
    t.Logf("s: %v, len: %d, cap: %d", s, len(s), cap(s))
    v := s[4] 
    // 是否会数组访问越界
}

会panic,执行完上述append操作之后,s的实际长度为4,容量维持不变为5. 此时访问s[4]会发生数组越界的错误。

  1. 初始化切片s长度和容量均为512,在s的基础上追加append一个元素,切片s的内容、长度以及容量分别是什么?
    func Test_slice(t *testing.T){
    s := make([]int,512)  
    s = append(s,1)
    // slice_test.go:91: len of s: 513, cap of s: 848
    t.Logf("len of s: %d, cap of s: %d",len(s),cap(s))
    }
    

    由于切片s原有容量为512,已经超过了阈值256,因此对其进行扩容操作会采用的计算共识为512 * (512 + 3*256)/4 = 832。再终,在mallocgc流程中,我们为扩容后的新切片分配到了6784 byte的空间,于是扩容后实际的新容量为cap = 6784/8 = 848.

相关扩展阅读:

# Go