Go 语言切片详解
Go 切片(Slice) 是对 Go Array 的抽象。尽管数组似乎足够灵活,但它们具有固定长度的限制。无法增加数组的长度。切片克服了这个限制。它提供了许多 Array 所需的实用函数,在 Go 编程中被广泛使用。
提示
- 切片本身不拥有任何数据。它们只是对现有数组的引用。
创建切片
具有 T 类型元素的切片表示为 []T
。要定义切片,可以将其声明为数组而不指定数组大小
package main
import (
"fmt"
)
func main() {
a := [5]int{76, 77, 78, 79, 80}
var b []int = a[1:4] //使用 a数组的索引 1 到 3 的元素创建一个切片
fmt.Println(b)
}
语法a[start:end]
从数组创建一个切片,a 从 索引 start
开始到索引 end - 1
结束。所以上述程序中的a[1:4]创建了一个从索引 1 到 3 的数组的切片表示。因此切片 b 的值为 [77 78 79]。
上述代码编译执行结果如下
[77 78 79]
让我们看看另一种创建切片的方法。
package main
import (
"fmt"
)
func main() {
c := []int{6, 7, 8}
fmt.Println(c)
}
在上面的程序中,创建了一个包含 3 个整数的数组并返回一个存储在 c 中的切片引用。
使用 make 创建切片
make 语法如下
func make([]T, len, cap) []T
该函数可用于通过传递类型、长度和容量来创建切片。容量参数是可选的,如果不传则默认使用第二个参数 长度(len)的值。make 函数创建一个数组并返回对它的切片引用。
package main
import (
"fmt"
)
func main() {
i := make([]int, 5, 5)
fmt.Println(i)
}
使用 make 创建切片时,这些值默认为零。上述程序编译执行结果如下:
[0 0 0 0 0]
修改切片
切片不拥有它自己的任何数据。它只是底层数组的表示。对切片所做的任何修改都将反映在底层数组中。
package main
import (
"fmt"
)
func main() {
darr := [...]int{57, 89, 90, 82, 100, 78, 67, 69, 59}
dslice := darr[2:5]
fmt.Println("修改之前的数组",darr)
for i := range dslice {
dslice[i]++
}
fmt.Println("修改之后的数组",darr)
}
上述代码编译执行结果如下
修改之前的数组 [57 89 90 82 100 78 67 69 59]
修改之后的数组 [57 89 91 83 101 78 67 69 59]
当多个切片共享同一基础数组时,改变任意一个切片,都将会反映到数组中。
package main
import (
"fmt"
)
func main() {
numa := [3]int{78, 79 ,80}
nums1 := numa[:] //创建一个包含数组中所有元素的切片
nums2 := numa[:]
fmt.Println("修改之前的数组:",numa)
nums1[0] = 100
fmt.Println("修改切片 nums1 之后的数组:", numa)
nums2[1] = 101
fmt.Println("修改切片 nums2 之后的数组:", numa)
}
上述代码执行结果如下
修改之前的数组: [78 79 80]
修改切片 nums1 之后的数组: [100 79 80]
修改切片 nums2 之后的数组: [100 101 80]
从输出可以看到,很明显当切片共享相同的数组时。对切片进行的修改都反映在数组中。
切片的len()和cap()函数
切片的长度是切片中元素的数量。切片的容量是底层数组中从创建切片的索引开始的元素数。
让我们编写一些代码来更好地理解这一点。
package main
import (
"fmt"
)
func main() {
fruitarray := [...]string{"apple", "orange", "grape", "mango", "water melon", "pine apple", "chikoo"}
fruitslice := fruitarray[1:3]
fmt.Printf("切片的长度:%d; 切片的容量:%d\n", len(fruitslice), cap(fruitslice)) //length of fruitslice is 2 and capacity is 6
}
在上面的程序中,fruitslice 是从 fruitarray 创建的。 因此 fruitslice 的长度 为 2。
数组 fruitarray 的长度为7。fruiteslice 是从 fruitarry 的索引1开始创建的。因此,fruitslice 的容量是 fruitarray 从索引 1 开始至最后的元素数量,所以该值是6。因此,fruitslice 容量为 6。
因此上述代码执行结果如下
切片的长度:2; 切片的容量:6
切片 append() 函数
正如我们已经知道的,数组被限制为固定长度,并且它们的长度不能增加。切片是动态的,可以使用append函数将新元素附加到切片。附加函数的定义是
func append(s []T, x ...T) []T
函数定义中的x ...T表示函数接受参数 x 的可变数量的参数。这些类型的函数称为可变参数函数。
不过,有一个问题可能会困扰我们。如果切片由数组支持并且数组本身具有固定长度,那么切片为什么具有动态长度。那么在底层发生的事情是,当新元素附加到切片时,会创建一个新数组。现有数组的元素被复制到这个新数组,并返回这个新数组的新切片引用。新切片的容量现在是旧切片的两倍。很酷吧:)。
package main
import (
"fmt"
)
func main() {
cars := []string{"Ferrari", "Honda", "Ford"}
fmt.Println("cars:", cars, "长度:", len(cars), "; 容量", cap(cars)) //cars的容量 3
cars = append(cars, "Toyota")
fmt.Println("cars:", cars, "新的长度", len(cars), "; 容量", cap(cars)) //cars的容量现在是6
}
在上面的程序中,cars最初的容量是3。接着我们在cars中添加了一个新元素,append(cars, "Toyota")。它将返回一个新的切片给cars变量。现在 cars 的容量翻了一番,变成了6。 上述程序执行结果如下
cars: [Ferrari Honda Ford] 长度: 3 ; 容量 3
cars: [Ferrari Honda Ford Toyota] 新的长度 4 ; 容量 6
多维切片
与数组类似,切片可以有多个维度。
package main
import (
"fmt"
)
func main() {
pls := [][]string {
{"C", "C++"},
{"JavaScript"},
{"Go", "Rust"},
}
for _, v1 := range pls {
for _, v2 := range v1 {
fmt.Printf("%s ", v2)
}
fmt.Printf("\n")
}
}
上述代码编译执行结果如下
C C++
JavaScript
Go Rust