教程 > Go 教程 > Go 基础 阅读:307

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

go slice长度和容量


切片 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

查看笔记

扫码一下
查看教程更方便