Go-学会使用切片

本节重点:

  • 学会使用切片

切片(slice)是对数组一个连续片段的引用(该数组我们称之为相关数组,通常是匿名的),所以切片是一个引用类型。
实际开发中我们很少使用数组,取而代之的是切片。切片是一个 长度可变的数组

创建切片

具有 T 类型元素的切片表示为[]T

package main
 
import (  
    "fmt"
)
 
func main() {  
    a := [5]int{76, 77, 78, 79, 80}
    var b []int = a[1:4] //创建一个切片 a[1] to a[3]
    fmt.Println(b)
}

使用语法 a[start:end] 创建一个从 a 数组索引 start 开始到 end - 1 结束的切片。因此,在上述程序的第 9 行中, a[1:4] 为从索引 1 到 3 创建了 a 数组的一个切片表示。因此, 切片 b的值为 [77 78 79]

让我们看看另一种创建切片的方法。

package main
 
import (  
    "fmt"
)
 
func main() {  
    c := []int{6, 7, 8} 
    fmt.Println(c)
}

在上面程序的第 9 行,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) 
}

在上面程序的第 9 行,我们根据数组索引 2,3,4 创建一个切片 dslice。for 循环将这些索引中的值逐个递增。当重新使用 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() {  
    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)) 
}

在上面的程序中,从 fruitarray 的索引 1 和 2 创建fruitslice 。 因此,fruitlice 的长度为 2

fruitarray 的长度是 7。fruiteslice 是从 fruitarray 的索引 1 开始创建的。因此, fruitslice的容量是从 fruitarray 索引为 1开始,也就是说从 orange 开始,该值为 6。因此, fruitslice的容量为 6。该程序输出length of slice 2 capacity 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)] 
    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)) 
    cars = append(cars, "Toyota")
    fmt.Println("cars:", cars, "has new length", len(cars), "and capacity", cap(cars)) 
}

在上面程序中,cars的容量最初是 3。在第 10 行,我们给 cars 添加了一个新的元素,并把 append(cars, "Toyota") 返回的切片赋值给 cars。现在 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 
切片的函数传递

可以认为切片在内部由结构类型表示。看起来是这样的:

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)                             
    fmt.Println("slice after function call", nos) 
}

上面程序的行号 17 中,调用函数将切片中的每个元素递减 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++"},
            {"Java"},
            {"Go", "Rust"},
            }
    for _, v1 := range pls {
        for _, v2 := range v1 {
            fmt.Printf("%s ", v2)
        }
        fmt.Printf("\n")
    }
}

程序的输出如下:

C C++  
Java 
Go Rust 

注意:Go 使用 2x 算法来增加数组长度

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

极简网络科技

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值