本文为译文 主要参考以下两篇文章。
数组是固定长度的、拥有相同类型的一组序列。在GO中,可以使用以下方式创建数组。
[N]Type
[N]Type{value1, value2, ..., valueN}
[...]Type{value1, value2, ..., valueN}
不像C/C++(数组实际是指针)和java(数组是对象的引用),在GO中,数组是值类型。这种特性会带来以下几种影响。
- 将一个数组赋值到另一个数组,会对数组中所有元素进行复制。
- 加入你传递一个数组给一个函数,这个函数接受的是这个数组的拷贝。
你可能会说,这种操作代价太高了。尤其当你使用的数组里面有很多元素的时候。
相对的,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)
}