文章目录

  • 一、切片
  • 二、声明切片
    • 方法1
    • 方法2
    • 总结:创建切片的各种方式
  • 三、切片初始化
    • 1. 声明的同时初始化
    • 2. 用数组初始化切片
    • 3. 切片的内存布局:
      • 读写操作实际目标是底层数组,只需注意索引号的差别。(本质:切片是数组的一个引用)
      • 这是因为,我们直接创建 slice 对象时,系统会自动分配底层数组
      • 还可用指针直接访问底层数组,退化成普通数组操作
  • 四、一些复杂类型切片
    • 1. [][]T,是指元素类型为 []T 的切片。
    • 2. 结构体数组 / 切片
  • 五、len()、cap()、append()、copy()
    • 1. len() 和 cap() 函数
    • 2. append() 函数
      • (1)用 append 内置函数实现切片追加,实例:
      • (2)append 函数原理 :向 slice 尾部添加数据,返回新的 slice 对象。
      • (3)若给切片 append 的元素数量超出原 slice.cap 限制,就会重新给 slice 分配底层数组,并进行扩容:
    • 3. copy() 函数(切片的深拷贝)
  • 六、切片遍历
  • 七、字符串和切片(string and slice)
    • 1. string 底层就是一个 byte 的数组,因此,也可以进行切片操作。
    • 2. string本身是不可变的,因此要改变string中的字符。需要如下操作:
  • 八、对切片[x:y:z] 两个冒号的理解
    • 1. 常规切片截取(一个冒号)
    • 2. 两个冒号的截取
  • 参考链接

一、切片

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   intcap   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 mainimport "fmt"func main() {var numbers []intprintSlice(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 mainimport "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 mainimport "fmt"func main() {//1.声明切片var s1 []intif 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 mainimport ("fmt"
)func main() {data := [...]int{0, 1, 2, 3, 4, 5}s := data[2:4]s[0] += 100s[1] += 200fmt.Println(s)fmt.Println(data)
}

输出结果:

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

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

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

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

实例:

package mainimport "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 mainimport ("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 mainimport "fmt"func main() {s := []int{0, 1, 2, 3}p := &s[2] // *int, 获取底层数组元素指针。*p += 100fmt.Println(s)
}

输出结果:

[0 1 102 3]

四、一些复杂类型切片

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

实例:

package mainimport ("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 mainimport ("fmt"
)func main() {d := [5]struct { //结构体数组x int}{}             //未初始化s := d[:]       //切片d[1].x = 10s[2].x = 20fmt.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 mainimport "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 mainimport ("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 mainimport ("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 mainimport ("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 mainimport ("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,这时会重新分配底层数组来扩大capif 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 mainimport ("fmt"
)func main() {data := [...]int{0, 1, 2, 3, 4, 10: 0} //数组s := data[:2:5]                        //将切片容量扩大到5fmt.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 mainimport "fmt"func main(){slice1 := make([]int, 5, 5)slice2 := slice1slice1[1] = 1fmt.Println(slice1) //[0 1 0 0 0]fmt.Println(slice2) //[0 1 0 0 0]
}

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

package mainimport "fmt"func main() {slice1 := make([]int, 5, 5)slice1[0] = 9fmt.Println(slice1)slice2 := make([]int, 4, 4)slice3 := make([]int, 5, 5)fmt.Println(slice2)fmt.Println(slice3)//拷贝fmt.Println(copy(slice2, slice1)) //4fmt.Println(copy(slice3, slice1)) //5//独立修改slice2[1] = 2slice3[1] = 3fmt.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 mainimport "fmt"func main() {var numbers []intprintSlice(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 mainimport ("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 mainimport "fmt"func main() {s := []int{1, 2, 3, 4}var s1 []intcopy(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) // 2s1[0] = 5fmt.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 mainimport ("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 mainimport ("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 mainimport ("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 mainimport ("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 mainimport "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 mainimport ("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

【Go】Go 语言切片(Slice)相关推荐

  1. go语言基础数据结构学习---- 数组, 列表(list)和切片(slice)

    go语言基础数据结构学习–> 数组, 列表(list)和切片(slice) go 语言中的 数组是类型相同的元素的集合, 列表是双链表的容器, 可以添加不同类型的数据切片是对现有数组的引用, 比 ...

  2. 深度解密Go语言之Slice

    Go 语言的 slice 很好用,不过也有一些坑.slice 是 Go 语言一个很重要的数据结构.网上已经有很多文章写过了,似乎没必要再写.但是每个人看问题的视角不同,写出来的东西自然也不一样.我这篇 ...

  3. 位置2的索引超出数组范围不能超过10_golang学习笔记--切片slice 与数组 arr

    因为我是写php的,刚开始写go的时候老是被数组坑.因为在go中的数组是固定长度,所以会存在越界的时候.而在go中也很少直接使用数组.更多的时候会使用slice(切片). 数组 数组是一个由固定长度的 ...

  4. 切片 go 去除第一个_golang学习笔记--切片slice 与数组 arr

    因为我是写php的,刚开始写go的时候老是被数组坑.因为在go中的数组是固定长度,所以会存在越界的时候.而在go中也很少直接使用数组.更多的时候会使用slice(切片). 数组 数组是一个由固定长度的 ...

  5. Go开发 之 容器(数组Array、切片slice、映射map、列表list)

    文章目录 0.唠唠叨叨 1.数组-Array 1.1.什么是数组 1.1.1.数组的声明 1.1.2.比较两个数组是否相等 1.1.3.示例 1.1.3.1.通过索引下标访问元素 1.1.3.2.数组 ...

  6. Golang 入门 : 切片(slice)

    切片(slice)是 Golang 中一种比较特殊的数据结构,这种数据结构更便于使用和管理数据集合.切片是围绕动态数组的概念构建的,可以按需自动增长和缩小.切片的动态增长是通过内置函数 append( ...

  7. 文心一言 VS 讯飞星火 VS chatgpt (19)-- go语言的slice和rust语言的Vec的扩容流程是什么?

    go语言的slice和rust语言的Vec的扩容流程是什么? 文心一言: 在Go语言中,slice(切片)和Rust语言中的Vec都是用于存储一组固定长度的元素的数据结构.它们的扩容流程略有不同,下面 ...

  8. 2023-05-20:go语言的slice和rust语言的Vec的扩容流程是什么?

    2023-05-20:go语言的slice和rust语言的Vec的扩容流程是什么? 答案2023-05-20: go语言的slice扩容流程 go版本是1.20.4. 扩容流程见源码见runtime/ ...

  9. Golang如何遍历切片slice

    前言 这两天用golang开发一个把企业微信的打卡记录同步到HR-OA系统的一个中间表的功能,同步时把从企业微信接口里查询到的打卡记录放到一个对象的切片里,然后遍历切片把每条打卡记录写到中间表,这里涉 ...

最新文章

  1. django数据模型字段和通用参数说明
  2. Bootstrap学习遇到的role属性--- 无障碍网页应用属性
  3. Boost:双图bimap的修改和替换测试程序
  4. java加载xml配置文件_java读取配置文件的几种方法
  5. nginx系列之三:日志配置
  6. Angular rxjs Subject笔记
  7. 如何获取UIWebView中全屏播放视频事件
  8. [python网络编程]使用scapy修改源IP发送请求
  9. CentOS下编译安装Gcc-4.9
  10. 守护线程C语言windows,C言语如何利用子线程刷新主线程
  11. 把服务器sql数据库导出excel文件,从sql中导出到excel表格数据-如何把SQLServer表数据导出为Excel文件...
  12. PaysApi第三方支付接口的接入与使用 React前端SSM后端
  13. gitlab的账号注册以及分组
  14. 关于无线网卡驱动更新后无法使用(错误代码43)的问题
  15. Python使用tkinter库制作带有Laber标签、Entry文本框、Progressbar进度条、text日志框等元素的GUI操作界面
  16. 如何评价2021年的B站跨年晚会
  17. uniapp开发的微信小程序如何上传至微信小程序平台-完整简单步骤
  18. ArcBlock 分享 | 冒志鸿:我为区块链技术落地“狂”!
  19. 计算机组装与维护我要自学网,【答疑】3D机械建模软件有哪些,3D机械建模一般用的是哪个软件? - 视频教程线上学...
  20. Python都能干什么

热门文章

  1. 红色的反色是青色引申出PS怎么反色之教程
  2. 转载05:全能程序员学习路线
  3. mac火狐浏览器不能打开任何网址问题解决方案!
  4. 随机位置生成小方块案例
  5. C# 图片与byte[]转换
  6. MCDF实验_lab0(0)
  7. 企业微信打卡怎么防止作弊?看看其他企业是怎么做的
  8. 7个等级 容灾等级_容灾备份的柒个国际标准等级(一)
  9. 电子警察是属于计算机应用中的,计算机应用基础测试题.doc
  10. 【人工智能项目】缺陷检测分割数据集相关整理分享