10.切片
介绍
切片也叫slice ,是对数组的一种方便、灵活和强大的包装器。切片本身不拥有任何数据。它们只是对现有数组的引用。
创建slice
具有类型 T 元素的切片由 []T 表示
package main
import (
"fmt"
)
func main() {
a := [5]int{76, 77, 78, 79, 80}
var b []int = a[1:4] //creates a slice from a[1] to a[3]
fmt.Println(b)
}
语法 a[start:end] 从数组 a 从索引 start 开始到索引 end - 1 创建一个切片。所以在第一行。上述程序 a[1:4] 从索引 1 到 3 开始创建数组 a 的切片表示。因此切片 b 具有值 [77 78 79] 。
让我们看看另一种创建切片的方法。
package main
import (
"fmt"
)
func main() {
c := []int{6, 7, 8} //creates and array and returns a slice reference
fmt.Println(c)
}
在上面的程序中, c := []int{6, 7, 8} 创建一个包含 3 个整数的数组,并返回一个存储在 c 中的切片引用。
修改切片
切片不拥有自己的任何数据。它只是底层数组的表示。对切片所做的任何修改都将反映在底层数组中。
package main
import (
"fmt"
)
func main() {
darr := [...]int{57, 89, 90, 82, 100, 78, 67, 69, 59}
dslice := darr[2:5]
fmt.Println("array before",darr)
for i := range dslice {
dslice[i]++
}
fmt.Println("array after",darr)
}
上述程序,我们从数组的索引 2、3、4 创建了 dslice 。 for 循环将这些索引中的值递增 1。当我们在 for 循环之后打印数组时,我们可以看到对切片的更改反映在数组中。该程序的输出是
array before [57 89 90 82 100 78 67 69 59]
array after [57 89 91 83 101 78 67 69 59]
当多个切片共享同一个底层数组时,每个切片所做的更改都将反映在数组中。
package main
import (
"fmt"
)
func main() {
numa := [3]int{78, 79 ,80}
nums1 := numa[:] //creates a slice which contains all elements of the array
nums2 := numa[:]
fmt.Println("array before change 1",numa)
nums1[0] = 100
fmt.Println("array after modification to slice nums1", numa)
nums2[1] = 101
fmt.Println("array after modification to slice nums2", numa)
}
numa[:] 中缺少起始值和结束值。开始和结束的默认值分别是 0 和 len(numa) 。 nums1 和 nums2 切片共享同一个数组。该程序的输出是
array before change 1 [78 79 80]
array after modification to slice nums1 [100 79 80]
array after modification to slice nums2 [100 101 80]
从输出中可以清楚地看出,当切片共享同一个数组时。对切片所做的修改反映在数组中。
切片的长度和容量
切片的长度是切片中元素的数量。切片的容量是基础数组中从创建切片的索引开始的元素数。
让我们写一些代码来更好地理解这一点。
package main
import (
"fmt"
)
func main() {
fruitarray := [...]string{"apple", "orange", "grape", "mango", "water melon", "pine apple", "chikoo"}
fruitslice := fruitarray[1:3]
fmt.Printf("length of slice %d capacity %d", len(fruitslice), cap(fruitslice)) //length of fruitslice is 2 and capacity is 6
}
在上面的程序中, fruitslice 是从 fruitarray 的索引 1 和 2 创建的。因此 fruitslice 的长度是2。
fruitarray 的长度是 7。 fruiteslice 是从 fruitarray 的索引 1 创建的。因此, fruitslice 的容量是 fruitarray 中从索引 1 开始的元素数,即从 orange 开始,该值为 6 。因此 fruitslice 的容量是 6。程序打印 slice 2 的容量为 6 的长度。
切片可以重新切片到它的容量。超出此范围的任何内容都会导致程序抛出运行时错误。
package main
import (
"fmt"
)
func main() {
fruitarray := [...]string{"apple", "orange", "grape", "mango", "water melon", "pine apple", "chikoo"}
fruitslice := fruitarray[1:3]
fmt.Printf("length of slice %d capacity %d\n", len(fruitslice), cap(fruitslice)) //length of is 2 and capacity is 6
fruitslice = fruitslice[:cap(fruitslice)] //re-slicing furitslice till its capacity
fmt.Println("After re-slicing length is",len(fruitslice), "and capacity is",cap(fruitslice))
}
在行号上述程序的 11, fruitslice 被重新切片到它的容量。上面的程序输出,
length of slice 2 capacity 6
After re-slicing length is 6 and capacity is 6
使用 make 创建切片
func make([]T, len, cap) []T 可用于通过传递类型、长度和容量来创建切片。容量参数是可选的,默认为长度。 make 函数创建一个数组并返回对它的切片引用。
package main
import (
"fmt"
)
func main() {
i := make([]int, 5, 5)
fmt.Println(i)
}
当使用 make 创建切片时,这些值默认为零。上面的程序会输出 [0 0 0 0 0] 。
附加到切片
正如我们已经知道的那样,数组的长度是固定的,并且它们的长度不能增加。切片是动态的,可以使用 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, "has old length", len(cars), "and capacity", cap(cars)) //capacity of cars is 3
cars = append(cars, "Toyota")
fmt.Println("cars:", cars, "has new length", len(cars), "and capacity", cap(cars)) //capacity of cars is doubled to 6
}
在上面的程序中, cars 的容量初始为3。我们向 cars 添加了一个新元素。 将 append(cars, "Toyota") 返回的切片再次赋值给cars。现在汽车的容量增加一倍,变成6辆。上面程序的输出是
cars: [Ferrari Honda Ford] has old length 3 and capacity 3
cars: [Ferrari Honda Ford Toyota] has new length 4 and capacity 6
切片类型的零值是 nil 。 nil 切片的长度和容量为 0。可以使用 append 函数将值附加到 nil 切片。
package main
import (
"fmt"
)
func main() {
var names []string //zero value of a slice is nil
if names == nil {
fmt.Println("slice is nil going to append")
names = append(names, "John", "Sebastian", "Vinay")
fmt.Println("names contents:",names)
}
}
在上面的程序中, names 是 nil,我们在 names 上附加了 3 个字符串。该程序的输出是
slice is nil going to append
names contents: [John Sebastian Vinay]
也可以使用 ... 运算符将一个切片附加到另一个切片。您可以在可变参数函数教程中了解有关此运算符的更多信息。
package main
import (
"fmt"
)
func main() {
veggies := []string{"potatoes","tomatoes","brinjal"}
fruits := []string{"oranges","apples"}
food := append(veggies, fruits...)
fmt.Println("food:",food)
}
在行号上面的 10 个程序食物是通过将 fruits 附加到 veggies 创建的。程序的输出是 food: [potatoes tomatoes brinjal oranges apples]
将切片传递给函数
切片可以被认为是在内部由结构类型表示的。这是它的样子,
type slice struct {
Length int
Capacity int
ZerothElement *byte
}
切片包含长度、容量和指向数组第零个元素的指针。当一个切片被传递给一个函数时,即使它是按值传递的,指针变量也会引用相同的底层数组。因此,当切片作为参数传递给函数时,函数内部所做的更改在函数外部也是可见的。让我们编写一个程序来检查一下。
package main
import (
"fmt"
)
func subtactOne(numbers []int) {
for i := range numbers {
numbers[i] -= 2
}
}
func main() {
nos := []int{8, 7, 6}
fmt.Println("slice before function call", nos)
subtactOne(nos) //function modifies the slice
fmt.Println("slice after function call", nos) //modifications are visible outside
}
上述程序函数subtactOne调用将切片的每个元素递减 2。在函数调用后打印切片时,这些更改是可见的。如果您还记得的话,这与数组不同,数组在函数内部对数组所做的更改在函数外部不可见。上述程序的输出是,
slice before function call [8 7 6]
slice after function call [6 5 4]
多维切片
类似于数组,切片可以有多个维度。
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
更多
更多关于go基础的内容,请 访问社区官网 cit965.com :)