Golang 切片综合指南

概述

在本文中,我们将讨论 “切片” 的概念,它是 Golang 中使用的一种重要数据结构。这一数据结构为你提供了处理与管理数据集合的方法。切片是围绕动态数组的概念构建的,它与动态数组相似,可以根据你的需要而伸缩。

  • 切片在增长方面是动态的,因为它们有自己的内置函数 append,可以快速高效地增长切片。
  • 您还可以通过切割底层内存来减小切片的大小。
  • 在底层内存中切片是在连续的块上分配的,因此切片为你提供的便利之处包括:索引、迭代与垃圾回收优化。

    切片表示

  • 切片不存储任何数据;它只描述底层数组的一部分。
  • 切片使用一个包含三个字段的结构表示:指向底层数组的指针(pointer)、长度(length)与容量(capacity)。
  • 这个数据结构类似于切片的描述符。

Slice representation

  • Pointer:指针用于指向数组的第一个元素,这个元素可以通过切片进行访问。在这里,指向的元素不必是数组的第一个元素。
  • Length:长度代表数组中所有元素的总数。
  • Capacity:容量表示切片可扩展的最大大小。

使用长度申明切片

在声明切片过程中,当你仅指定长度(Length)时,容量(Capacity)值与长度(Length)值相同。
Declare a slice using the length

1
2
3
4
5
// Declaring a slice by length. Create a slice of int. 
// Contains a length and capacity of 5 elements.
slice := make([]int, 5)
fmt.Println(len(slice)) // Print 5
fmt.Println(cap(slice)) // Print 5

使用长度和容量申明切片

在声明切片过程中,当你分别指定长度(Length)和容量(Capacity)时,这将初始化一段无法访问的底层数组来创建一个具有可用容量的切片。

1
2
3
4
5
6
7
8
/* 
Declaring a slice by length and capacity
Create a slice of integers.
Contains a length of 3 and has a capacity of 5 elements.
*/

slice := make([]int, 3, 5)
fmt.Println(len(slice)) // Print 3
fmt.Println(cap(slice)) // Print 5

Declare a slice with length and capacity

但是请注意,尝试创建容量小于长度的切片是不允许的。

使用切片字面量创建切片

创建切片的惯用方法是使用切片字面量。它与创建数组相似,只是它不需要在 [ ] 操作符中指定值。你初始化切片时所用元素的数量将决定切片的初始长度与容量。

1
2
3
4
5
6
7
8
9
10
// Create a slice of strings. 
// Contains a length and capacity of 5 elements.
slice := []string{"Red", "Blue", "Green", "Yellow", "Pink"}
fmt.Println(len(slice)) //Print 5
fmt.Println(cap(slice)) //Print 5
// Create a slice of integers.
// Contains a length and capacity of 3 elements.
intSlice:= []int{10, 20, 30}
fmt.Println(len(intSlice)) //Print 3
fmt.Println(cap(intSlice)) //Print 3

声明一个带有索引位置的切片

当使用切片字面量时,你可以初始化切片的长度与容量。你所需要做的就是初始化表示所需长度和容量的索引。下面的语法将创建一个长度和容量均为 100 的切片。

1
2
3
4
5
6
7
// Create a slice of strings.
// Initialize the 100th element with an empty string.
slice := []int{99: 88}
fmt.Println(len(slice))
// Print 100
fmt.Println(cap(slice))
// Print 100

Declare a slice with index positions

声明数组与切片的区别:

  • 如果你使用[]操作符中指定一个值,那么你在创建一个数组。
  • 如果你不在[]中指定值,则创建一个切片。
1
2
3
4
5
// Create an array of three integers. 
array := [3]int{10, 20, 30}

//Create a slice of integers with a length and capacity of three.
slice := []int{10, 20, 30}

声明一个 nil 切片

  • 切片用 nil 代表零值。
  • 一个 nil 切片的长度和容量等于 0,且没有底层数组。
1
2
3
4
5
6
7
8
// Create a nil slice of integers. 
var slice []int32
fmt.Println(slice == nil)
//This line will print true
fmt.Println(len(slice))
// This line will print 0
fmt.Println(cap(slice))
// This line will print 0

Declare a nil slice

声明一个空切片

还可以通过初始化声明切片创建一个空切片。

1
2
3
4
5
6
7
8
9
10
// Use make to create an empty slice of integers.
sliceOne := make([]int, 0)
// Use a slice literal to create an empty slice of integers.
sliceTwo := []int{}
fmt.Println(sliceOne == nil) // This will print false
fmt.Println(len(sliceOne)) // This will print 0
fmt.Println(cap(sliceOne)) // This will print 0
fmt.Println(sliceTwo == nil) // This will print false
fmt.Println(len(sliceTwo)) // This will print 0
fmt.Println(cap(sliceTwo)) // This will print 0

Declare an empty slice

为任何特定索引赋值

要修改单个元素的值,请使用[]操作符。

1
2
3
4
5
6
// Create a slice of integers.
// Contains a length and capacity of 4 elements.
slice := []int{10, 20, 30, 40}
fmt.Println(slice) //This will print [10 20 30 40]
slice[1] = 25 // Change the value of index 1.
fmt.Println(slice) // This will print [10 25 30 40]

Assign a value to any specific index

对切片进行切片

我们之所以称呼切片为切片,是因为你可以通过对底层数组的一部分进行切片来创建一个新的切片。

1
2
3
4
5
6
7
8
9
10
11
12
/* Create a slice of integers. Contains a 
length and capacity of 5 elements.*/

slice := []int{10, 20, 30, 40, 50}
fmt.Println(slice) // Print [10 20 30 40 50]
fmt.Println(len(slice)) // Print 5
fmt.Println(cap(slice)) // Print 5
/* Create a new slice.Contains a length
of 2 and capacity of 4 elements.*/

newSlice := slice[1:3]
fmt.Println(slice) //Print [10 20 30 40 50]
fmt.Println(len(newSlice)) //Print 2
fmt.Println(cap(newSlice)) //Print 4

Take a slice of a slice

在执行切片操作之后,我们拥有两个共享同一底层数组的切片。然而,这两个切片以不同的方式查看底层数组。原始切片认为底层数组的容量为 5,但 newSlice 与之不同,对 newSlice 而言,底层数组的容量为 4。newSlice 无法访问位于其指针之前的底层数组元素。就 newSlice 而言,这些元素甚至并不存在。使用下面的方式可以为任意切片后的 newSlice 计算长度和容量。

切片的长度与容量如何计算?

切片 slice[i:j] 的底层数组容量为 k 长度(Length):j - i 容量(Capacity):k - i

计算新的长度和容量

切片 slice[1:3] 的底层数组容量为 5 长度(Length):3 - 1 = 2 容量(Capacity):5 - 1 = 4

对一个切片进行更改的结果

一个切片对底层数组的共享部分所做的更改可以被另一个切片看到。

1
2
3
4
5
6
7
8
9
// Create a slice of integers.
// Contains a length and capacity of 5 elements.
slice := []int{10, 20, 30, 40, 50}
// Create a new slice.
// Contains a length of 2 and capacity of 4 elements.
newSlice := slice[1:3]
// Change index 1 of newSlice.
// Change index 2 of the original slice.
newSlice[1] = 35

将数值 35 分配给 newSlice 的第二个元素后,该更改也可以在原始切片的元素中被看到。

运行时错误显示索引超出范围

一个切片只能访问它长度以内的索引位。尝试访问超出长度的索引位元素将引发一个运行时错误。与切片容量相关联的元素只能用于切片增长。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Create a slice of integers.
// Contains a length and capacity of 5 elements.
slice := []int{10, 20, 30, 40, 50}
// Create a new slice.
// Contains a length of 2 and capacity of 4 elements.
newSlice := slice[1:3]
// Change index 3 of newSlice.
// This element does not exist for newSlice.
newSlice[3] = 45

/*
Runtime Exception:
panic: runtime error: index out of range
*/

切片增长

与使用数组相比,使用切片的优势之一是:你可以根据需要增加切片的容量。当你使用内置函数 「append」 时,Golang 会负责处理所有操作细节。

  • 使用 append 前,你需要一个源切片和一个要追加的值。
  • 当你的 append 调用并返回时,它将为你提供一个更改后的新切片。
  • append 函数总会增加新切片的长度。
  • 另一方面,容量可能会受到影响,也可能不会受到影响,这取决于源切片的可用容量。

使用 append 向切片追加元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*  Create a slice of integers.
Contains a length and capacity of 5 elements.*/

slice := []int{10, 20, 30, 40, 50}

/* Create a new slice.
Contains a length of 2 and capacity of 4 elements.*/

newSlice := slice[1:3]
fmt.Println(len(newSlice)) // Print 2
fmt.Println(cap(newSlice)) // Print 4

/* Allocate a new element from capacity.
Assign the value of 60 to the new element.*/

newSlice = append(newSlice, 60)
fmt.Println(len(newSlice)) // Print 3
fmt.Println(cap(newSlice)) // Print 4

当切片的底层数组没有可用容量时,append 函数将创建一个新的底层数组,拷贝正在引用的现有值,然后再分配新值。

使用 append 增加切片的长度和容量

1
2
3
4
5
6
7
8
9
10
11
// Create a slice of integers.
// Contains a length and capacity of 4 elements.
slice := []int{10, 20, 30, 40}
fmt.Println(len(slice)) // Print 4
fmt.Println(cap(slice)) // Print 4

// Append a new value to the slice.
// Assign the value of 50 to the new element.
newSlice= append(slice, 50)
fmt.Println(len(newSlice)) //Print 5
fmt.Println(cap(newSlice)) //Print 8

Increase the length and capacity of a slice

在 append 操作后,newSlice 被给予一个自有的底层数组,该底层数组的容量是原底层数组容量的两倍。在增加底层数组容量时,append 操作十分聪明。举个例子,当切片的容量低于 1,000 个元素时,容量增长总是翻倍的。一旦元素的数量超过 1,000 个,容量就会增长 1.25 倍,即 25%。随着时间的推移,这种增长算法可能会在 Golang 中发生变化。

更改新切片不会对旧切片产生任何影响,因为新切片现在有一个不同的底层数组,它的指针指向一个新分配的数组。

将一个切片追加到另一个切片中

内置函数 append 还是一个可变参数函数。这意味着你可以传递多个值来追加到单个切片中。如果你使用 … 运算符,可以将一个切片的所有元素追加到另一个切片中。

1
2
3
4
5
6
// Create two slices each initialized with two integers.
slice1:= []int{1, 2}
slice2 := []int{3, 4}
// Append the two slices together and display the results.
fmt.Println(append(slice1, slice2...))
//Output: [1 2 3 4]

对切片执行索引

  • 通过指定一个下限和一个上限来形成切片,例如:a[low:high]。这将选择一个半开范围,其中包含切片的第一个元素,但不包含切片的最后一个元素。
  • 你可以省略上限或下限,这将使用它们的默认值。下限的默认值是 0,上限的默认值是切片的长度。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
a := [...]int{0, 1, 2, 3} 
// an array
s := a[1:3]
// s == []int{1, 2}
// cap(s) == 3
s = a[:2]
// s == []int{0, 1}
// cap(s) == 4
s = a[2:]
// s == []int{2, 3}
// cap(s) == 2
s = a[:]
// s == []int{0, 1, 2, 3}
// cap(s) == 4

遍历切片

Go 有一个特殊的关键字 range,你可以使用该关键字对切片进行遍历。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Create a slice of integers.
// Contains a length and capacity of 4 elements.
slice := []int{10, 20, 30, 40}
// Iterate over each element and display each value.
for index, value := range slice {
fmt.Printf("Index: %d Value: %d\n", index, value)
}
/*
Output:
Index: 0 Value: 10
Index: 1 Value: 20
Index: 2 Value: 30
Index: 3 Value: 40
*/

  • 在遍历切片时,关键字 range 将返回两个值。
  • 第一个值是索引下标,第二个值是索引位中值的副本。
  • 一定要知道 range 是在复制值,而不是返回值的引用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
Create a slice of integers.Contains
a length and capacity of 4 elements.
*/

slice := []int{10, 20, 30, 40}
/*
Iterate over each element and display
the value and addresses.
*/

for index, value := range slice {
fmt.Printf("Value: %d Value-Addr: %X ElemAddr: %X\n",
value, &value, &slice[index])
}
/*
Output:
Value: 10 Value-Addr: 10500168 ElemAddr: 1052E100
Value: 20 Value-Addr: 10500168 ElemAddr: 1052E104
Value: 30 Value-Addr: 10500168 ElemAddr: 1052E108
Value: 40 Value-Addr: 10500168 ElemAddr: 1052E10C
*/

range 关键字提供元素的拷贝。

如果你不需要下标值,你可以使用下划线字符丢弃该值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Create a slice of integers.
// Contains a length and capacity of 4 elements.
slice := []int{10, 20, 30, 40}
// Iterate over each element and display each value.
for _, value := range slice {
fmt.Printf("Value: %d\n", value)
}
/*
Output:
Value: 10
Value: 20
Value: 30
Value: 40
*/

关键字 range 总是从开始处遍历一个切片。如果你需要对切片的迭代进行更多的控制,你可以使用传统的 for 循环。

1
2
3
4
5
6
7
8
9
10
11
12
// Create a slice of integers.
// Contains a length and capacity of 4 elements.
slice := []int{10, 20, 30, 40}
// Iterate over each element starting at element 3.
for index := 2; index < len(slice); index++ {
fmt.Printf("Index: %d Value: %d\n", index, slice[index])
}
/*
Output:
Index: 2 Value: 30
Index: 3 Value: 40
*/

##总结
在本文中,我们深入探讨了切片的概念。我们了解到,切片并不存储任何数据,而是描述了底层数组的一部分。我们还看到,切片可以在底层数组的范围内增长和收缩,并配合索引可作为数组使用;切片的零值是 nil;函数 lencapappend 都将 nil 看作一个长度和容量都为 0 的空切片;你可以通过切片字面量或调用 make 函数(将长度和容量作为参数)来创建切片。希望这些对你有所帮助!

免责声明

我参考了各种博客、书籍和媒体故事来撰写这篇文章。如有任何疑问,请在评论中与我联系。

到此为止……开心编码……快乐学习😃

译自:掘金翻译计划 原文:https://codeburst.io/a-comprehensive-guide-to-slices-in-golang-bacebfe46669

微信订阅号

-------------本文结束感谢您的阅读-------------