【Golang】静态数组(Array)和切片(slices)

Posted by 西维蜀黍 on 2020-03-15, Last Modified on 2021-09-21

数组(Array)

Define Array

An array type definition specifies a length and an element type,语法格式如下:

var variable_name [SIZE]variable_type

The type [n]T is an array of n values of type T.

The expression

var a [10]int

declares a variable a as an array of ten integers.

Arrays can be indexed in the usual way, so the expression s[n] accesses the nth element, starting from zero.

var a [4]int
a[0] = 1
i := a[0]
// i == 1

Arrays do not need to be initialized explicitly; the zero value of an array is a ready-to-use array whose elements are themselves zeroed:

// a[2] == 0, the zero value of the int type

The in-memory representation of [4]int is just four integer values laid out sequentially:

Array is NOT Pointer

Go’s arrays are values. An array variable denotes the entire array; it is not a pointer to the first array element (as would be the case in C).

a := [2]string{"1", "2"}
b := a
b[0] = "0"
fmt.Println(a) // [1 2]

This means that when you assign or pass around an array value you will make a copy of its contents. (To avoid the copy you could pass a pointer to the array, but then that’s a pointer to an array, not an array.) One way to think about arrays is as a sort of struct but with indexed rather than named fields: a fixed-size composite value.

An array literal can be specified like so:

b := [2]string{"Penn", "Teller"}

Or, you can have the compiler count the array elements for you:

b := [...]string{"Penn", "Teller"}

In both cases, the type of b is [2]string.

Array Size

An array’s length is part of its type, so arrays cannot be resized.

  • An array’s size is fixed; its length is part of its type ([4]int and [5]int are distinct, incompatible types)
package main

import "fmt"

func main() {
	var a [2]string
	a[0] = "Hello"
	a[1] = "World"
	fmt.Println(a[0], a[1])
	fmt.Println(a)

	primes := [6]int{2, 3, 5, 7, 11, 13} // or primes := []int{2, 3, 5, 7, 11, 13}
	fmt.Println(primes)
}

初始化数组中 {} 中的元素个数不能大于 [] 中的数字。

Size Difference between Array and Slice

注意,数组和切片的区别在于:声明数组时,需要显式指明数据的长度,而切片不需要。

如果忽略 [] 中的数字不设置数组大小,Go 语言会根据元素的个数来设置数组的大小:

// 声明切片
var balance = []float32{1000.0, 2.0, 3.4, 7.0, 50.0}
// 声明(静态)数组
var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

而数组与切片之间不能相互转换:

package main

func main() {
	var a [5]int = [5]int{1,2,3,4,5}
	test(a)
}

func test(b []int) {}

error:

# command-line-arguments
./main.go:5:6: cannot use a (type [5]int) as type []int in argument to test

Array Iteration

// Show index only, seldom used
list := []int{2, 3, 5, 7, 11, 13}
for index := range list {
	fmt.Println("index:", index)
}

// Show value only
for _, value := range list {
	fmt.Println("value:", value)
}

// show index and value
for index, value := range list {
	fmt.Println("index:", index)
	fmt.Println("value:", value)
	fmt.Println()
}

Array Copy

func main() {
	var a [2]int = [2]int{1, 2}
	b := a
	b[0] = 2
	fmt.Printf("a: %v, b: %v\n", a, b)
	fmt.Printf("a: %p, b: %p\n", &a, &b)
}

//a: [1 2], b: [2 2]
//a: 0xc00009e010, b: 0xc00009e020

There are major differences between the ways arrays work in Go and C. In Go,

  • Arrays are values. Assigning one array to another copies all the elements.
  • In particular, if you pass an array to a function, it will receive a copy of the array, not a pointer to it.
  • The size of an array is part of its type. The types [10]int and [20]int are distinct.

The value property can be useful but also expensive; if you want C-like behavior and efficiency, you can pass a pointer to the array.

func Sum(a *[3]float64) (sum float64) {
    for _, v := range *a {
        sum += v
    }
    return
}

array := [...]float64{7.0, 8.5, 9.1}
x := Sum(&array)  // Note the explicit address-of operator

切片(Slices)

An array has a fixed size. A slice, on the other hand, is a dynamically-sized, flexible view into the elements of an array. In practice, slices are much more common than arrays.

A slice is a descriptor for a contiguous segment of an underlying array and provides access to a numbered sequence of elements from that array.

如何理解上面这句话:

array := [3]string{"11", "22", "33"}
s := array[:] // a slice referencing the storage of array
s[0] = "00"
fmt.Println(array) // [00 22 33]

这是因为,当将一个 array 转换成一个 slice的时候,slice底层的 array 指针其实会指向到这个 array。


由于切片(Slices) 只是底层数组的连续元素的一个描述符,因此,切片中的元素对应的内存地址和这个元素对应在底层数组的元素的内存地址是完全相同的。

这也意味着:

The type []T is a slice with elements of type T.

A slice is formed by specifying two indices, a low and high bound, separated by a colon:

a[low : high]

This selects a half-open range which includes the first element, but excludes the last one.

The following expression creates a slice which includes elements 1 through 3 of a:

a[1:4]
package main

import "fmt"

func main() {
	primes := [6]int{2, 3, 5, 7, 11, 13}

	var s []int = primes[1:4]
	fmt.Println(s)
}

Define Slices

A slice literal is declared just like an array literal, except you leave out the element count:

letters := []string{"a", "b", "c", "d"}

A slice can be created with the built-in function called make, which has the signature,

func make([]T, len, cap) []T

where T stands for the element type of the slice to be created. The make function takes a type, a length, and an optional capacity. When called, make allocates an array and returns a slice that refers to that array.

var s []byte
s = make([]byte, 5, 5)
// s == []byte{0, 0, 0, 0, 0}

When the capacity argument is omitted, it defaults to the specified length. Here’s a more succinct version of the same code:

s := make([]byte, 5)

The length and capacity of a slice can be inspected using the built-in len and cap functions.

len(s) == 5
cap(s) == 5

Capacity and Length

make([]T, length, capacity)
  • length 是这个切片对应的静态数组实际拥有元素的数量
  • capacity 是这个切片目前可以容纳元素的数量,或者说体现了该切片实际在内存中占用的空间长度(当其对应的静态数组实际拥有元素的数量超过了这个数字,golang内内部会自动对该切片实际在内存中占用的空间进行自动增加,这对developer来说是完全透明的)

切片初始化

Case 1 - 提供具体数值初始化切片

s := []int{1, 2, 3} 

直接初始化切片,[] 表示是切片类型,{1,2,3} 表示该切片包含三个元素,分别是1,2,3,其cap=len=3

s := arr[:] 

Case 2 - 通过 make() 初始化切片

s := make([]int, len, cap) 

通过内置函数 make() 初始化切片 s,[]int 标识为其元素类型为int的切片。

从底层操作来说,上面代码在内存空间中开辟了一个长度为 len 的 []int 数组。

因此,A slice created with make always allocates a new, hidden array to which the returned slice value refers. That is, executing

make([]T, length, capacity)

produces the same slice as allocating an array and slicing it, so these two expressions are equivalent:

make([]int, 50, 100)
new([100]int)[0:50]

Case 3 - 通过一个静态数组(的一部分)创建出一个切片

如果通过这种将“一个静态数组的一部分”转换出一个切片,这个切片其实是原数组的一个描述符,即这个切片的header中的指针,仍然指向原数组,这意味着:

  • 改变原数组中元素的值,也会影响到切片
package main

import (
	"fmt"
)

func main() {
	a := [2]string{"1", "2"}
	b := a[0:1]
	a[0] = "0"
	fmt.Println(b)
}

// output:
[0]

Case 4 - 通过一个切片创建另一个切片

通过一个切片初始化另一个切片,他们 header中的数组指针,都指向同一个数组。

因此,改变切片中元素的值,也会影响到原数组:

package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

func main() {
	var a []int = []int{1, 2, 3, 4, 5, 6}
	b := a[0:3]
	a[2] = 100
	fmt.Printf("%v\n", a)
	fmt.Printf("%v\n", b)

	a1 := (*reflect.SliceHeader)(unsafe.Pointer(&a))
	a2 := (*reflect.SliceHeader)(unsafe.Pointer(&b))
	fmt.Println(*a1)
	fmt.Println(*a2)
}

// output
[1 2 100 4 5 6]
[1 2 100]
{824633835760 6 6}
{824633835760 3 6}

可以看到,通过一个切片创建另一个切片时:

  1. 会再创建一个 SliceHeader,意味着这时候有两个 SliceHeader (分别对应两个Slice struct)
  2. 但是这两个 slice struct的 Data filed 都指向通过一个 array

因此,改变从切片A转换出来的切片B的值,也会影响从同一个切片转换出来的另一个切片C的值:

package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

func main() {
	var a []int = []int{1, 2, 3, 4, 5, 6}
	b := a[0:3]
	c := a[0:3]

	b[2] = 200

	fmt.Printf("%v\n", b)
	fmt.Printf("%v\n", c)
	a1 := (*reflect.SliceHeader)(unsafe.Pointer(&a))
	a2 := (*reflect.SliceHeader)(unsafe.Pointer(&b))
	a3 := (*reflect.SliceHeader)(unsafe.Pointer(&c))

	fmt.Println(*a1)
	fmt.Println(*a2)
	fmt.Println(*a3)
}

// output
[1 2 200]
[1 2 200]
{824633835760 6 6}
{824633835760 3 6}
{824633835760 3 6}

Slice Slices

As we slice s, observe the changes in the slice data structure and their relation to the underlying array:

s := make([]byte, 5)
s = s[2:4]

Slicing does not copy the slice’s data. It creates a new slice that points to the original array. This makes slice operations as efficient as manipulating array indices.

证明:

a := make([]byte, 5)
b := a[2:4]
var a1 = *(*[3]int)(unsafe.Pointer(&a))
var a2 = *(*[3]int)(unsafe.Pointer(&b))
fmt.Println(a1)
fmt.Println(a2)

// output
[824634404571 5 5]
[824634404573 2 3]

Therefore, modifying the elements (not the slice itself) of a re-slice modifies the elements of the original slice(这也再次说明了 slice 只是底层array的descriptor):

d := []byte{'r', 'o', 'a', 'd'}
e := d[2:]
// e == []byte{'a', 'd'}
e[1] = 'm'
// e == []byte{'a', 'm'}
// d == []byte{'r', 'o', 'a', 'm'}

slices 通过函数传递

It’s important to understand that even though a slice contains a pointer, it is itself a value. Under the covers, it is a struct value holding a pointer and a length. It is not a pointer to a struct.

When we called IndexRune in the previous example, it was passed a copy of the slice header. That behavior has important ramifications.

Example

package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

func main() {
	slice1 := make([]byte, 10)

	fmt.Println("before", slice1)
	a1 := (*reflect.SliceHeader)(unsafe.Pointer(&slice1))
	fmt.Printf("slice1's struct's address: %p\n", &a1)

	AddOneToEachElement(slice1)

	fmt.Println("after", slice1)
}

func AddOneToEachElement(slice2 []byte) {
	a1 := (*reflect.SliceHeader)(unsafe.Pointer(&slice2))
	fmt.Printf("slice2's struct's address: %p\n", &a1)

	for i := range slice2 {
		slice2[i]++
	}
}

// output:
before [0 0 0 0 0 0 0 0 0 0]
slice1's struct's address: 0xc00012e020
slice2's struct's address: 0xc00012e028
after [1 1 1 1 1 1 1 1 1 1]

len()cap() 函数

A slice has both a length and a capacity.

  • The length of a slice is the number of elements it contains.
  • The capacity of a slice is the number of elements in the underlying array, counting from the first element in the slice.

The length and capacity of a slice s can be obtained using the expressions len(s) and cap(s).

You can extend a slice’s length by re-slicing it, provided it has sufficient capacity. Try changing one of the slice operations in the example program to extend it beyond its capacity and see what happens.

实例

package main

import "fmt"

func main() {
	s := []int{2, 3, 5, 7, 11, 13}
	printSlice(s)

	// Slice the slice to give it zero length.
	s = s[:0]
	printSlice(s)

	// Extend its length.
	s = s[:4]
	printSlice(s)

	// Drop its first two values.
	s = s[2:]
	printSlice(s)
}

func printSlice(s []int) {
	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

//output
len=6 cap=6 [2 3 5 7 11 13]
len=0 cap=6 []
len=4 cap=6 [2 3 5 7]
len=2 cap=4 [5 7]

看起来,上面的规律是:

  • 如果指定了起始的 index,那么返回的 slice 的 cap 为从该起始 index 到末尾的长度
  • 如果没有指定起始 index,那么返回的 slice 的 cap 与原数组长度相同

对为 nil 的 array 取 len

	var a []int32 = make([]int32, 5)
	println(a == nil) //false

	var b []int32
	println(b == nil) //true
	var c []int32
	println(len(c)) //0

	// compiling error
	//var d *[]int32
	//println(len(d))

Slices元素追加

package main

func main() {
   statisArr := [8]int{0, 1, 2, 3, 4, 5, 6, 7}

   arr := make([]int, 0)
   for _, ele := range statisArr {
      arr = append(arr, ele)
   }
}

The length of a slice may be changed as long as it still fits within the limits of the underlying array; just assign it to a slice of itself. The capacity of a slice, accessible by the built-in function cap, reports the maximum length the slice may assume. Here is a function to append data to a slice. If the data exceeds the capacity, the slice is reallocated. The resulting slice is returned. The function uses the fact that len and cap are legal when applied to the nil slice, and return 0.

func Append(slice, data []byte) []byte {
    l := len(slice)
    if l + len(data) > cap(slice) {  // reallocate
        // Allocate double what's needed, for future growth.
        newSlice := make([]byte, (l+len(data))*2)
        // The copy function is predeclared and works for any slice type.
        copy(newSlice, slice)
        slice = newSlice
    }
    slice = slice[0:l+len(data)]
    copy(slice[l:], data)
    return slice
}

We must return the slice afterwards because, although Append can modify the elements of slice, the slice itself (the run-time data structure holding the pointer, length, and capacity) is passed by value.

The idea of appending to a slice is so useful it’s captured by the append built-in function. To understand that function’s design, though, we need a little more information, so we’ll return to it later.

Deeper Understanding

Look at the following function that extends its argument slice of ints by one element:

package main

import (
	"fmt"
)

func main() {
	var iBuffer [10]int
	slice := iBuffer[0:0]
	for i := 0; i < 20; i++ {
		slice = Extend(slice, i)
		fmt.Println(slice)
	}
}
func Extend(slice []int, element int) []int {
	n := len(slice)
	slice = slice[0 : n+1]
	slice[n] = element
	return slice
}

output:

[0]
[0 1]
[0 1 2]
[0 1 2 3]
[0 1 2 3 4]
[0 1 2 3 4 5]
[0 1 2 3 4 5 6]
[0 1 2 3 4 5 6 7]
[0 1 2 3 4 5 6 7 8]
[0 1 2 3 4 5 6 7 8 9]
panic: runtime error: slice bounds out of range [:11] with capacity 10

goroutine 1 [running]:
main.Extend(...)
        /Users/shiwei/SW/GoPlayground/sw.go:17
main.main()
        /Users/shiwei/SW/GoPlayground/sw.go:11 +0x100

It doesn’t grow the slice.

Nil Slices

一个切片默认为 nil,长度为 0,实例如下:

package main

import "fmt"

func main() {
  var numbers []int
  printSlice(numbers)
  if(numbers == nil){
   fmt.Printf("切片是空的")
  }
}

func printSlice(x []int){
  fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}

//output
len=0 cap=0 slice=[]
切片是空的

多维 Slices(Multi-dimensional Slices)

Like arrays, slices are always one-dimensional but may be composed to construct higher-dimensional objects. With arrays of arrays, the inner arrays are, by construction, always the same length; however with slices of slices (or arrays of slices), the inner lengths may vary dynamically. Moreover, the inner slices must be initialized individually.

package main

import (
	"fmt"
)

func main() {
	a := make([][]int, 0)

	a1 := []int{1, 2, 3, 4}
	a = append(a, a1)

	a2 := []int{100}
	a = append(a, a2)

	fmt.Printf("%v\n", a)
}

Reference