【Go】Go 语言切片(Slice)


一、切片

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

Go 数组的长度不可改变,在特定场景中就不太适用,Go 中提供了一种灵活,功能强悍的内置类型:切片(“动态数组”),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

需要说明,slice 并不是数组或数组指针。它通过内部指针和相关属性 引用数组片段 ,以实现变长方案。

  1. 切片:切片是数组的一个引用,因此切片是引用类型。但自身是结构体,值拷贝传递。
  2. 切片的长度可以改变,因此,切片相当于一个可变的数组。
  3. 切片遍历方式和数组一样,可以用 len() 求长度。表示可用元素数量,读写操作不能超过该限制。
  4. cap 可以求出 slice 最大扩张容量,不能超出数组限制。0 <= len(slice) <= len(array),其中array是slice引用的数组。
  5. 切片的定义:var 切片变量名 []类型,比如 var str []stringvar arr []int
  6. 如果 slice == nil,那么 len、cap 结果都等于 0。

切片 Slice 在源码中的数据结构定义如下:

type slice struct {
    
    
    array unsafe.Pointer  //一个指向数组的指针
    len   int
    cap   int
}

切片的结构体由3部分构成,Pointer 是指向一个数组的指针,len 代表当前切片的长度,cap 是当前切片的容量。cap 总是大于等于 len 的。

总结:
Go 语言中的切片类型是从数组类型基础上发展出来的新类型,当声明一个数组时,不指定该数组长度,则该类型为切片(“动态数组”),切片有自己独立的内部结构字段(len, cap, array pointer),并于其引用的底层数组共用存储空间。


二、声明切片

方法1

你可以 声明 一个 未指定大小的数组 来定义切片,切片声明时不需要说明长度([]没有声明长度,说明这是一个切片,而不是一个数组。因为数组声明是必须指定长度的。):

var identifier []type

如上这种形式的只声明不初始化,这时切片 默认 初始化为 nillen=0 cap=0 slice=[]

之所以为 nil ,是因为 没有分配存储空间

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

package main

import "fmt"

func main() {
    
    
	var numbers []int

	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)
}

输出结果:

len=0 cap=0 slice=[]
切片是空的

多一嘴:

nil 切片被用在很多标准库和内置函数中,描述一个不存在的切片的时候,就需要用到 nil 切片。比如函数在发生异常的时候,返回的切片就是 nil 切片。nil 切片的指针指向 nil。
空切片一般会用来表示一个空的集合。比如数据库查询,一条结果也没有查到,那么就可以返回一个空切片。

方法2

如果你想声明一个拥有初始长度或规定容量的切片(可以指定切片的长度和容量),可以使用 make() 函数来创建切片:

var slice1 []type = make([]type, length, capacity)

也可以简写为

slice1 := make([]type, length, capacity)

这里 length 是数组的长度并且也是切片的初始长度。
容量 capacity 为可选参数(可选的意思是可以缺省,如果不指定capacity,则capacity默认等于length)。

make 创建的切片与其底层数组:

在这里插入图片描述

实例:缺省 capacity

package main

import "fmt"

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

func printSlice(x []int) {
    
    
	fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}

输出结果:

len=3 cap=3 slice=[0 0 0]

如上这样创建切片、不初始化,那么切片被系统自动初始化为 0。(而不是 nil )

之所以 不是nil ,是因为 make 函数为其 分配了内存空间

总结:创建切片的各种方式

实例:

package main

import "fmt"

func main() {
    
    
	//1.声明切片
	var s1 []int
	if s1 == nil {
    
    
		fmt.Println("s1是空")
	} else {
    
    
		fmt.Println("s1不是空")
	}

	// 2.make()创建
	var s2 []int = make([]int, 0)
	var s3 []int = make([]int, 0, 0)
	if s2 == nil {
    
    
		fmt.Println("s2是空")
	} else {
    
    
		fmt.Println("s2不是空")
	}
	if s3 == nil {
    
    
		fmt.Println("s3是空")
	} else {
    
    
		fmt.Println("s3不是空")
	}

	fmt.Println(s1, s2, s3)

	// 3.:=
	s4 := []int{
    
    }
	s5 := []int{
    
    1, 2, 3}
	fmt.Println(s4, s5)
	if s4 == nil {
    
    
		fmt.Println("s4是空")
	} else {
    
    
		fmt.Println("s4不是空")
	}

	// 4.从数组切片
	arr := [5]int{
    
    1, 2, 3, 4, 5} //数组
	var s6 []int                 //切片
	s6 = arr[1:4]
	fmt.Println(s6)

}

输出结果:

s1是空
s2不是空
s3不是空
[] [] []
[] [1 2 3]
s4不是空
[2 3 4]

有四种创建切片的方法:

  1. 常规声明
  2. make() 函数创建
  3. 赋值符 := 创建
  4. 引用数组

其中,只有 “常规声明” 却不初始化的切片被系统默认为 nil (没有内存空间)。用 make() 函数或 := 创建却不初始化的切片为空切片(拥有内存空间,只是没有元素),如果有元素的话会被系统默认初始化为 0 。这是因为 “常规声明” 不会为切片分配存储空间,而其他方法会分配。

空切片和 nil 切片的区别在于,空切片指向的地址不是 nil,指向的是一个内存地址,但是它没有分配任何内存空间,即底层元素包含0个元素。

最后需要说明的一点是。不管是使用 nil 切片还是空切片,对其调用内置函数 append,len 和 cap 的效果都是一样的。


三、切片初始化

1. 声明的同时初始化

s := []int{
    
    1, 2, 3}   //一句代码完成声明和初始化两个工作

直接完成了声明和初始化切片,[] 表示是切片类型,{1,2,3} 初始化值依次是 1,2,3,其 cap=len=3:len=3 cap=3 slice=[1 2 3]

赋值符初始化的切片与其底层数组:
在这里插入图片描述

2. 用数组初始化切片

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

//用数组arr的所有值初始化切片
s := arr[:] 

将 arr 中从下标 startIndexendIndex-1 下的元素创建为一个新的切片:

s := arr[startIndex:endIndex] 

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

s := arr[startIndex:] 

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

s := arr[:endIndex] 

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

s1 := s[startIndex:endIndex] 

引用数组元素初始化的切片与其底层数组:
在这里插入图片描述

用数组初始化切片的方法总结:

全局变量:
var arr = [...]int{
    
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9} //这是一个数组
var slice0 []int = arr[start:end] 
var slice1 []int = arr[:end]        
var slice2 []int = arr[start:]        
var slice3 []int = arr[:] 
var slice4 = arr[:len(arr)-1]      //去掉切片的最后一个元素
局部变量:
arr2 := [...]int{
    
    9, 8, 7, 6, 5, 4, 3, 2, 1, 0}  //这是一个数组
slice5 := arr[start:end]
slice6 := arr[:end]        
slice7 := arr[start:]     
slice8 := arr[:]  
slice9 := arr[:len(arr)-1] //去掉切片的最后一个元素

在这里插入图片描述

3. 切片的内存布局:

在这里插入图片描述

读写操作实际目标是底层数组,只需注意索引号的差别。(本质:切片是数组的一个引用)

package main

import (
	"fmt"
)

func main() {
    
    
	data := [...]int{
    
    0, 1, 2, 3, 4, 5}

	s := data[2:4]
	s[0] += 100
	s[1] += 200

	fmt.Println(s)
	fmt.Println(data)
}

输出结果:

[102 203]
[0 1 102 203 4 5]

可见:对切片内容的改变实际上改变的是它所引用的数组。
切片就像一个傀儡、一个指针、一个虚构,对它的操作就是对原数组的操作。切片和它所引用的数组是一体的,虽然我们看到的是一个切片,其实它还是底层的数组。它们两者是统一的,你就把切片当成一个原数组的一段就行。

这时有同学就有疑问了,前面的那么多创建切片的方式,并不都是通过引用数组得来的呀,大部分都是直接创建切片的呀?

这是因为,我们直接创建 slice 对象时,系统会自动分配底层数组

实例:

package main

import "fmt"

func main() {
    
    
    s1 := []int{
    
    0, 1, 2, 3, 8: 100} // 通过初始化表达式构造,可使用索引号。
    fmt.Println(s1, len(s1), cap(s1))

    s2 := make([]int, 6, 8) // 使用 make 创建,指定 len 和 cap 值。
    fmt.Println(s2, len(s2), cap(s2))

    s3 := make([]int, 6) // 省略 cap,相当于 cap = len。
    fmt.Println(s3, len(s3), cap(s3))
}

输出结果:

[0 1 2 3 0 0 0 0 100] 9 9
[0 0 0 0 0 0] 6 8
[0 0 0 0 0 0] 6 6

从这个实例可以看到,你创建的的确是切片,但是它还是数组的形式呀,并不是其他形式。
所以说切片的本质是数组,但是并不是数组,它只是引用了数组的一段。
你创建了一个切片,系统会自动为你创建一个底层数组,然后引用这个底层数组生成一个切片。
你觉得你操作的是切片本身,但实际上操作的是它所依托的那个底层的数组。

既然访问的还是底层数组,那我们为什么不直接操作数组呢?
这是因为切片长度可变的灵活性:使用 make 动态创建slice,避免了数组必须用常量做长度的麻烦。

比如:切片resize(调整大小)

package main

import (
	"fmt"
)

func main() {
    
    
	var a = []int{
    
    1, 3, 4, 5}
	fmt.Printf("slice a : %v , len(a) : %v\n", a, len(a))
	b := a[1:2]
	fmt.Printf("slice b : %v , len(b) : %v\n", b, len(b))
	c := b[0:3]
	fmt.Printf("slice c : %v , len(c) : %v\n", c, len(c))
}

输出结果:

slice a : [1 3 4 5] , len(a) : 4
slice b : [3] , len(b) : 1
slice c : [3 4 5] , len(c) : 3

其原理仍然是:读写操作实际目标是底层数组。
但是这里用切片的话,它的长度就非常灵活。

还可用指针直接访问底层数组,退化成普通数组操作

package main

import "fmt"

func main() {
    
    
	s := []int{
    
    0, 1, 2, 3}
	p := &s[2] // *int, 获取底层数组元素指针。
	*p += 100

	fmt.Println(s)
}

输出结果:

[0 1 102 3]

四、一些复杂类型切片

1. [][]T,是指元素类型为 []T 的切片。

实例:

package main

import (
	"fmt"
)

func main() {
    
    
	data := [][]int{
    
                 //[]int类型的切片
		[]int{
    
    1, 2, 3},          //初始化值
		[]int{
    
    100, 200},
		[]int{
    
    11, 22, 33, 44},
	}
	fmt.Println(data)
}

输出结果:

[[1 2 3] [100 200] [11 22 33 44]]

2. 结构体数组 / 切片

可直接修改 struct array/slice 成员:

package main

import (
	"fmt"
)

func main() {
    
    
	d := [5]struct {
    
     //结构体数组
		x int
	}{
    
    }             //未初始化

	s := d[:]       //切片

	d[1].x = 10
	s[2].x = 20

	fmt.Println(d)
	fmt.Printf("%p, %p\n", &d, &d[0])

}

输出结果:

[{
    
    0} {
    
    10} {
    
    20} {
    
    0} {
    
    0}]
0xc00000c540, 0xc00000c540

五、len()、cap()、append()、copy()

1. len() 和 cap() 函数

可以使用 len() 方法获取切片长度。

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

以下为具体实例:

package main

import "fmt"

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

func printSlice(x []int) {
    
    
	fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}

输出结果:

len=3 cap=5 slice=[0 0 0]

2. append() 函数

(1)用 append 内置函数实现切片追加,实例:

package main

import (
	"fmt"
)

func main() {
    
    

	var a = []int{
    
    1, 2, 3}
	fmt.Printf("slice a : %v\n", a)
	var b = []int{
    
    4, 5, 6}
	fmt.Printf("slice b : %v\n", b)
	c := append(a, b...)
	fmt.Printf("slice c : %v\n", c)
	d := append(c, 7)
	fmt.Printf("slice d : %v\n", d)
	e := append(d, 8, 9, 10)
	fmt.Printf("slice e : %v\n", e)

}

输出结果:

slice a : [1 2 3]
slice b : [4 5 6]
slice c : [1 2 3 4 5 6]
slice d : [1 2 3 4 5 6 7]
slice e : [1 2 3 4 5 6 7 8 9 10]

(2)append 函数原理 :向 slice 尾部添加数据,返回新的 slice 对象。

package main

import (
	"fmt"
)

func main() {
    
    

	s1 := make([]int, 0, 5)
	fmt.Printf("%p\n", &s1)
	fmt.Println(s1)

	s2 := append(s1, 1)
	fmt.Printf("%p\n", &s2)
	fmt.Println(s2)

}

输出结果:

0xc000004078
[]
0xc0000040a8
[1]

(3)若给切片 append 的元素数量超出原 slice.cap 限制,就会重新给 slice 分配底层数组,并进行扩容:

package main

import (
	"fmt"
)

func main() {
    
    

	data := [...]int{
    
    0, 1, 2, 3, 4, 10: 0} //数组
	s := data[:2:3]
	fmt.Println(s)
	fmt.Println(len(s), cap(s))

	s = append(s, 100, 200, 300) // 一次 append 三个值,超出 s.cap 限制。

	fmt.Println(s, data)         // 重新分配底层数组,与原数组无关。
	fmt.Println(&s[0], &data[0]) // 比对底层数组起始指针。

}

输出结果:

[0 1]
2 3
[0 1 100 200 300] [0 1 2 3 4 0 0 0 0 0 0]
0xc00000c570 0xc00004a060

从输出结果可以看出:append 后的 s 被重新分配了底层数组(也就是说 s 的底层数组不再是 data,那么修改 s 的值不会再影响 data,它们不再有关联),并把原数组中的值拷贝到新数组中。这是因为超出了原切片的容量。在上例中,如果只追加一个值,则不会超过 s.cap 限制,也就不会重新分配。

切片的自动扩容策略是这样的:(文章 简单说说go语言Slice的底层实现 通过分析源码对这一点提出了质疑)
通常 以 2 倍容量 进行扩容,并重新分配底层数组(新底层数组的容量也变大)。
如果切片的容量小于 1024 个元素,扩容的时候就翻倍增加容量。一旦元素个数超过 1024 个元素,那么增长因子就变成 1.25 ,即每次增加原来容量的四分之一。
注意:扩容扩大的容量都是针对原来的容量而言的,而不是针对原来数组的长度而言的。

所以,在大批量添加数据时,建议 一次性分配足够大的空间 ,以减少内存分配和数据复制开销。或 初始化足够长的 len 属性,改用索引号进行操作。
及时释放不再使用的 slice 对象,避免持有过期数组,造成 GC 无法回收。

slice中 cap 重新分配规律:

package main

import (
	"fmt"
)

func main() {
    
    

	s := make([]int, 0, 1)
	fmt.Println(s)
	
	c := cap(s)                        //计算容量
	fmt.Println(c)

	for i := 0; i < 50; i++ {
    
    
		s = append(s, i)               //按理说 append 第2个元素时就超出了cap,这时会重新分配底层数组来扩大cap
		if n := cap(s); n > c {
    
    
			fmt.Printf("cap: %d -> %d\n", c, n)
			c = n
		}
	}

}

输出结果:

[]
1
cap: 1 -> 2
cap: 2 -> 4
cap: 4 -> 8
cap: 8 -> 16
cap: 16 -> 32
cap: 32 -> 64

我们可以发现,通常以 2 倍的 cap 重新分配。

提一嘴哈,如果给切片 append 元素时,不超切片容量就没事,操作的还是原数组:

package main

import (
	"fmt"
)

func main() {
    
    

	data := [...]int{
    
    0, 1, 2, 3, 4, 10: 0} //数组
	s := data[:2:5]                        //将切片容量扩大到5
	fmt.Println(s)
	fmt.Println(len(s), cap(s))

	s = append(s, 100, 200, 300) // 一次 append 三个值,这次没超出 s.cap 限制。

	fmt.Println(s, data)         
	fmt.Println(&s[0], &data[0]) // 比对底层数组起始指针

}

输出结果:

[0 1]
2 5
[0 1 100 200 300] [0 1 100 200 300 0 0 0 0 0 0]
0xc00004a060 0xc00004a060

3. copy() 函数(切片的深拷贝)

切片的拷贝分为2种,一种是浅拷贝,一种是深拷贝。
浅拷贝:源切片和目的切片共享同一底层数组空间,源切片修改,目的切片同样被修改。(赋值符实现)
深拷贝:源切片和目的切片各自都有彼此独立的底层数组空间,各自的修改,彼此不受影响。(使用内置函数copy()函数实现)

以下通过具体实例来说明:

浅拷贝:源切片和目的切片共享同一底层数组空间

package main

import "fmt"

func main(){
    
    
  slice1 := make([]int, 5, 5)
  slice2 := slice1
  slice1[1] = 1
  fmt.Println(slice1) //[0 1 0 0 0]
  fmt.Println(slice2) //[0 1 0 0 0]
}

深拷贝:源切片和目的切片各自都有彼此独立的底层数组空间

package main

import "fmt"

func main() {
    
    
	slice1 := make([]int, 5, 5)
	slice1[0] = 9
	fmt.Println(slice1)
	slice2 := make([]int, 4, 4)
	slice3 := make([]int, 5, 5)
	fmt.Println(slice2)
	fmt.Println(slice3)
	//拷贝
	fmt.Println(copy(slice2, slice1)) //4
	fmt.Println(copy(slice3, slice1)) //5
	//独立修改
	slice2[1] = 2
	slice3[1] = 3
	fmt.Println(slice1) //[9 0 0 0 0 0]
	fmt.Println(slice2) //[9 2 0 0]
	fmt.Println(slice3) //[9 3 0 0 0]
}

输出结果:

[9 0 0 0 0]
[0 0 0 0]
[0 0 0 0 0]
4
5
[9 0 0 0 0]
[9 2 0 0]
[9 3 0 0 0]

copy 函数的原理:copy 函数在两个 slice 间复制数据,复制长度以 len 小的为准。两个 slice 可指向同一底层数组,允许元素区间重叠。

下面的三个实例描述了深拷贝切片的 copy 方法的使用:

  1. 实例 1

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

package main

import "fmt"

func main() {
    
    
	var numbers []int
	printSlice(numbers)

	numbers = append(numbers, 0, 1, 2, 3, 4)
	printSlice(numbers)

	/* 创建切片 numbers1 是之前切片的两倍容量*/
	numbers1 := make([]int, len(numbers), (cap(numbers))*2)
	printSlice(numbers1)

	/* 拷贝 numbers 的内容到 numbers1 */
	copy(numbers1, numbers)

	printSlice(numbers1)
}

func printSlice(x []int) {
    
    
	fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}

输出结果:

len=0 cap=0 slice=[]
len=5 cap=6 slice=[0 1 2 3 4]
len=5 cap=12 slice=[0 0 0 0 0]
len=5 cap=12 slice=[0 1 2 3 4]
  1. 实例 2
package main

import (
	"fmt"
)

func main() {
    
    

	s1 := []int{
    
    1, 2, 3, 4, 5}
	fmt.Printf("slice s1 : %v\n", s1)
	s2 := make([]int, 10)
	fmt.Printf("slice s2 : %v\n", s2)
	copy(s2, s1)
	fmt.Printf("copied slice s1 : %v\n", s1)
	fmt.Printf("copied slice s2 : %v\n", s2)

}

输出结果:

slice s1 : [1 2 3 4 5]
slice s2 : [0 0 0 0 0 0 0 0 0 0]
copied slice s1 : [1 2 3 4 5]
copied slice s2 : [1 2 3 4 5 0 0 0 0 0]
  1. 实例 3
package main

import "fmt"

func main() {
    
    
	s := []int{
    
    1, 2, 3, 4}
	var s1 []int
	copy(s1, s)
	fmt.Println(s1) // []
	fmt.Println(s)  // [1 2 3 4]

	s1 = make([]int, 2)
	count := copy(s1, s)
	fmt.Println(s1)    // [1 2]
	fmt.Println(s)     // [1 2 3 4]
	fmt.Println(count) // 2

	s1[0] = 5
	fmt.Println(s1) // [5 2]
	fmt.Println(s)  // [1 2 3 4]

}

输出结果:

[]
[1 2 3 4]
[1 2]
[1 2 3 4]
2
[5 2]
[1 2 3 4]

从该例可知,copy 函数只会拷贝目标切片的长度个元素,并且 copy 后两个切片是互相没有影响的。

实例 2 和 3 说明 copy 函数在两个 slice 间复制数据,复制长度以 len 小的为准。

  1. 实例 4
package main

import (
	"fmt"
)

func main() {
    
    

	data := [...]int{
    
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	fmt.Println("array data : ", data)
	s1 := data[8:]
	s2 := data[:5]
	fmt.Printf("slice s1 : %v\n", s1)
	fmt.Printf("slice s2 : %v\n", s2)
	copy(s2, s1)
	fmt.Printf("copied slice s1 : %v\n", s1)
	fmt.Printf("copied slice s2 : %v\n", s2)
	fmt.Println("last array data : ", data)

}

输出结果:

array data :  [0 1 2 3 4 5 6 7 8 9]
slice s1 : [8 9]
slice s2 : [0 1 2 3 4]
copied slice s1 : [8 9]
copied slice s2 : [8 9 2 3 4]
last array data :  [8 9 2 3 4 5 6 7 8 9]

应及时将所需数据 copy 到较小的 slice,以便释放超大号底层数组内存。


六、切片遍历

import (
	"fmt"
)

func main() {
    
    

	data := [...]int{
    
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	slice := data[:]
	for index, value := range slice {
    
    
		fmt.Printf("inde : %v , value : %v\n", index, value)
	}

}

输出结果:

inde : 0 , value : 0
inde : 1 , value : 1
inde : 2 , value : 2
inde : 3 , value : 3
inde : 4 , value : 4
inde : 5 , value : 5
inde : 6 , value : 6
inde : 7 , value : 7
inde : 8 , value : 8
inde : 9 , value : 9

七、字符串和切片(string and slice)

1. string 底层就是一个 byte 的数组,因此,也可以进行切片操作。

对字符串进行切片操作:

package main

import (
	"fmt"
)

func main() {
    
    
	str := "hello world"
	s1 := str[0:5]
	fmt.Println(s1)

	s2 := str[6:]
	fmt.Println(s2)
}

输出结果:

hello
world

2. string本身是不可变的,因此要改变string中的字符。需要如下操作:

现在要改变英文字符串 “Hello world” 中的内容:

package main

import (
	"fmt"
)

func main() {
    
    
	str := "Hello world"
	s := []byte(str) //将字符串类型转换成一个切片,中文字符需要用[]rune(str)
	fmt.Println(s)

	s[6] = 'G'
	s = s[:8]
	s = append(s, '!')
	fmt.Println(s)

	str = string(s) //将切片转换成字符串
	fmt.Println(str)
}

输出结果:

[72 101 108 108 111 32 119 111 114 108 100]
[72 101 108 108 111 32 71 111 33]
Hello Go!

改变中文字符串的内容:

package main

import (
	"fmt"
)

func main() {
    
    
	str := "你好,世界!hello world!"
	s := []rune(str)
	s[3] = '够'
	s[4] = '浪'
	s[12] = 'g'
	s = s[:14]
	str = string(s)
	fmt.Println(str)
}

输出结果:

你好,够浪!hello go

多一嘴:
数组or切片转字符串:

strings.Replace(strings.Trim(fmt.Sprint(array_or_slice), "[]"), " ", ",", -1)

八、对切片[x:y:z] 两个冒号的理解

1. 常规切片截取(一个冒号)

可以通过设置下限及上限来设置截取切片 [lower_bound:upper_bound]
截取的内容是下标从 lower_boundupper_bound-1,示例如下:

package main

import "fmt"

func main() {
    
    
	/* 创建切片 */
	numbers := []int{
    
    0, 1, 2, 3, 4, 5, 6, 7, 8}
	printSlice(numbers)

	/* 打印原始切片 */
	fmt.Println("numbers ==", numbers)

	/* 打印子切片从索引1(包含) 到索引4(不包含)*/
	fmt.Println("numbers[1:4] ==", numbers[1:4])

	/* 默认下限为 0*/
	fmt.Println("numbers[:3] ==", numbers[:3])

	/* 默认上限为 len(s)*/
	fmt.Println("numbers[4:] ==", numbers[4:])

	numbers1 := make([]int, 0, 5)
	printSlice(numbers1)

	/* 打印子切片从索引  0(包含) 到索引 2(不包含) */
	number2 := numbers[:2]
	printSlice(number2)

	/* 打印子切片从索引 2(包含) 到索引 5(不包含) */
	number3 := numbers[2:5]
	printSlice(number3)

}

func printSlice(x []int) {
    
    
	fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}

输出结果:

len=9 cap=9 slice=[0 1 2 3 4 5 6 7 8]
numbers == [0 1 2 3 4 5 6 7 8]
numbers[1:4] == [1 2 3]
numbers[:3] == [0 1 2]
numbers[4:] == [4 5 6 7 8]
len=0 cap=5 slice=[]
len=2 cap=9 slice=[0 1]
len=3 cap=7 slice=[2 3 4]

2. 两个冒号的截取

package main

import (
	"fmt"
)

func main() {
    
    
	slice := []int{
    
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	d1 := slice[6:8]
	fmt.Println(d1, len(d1), cap(d1))
	d2 := slice[:6:8]
	fmt.Println(d2, len(d2), cap(d2))
}

输出结果:

[6 7] 2 4
[0 1 2 3 4 5] 6 8

常规切片截取:slice[6:8] 表示从下标第6位到第7位,长度len为2, 最大可扩充长度cap为 4(6到9,到尾部是默认的)。

两个冒号的截取:slice[:6:8] , slice内容为从 0 到第 5 位,长度 len 为6,最大扩充项 cap 设置为 8(0到7)。

所以说,两个冒号相对于一个冒号多的那个冒号是第二个冒号,这个冒号后的数字用于控制最大容量。
或者说常规切片截取相对于两个冒号的截取少了第二个冒号,省略了,默认第二个冒号后的数字到尾部。

总结:
a[x:y:z] 切片内容是: [x:y] 切片长度,[x:z] 切片容量。长度计算:y-x,容量计算:z-x。


参考链接

  1. Go 语言切片(Slice)
  2. 切片Slice

猜你喜欢

转载自blog.csdn.net/weixin_44211968/article/details/121356236