Golang:切片

作者: 与蟒唯舞 | 来源:发表于2017-12-20 17:00 被阅读11次

    切片(slice)是建立在数组之上的更方便,更灵活,更强大的数据结构。切片并不存储任何元素而只是对现有数组的引用。

    切片的长度是指切片中元素的个数。切片的容量是指从切片的起始元素开始到其底层数组中的最后一个元素的个数。

    问题:切片是建立在数组之上的,而数组本身不能改变长度,那么切片是如何动态改变长度的呢?实际发生的情况是,当新元素通过调用 append 函数追加到切片末尾时,如果超出了容量,append 内部会创建一个新的数组。并将原有数组的元素拷贝给这个新的数组,最后返回建立在这个新数组上的切片。这个新切片的容量是旧切片容量的二倍。下面的程序使事情变得容易理解:

    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。现在 cars 的容量翻倍,变为 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)
        }
    }
    

    可以使用 ... 操作符将一个切片追加到另一个切片末尾:

    package main
    
    import (  
        "fmt"
    )
    
    func main() {  
        veggies := []string{"potatoes", "tomatoes", "brinjal"}
        fruits := []string{"oranges", "apples"}
        food := append(veggies, fruits...)
        fmt.Println("food:", food)
    }
    

    上面的程序中,将 fruits 追加到 veggies 并赋值给 food。... 操作符用来展开切片。

    可以认为切片在内部表示为如下的结构体:

    type slice struct {  
        Length        int
        Capacity      int
        ZerothElement *byte
    }
    

    可以看到切片包含长度、容量、以及一个指向首元素的指针。

    切片保留对底层数组的引用。只要切片存在于内存中,数组就不能被垃圾回收。这在内存管理方面是值得关注的。假设我们有一个非常大的数组,而我们只需要处理它的一小部分,为此我们创建这个数组的一个切片,并处理这个切片。这里要注意的是,数组仍然存在于内存中,因为切片正在引用它。

    解决该问题的一个方法是使用 copy 函数 func copy(dst, src []T) int 来创建该切片的一个拷贝。这样我们就可以使用这个新的切片,原来的数组可以被垃圾回收。

    package main
    
    import (  
        "fmt"
    )
    
    func countries() []string {  
        countries := [...]string{"USA", "Singapore", "Germany", "India", "Australia"}
        neededCountries := countries[:len(countries)-2]
        countriesCpy := make([]string, len(neededCountries))
        copy(countriesCpy, neededCountries) //copies neededCountries to countriesCpy
        return countriesCpy
    }
    func main() {  
        countriesNeeded := countries()
        fmt.Println(countriesNeeded)
    }
    

    在上面程序中,第 9 行 neededCountries := countries[:len(countries)-2] 创建了一个底层数组为 countries 并排除最后两个元素的切片。第 11 行将 neededCountries 拷贝到 countriesCpy 并在下一行返回 countriesCpy。现在数组 countries 可以被垃圾回收,因为 neededCountries 不再引用。

    相关文章

      网友评论

        本文标题:Golang:切片

        本文链接:https://www.haomeiwen.com/subject/yhvgwxtx.html