11 array & slice

86 阅读6分钟

本文为译文 主要参考以下两篇文章。

Arrays vs Slices

Arrays and Slices

数组是固定长度的、拥有相同类型的一组序列。在GO中,可以使用以下方式创建数组。

[N]Type
[N]Type{value1, value2, ..., valueN}
[...]Type{value1, value2, ..., valueN}

不像C/C++(数组实际是指针)和java(数组是对象的引用),在GO中,数组是值类型。这种特性会带来以下几种影响。

  1. 将一个数组赋值到另一个数组,会对数组中所有元素进行复制。
  2. 加入你传递一个数组给一个函数,这个函数接受的是这个数组的拷贝。

你可能会说,这种操作代价太高了。尤其当你使用的数组里面有很多元素的时候。

相对的,slice就非常的灵活了。slice可以使用内置的append函数调整大小。同时slice是引用类型。同时,在go所有的标准库函数中,使用的都是slice。

创建slice的方式如下

make([]Type, length, capacity)
make([]Type, length)
[]Type{}
[]Type{value1, value2, ..., valueN}

综上:slice更加灵活,干净。所以建议更多的使用slice。

一些有助于帮助理解的例子

package main

import "fmt"

func main() {
	// Array
	array := [3]int{1, 2, 3}
	arrayCopy := array
	fmt.Printf("&array[0]: %p\n", &array[0])         //&array[0]: 0xc000016078
	fmt.Printf("&arrayCopy[0]: %p\n", &arrayCopy[0]) //&arrayCopy[0]:0xc000016090

	fmt.Println("---------------------------")

	// Slice
	slice := []int{1, 2, 3}
	sliceRef := slice
	fmt.Printf("&slice[0]: %p\n", &slice[0])       //&slice[0]: 0xc0000160a8
	fmt.Printf("&sliceRef[0]: %p\n", &sliceRef[0]) //&sliceRef[0]: 0xc0000160a8
	slice = append(slice, 4, 5)
	fmt.Printf("&slice[0]: %p\n", &slice[0])       //&slice[0]: 0xc00001c0f0
	fmt.Printf("&sliceRef[0]: %p\n", &sliceRef[0]) //&sliceRef[0]: 0xc0000160a8

	slice[2] = 999
	fmt.Printf("slice: %v\n", slice)       // [1 2 999 4 5]
	fmt.Printf("sliceRef: %v\n", sliceRef) //[1 2 3]

}

array declaration

语法如[n]T。n代表了元素的数量,T代表了类型。元素的数量也是数组类型的一部分[5]int[25]int是不同的数据类型。因此数组不能被重新设置大小。

数组是值类型(arrays are value types)

go中数组是值类型,不是引用类型。因此在go中无论是是变量赋值,还是函数参数传递,都是数组的拷贝。

数组处理的常用方法

package main

import "fmt"

func main() {  
    a := [...]float64{67.7, 89.8, 21, 78}
    fmt.Println("length of a is",len(a))
    
    for i := 0; i < len(a); i++ { //looping from 0 to the length of the array
        fmt.Printf("%d th element of a is %.2f\n", i, a[i])
    }
    
     for i, v := range a {//range returns both the index and value
        fmt.Printf("%d the element of a is %.2f\n", i, v)
        sum += v
    }

}

slice

slice是在数组之上一种灵活的包装器。slice没有属于自己的任何数据,他们只是已存在数组的引用。

create a slice

//mode 1
a := [5]int{76, 77, 78, 79, 80}
var b []int = a[1:4] //creates a slice from a[1] to a[3]

//mode 2
c := []int{6, 7, 8} //creates and array and returns a slice reference

//mode 3  
// func make([]T, len, cap) []T 
//The make function creates an array and returns a slice reference to it.
//The values are zeroed by default when a slice is created using make. The above program will output [0 0 0 0 0].
i := make([]int, 5, 5)

第二种形式创建的数组**c := []int{6, 7, 8}**首先会创建一个有3个长度的数组。然后返回一个slice存在变量c上。

modifying a slice

slice没有任何他自己的任何数据。他只是底层数组的代表。任何对slice的修改都会反应到底层数组上。

package main

import (  
    "fmt"
)

func main() {  
    darr := [...]int{57, 89, 90, 82, 100, 78, 67, 69, 59}
    dslice := darr[2:5]
    
    //array before [57 89 90 82 100 78 67 69 59]  
    fmt.Println("array before",darr)
    for i := range dslice {
        dslice[i]++
    }
    //array after [57 89 91 83 101 78 67 69 59]  
    fmt.Println("array after",darr) 
}

当好几个slice共享一个array时,每一个的改变将会互相影响。

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[:]
    // [78 79 80]
	fmt.Println("array before change 1", nums1)
	nums1[0] = 100
    //array after modification to slice nums1 [100 79 80]
	fmt.Println("array after modification to slice nums1", nums1)
	nums2[1] = 101
    //array after modification to slice nums1 [100 101 80]
	fmt.Println("array after modification to slice nums1", nums1)
}

一个slice可以被重新根据其容量进行切片。

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))
}

length of slice 2 capacity 6  
After re-slicing length is 6 and capacity is 6  

appending to a slice

The definition of append function is func append(s []T, x ...T) []T

可能会有一个问题困扰你,如果slice是基于数组的,数组的长度是固定的,那么slice怎么会有动态的长度呢?当一些新的元素被append到slice之上的时候,一个新数组会被创建,原先的元素会被复制到新数组上,并且会返回一个新数组的slice引用。新的slice容量是旧的的2倍。

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: [Ferrari Honda Ford] has old length 3 and capacity 3  
cars: [Ferrari Honda Ford Toyota] has new length 4 and capacity 6  

如果新增的元素大于两倍容量,那么就是两倍容量+新增元素的差值即新slice的容量

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", "1", "2", "3", "4")
	fmt.Println("cars:", cars, "has new length", len(cars), "and capacity", cap(cars)) //capacity of cars is doubled to 6
}


cars: [Ferrari Honda Ford] has old length 3 and capacity 3
cars: [Ferrari Honda Ford Toyota 1 2 3 4] has new length 8 and capacity 8

It is also possible to append one slice to another using the ... operator.

//It is also possible to append one slice to another using the ... operator.
veggies := []string{"potatoes","tomatoes","brinjal"}
fruits := []string{"oranges","apples"}
food := append(veggies, fruits...)
fmt.Println("food:",food)

Passing a slice to a function

slice可以认为是一种结构体类型。表示如下。因此即使通过值传递作为参数给函数,指针变量其实指的还是相同的底层数组。因此在函数内部的针对slice的改变同样也会反应在外部。

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}
    //slice before function call [8 7 6]  
    fmt.Println("slice before function call", nos)
    subtactOne(nos)                               //function modifies the slice
    //slice after function call [6 5 4]  
    fmt.Println("slice after function call", nos) //modifications are visible outside
}

内存优化

slice的底层是数组,只要slice在内存中,数组就不会被垃圾回收。当我们在做内存管理的时候,这个可能需要去关心。假设我们有个很大的数组,我们只想操作其中的一块,如果这个slice在,大数组就永远在。

解决这种问题的方式是用copy function func copy(dst, src []T) int这样我们可以使用新的slice,原始的数组就会被收回。

利用copy的方式,我们可以copy一个小的数组出来,让大数组去垃圾回收。

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)
}