Go - Slices
- Slices are built on top of arrays that hold their elements
- Slices have dynamic sizes, elements can be added or removed as needed
- A slice literal is declared just like an array literal but without the length
Declaration
// A slice is declared like an array literal but without the length.var numbers []int // Slice of integersvar names []string // Slice of stringsvar multi [][]int // Slice of slices of integers
Initialization
A slice is associated with an undelying array that hold its elements, the slice shares storage with its array and any other slices of the same array.
A slice can be initialized using the slice literal syntax that describes the entire underlying array.
// Initialize using the slice literal syntax.// A new hidden array is allocated that holds the slice elements.numbers := []int{1, 2, 3, 4}letters := []string{"a", "b", "c"}// Initialize a multidimentional slice (slice of slices).multi := [][]int{ []int{1, 2, 3}, []int{4, 5, 6},}// The type can be omitted for the elements.multi = [][]int{ {1, 2, 3}, {4, 5, 6},}
The slice literal syntax is a shorthand for a slice operation applied to and array.
A slice expression creates a slice from an array:
- Syntax:
a[low:high]
(a
can be an array or a pointer to an array) - The
low
andhigh
indices select which elements appear in the result - Any of the indices may be omitted, a missing low index defaults to zero, a missing high index defaults to the length of the sliced array
// A slice literal.numbers := []int{1, 2, 3, 4}// A shorthand for creating a slice from an array.array := [4]int{1, 2, 3, 4} // Array that holds the elements// Both indices are omitted to refer to the whole array.numbers = array[:] // Slice that refers to the array// Same asnumbers = array[0:len(array)]numbers = array[:len(array)] // The low index defaults to 0numbers = array[0:] // The high index defaults to the length
We can also create a new slice that refers to a subrange of an existing slice.
slice := []int{1, 2, 3, 4} // [1 2 3 4]// Create a new slice with elements from index 1 (inclusive) to 3 (exclusive).subSlice := slice[1:3] // [2 3]
Slices can be created using the make()
function where the type, length and optional capacity may be specified make([]T, length, capacity)
.
// Initialize a slice using the `make` function.// The elements will be initialized to the zero value of their type.// The second argument is the length of the slice.slice := make([]int, 4) // []int{0, 0, 0, 0}, len=4, cap=4// Equivalent to allocating an array and slicing it.slice = new([4]int)[:] // []int{0, 0, 0, 0}, len=4, cap=4// The third and optional argument is the capacity of the slice.// It defines the size of the underlying array.slice = make([]int, 4, 10) // []int{0, 0, 0, 0}, len=4, cap=10// Equivalent toslice = new([10]int)[0:4] // []int{0, 0, 0, 0}, len=4, cap=10// Empty (non-nil) slice.empty := make([]int, 0) // []int{}, len=0, cap=0
For slices of slices, the inner slices must be initialized individually.
slice := make([][]int, 2) // [][]int{[]int(nil), []int(nil)}slice[0] // nilslice[1] // nilslice[0] = make([]int, 4) // []int{0, 0, 0, 0}slice[1] = make([]int, 4) // []int{0, 0, 0, 0}
Length and Capacity
The len()
function can be used to get the length of a slice and the cap()
function to get the capacity.
// A slice has a length and a capacity.// 0 <= len(slice) <= cap(slice)slice := []int{1, 2, 3, 4, 5, 6}// The length of a slice is the number of elements it contains.length := len(slice) // 6// The underlying array of a slice may extend past the end of the slice.// The capacity is a measure of that extent, it is the sum of the length// of the slice and the length of the array beyond the slice.capacity := cap(slice) // 6s1 := slice[:0]len(s1) // 0cap(s1) // 6// A slice's length can be extended by re-slicing it.s1 = s1[:4]len(s1) // 4cap(s1) // 6s2 := s[:3]len(s2) // 3cap(s2) // 6s3 := slice[2:]len(s3) // 2cap(s3) // 4 (Starting from the element at index 2)
Zero Value
The value of an uninitialized slice is nil
and it has no underlying array.
var slice []int // nil// The length and capacity of a nil slice are 0.len(slice) // 0cap(slice) // 0// Indexing a nil slice will cause a panic.slice[0] // panic: runtime error: index out of range
Slice Elements
The slice elements can be accessed using their index in square brackets []
:
first := slice[0] // Access the 1st elementlast := slice[len(slice)-1] // Access the last elementn := numbers[0][1] // Access the 2nd element of the 1st sliceslice[1] = 10 // Set the value of the 2nd elementnumbers[1][0] = 7 // Set the value of the 1st element of the 2nd slice// A panic will occur at runtime if the index is out of range.slice[100] // panic: runtime error: index out of range// Pointer to a slice.p := &slice// Explicit pointer dereferencing is required not like arrays.(*p)[0] = 10p[0] = 10 // invalid operation: cannot index p
Changing the elements of a slice modifies the underlying array, as they both share the same memory storage.
array := [4]int{1, 2, 3, 4} // Array that holds the elementsslice := array[:] // Slice that refers to the whole array// Changing the elements of a slice modifies the corresponding// elements of its underlying array as the slice and the array// share the same memory storage.slice[0] = 11// The same goes for modifying the array elements directly.array[3] = 44fmt.Println(array) // [11 2 3 44]fmt.Println(slice) // [11 2 3 44]
When a slice is passed to a function as an argument, a reference to the undelying array is passed and not a copy.
slice := []int{1, 2, 3} // [1 2 3]// Changes made to the slice inside the function are made on// the original array and will be visible outside of the function.func setFirstElement(s []int, n int) { s[0] = n}setFirstElement(slice, 10)fmt.Println(slice) // [10 2 3]
Append Elements
The built-in append()
function is used to append one or more values to a slice and returns the resulting slice.
var s []int // nil, len=0, cap=0// It works on nil slices.s = append(s, 1) // []int{1}, len=1, cap=1// If the capacity of the slice is not large enough to fit the additional// values, a new larger array will be allocated to fit all the values// otherwise append re-uses the underlying array.// That's why we assign the resulting slice to the same variable.s = append(s, 2) // []int{1, 2}, len=2, cap=2// Append multiple values at the same time.s = append(s, 3, 4, 5) // []int{1, 2, 3, 4, 5}, len=5, cap=6
Copy a Slice
The copy()
function copies elements from a source slice to a destination slice and returns the number of copied elements.
dest := make([]int, 5) // []int{0, 0, 0, 0, 0}src := []int{1, 2, 3, 4, 5}// Copy the elements from `src` to `dest`.// Both arguments must be slices of the same element type.n := copy(dest, src) // Returns the number of copied elementfmt.Println(n) // 5fmt.Println(dest) // [1 2 3 4 5]// `copy` can handle source and destination slices that share the same// underlying array, handling overlapping slices correctly.n = copy(dest, dest[3:]) // copy([1 2 3 4 5], [4 5])// The number of elements copied is the minimum of the lengths of the 2 slices.// In this case the source slice is of length 2, so 2 elements are copied.fmt.Println(n) // 2fmt.Println(dest) // [4 5 3 4 5]// A special case exists when copying to a `[]byte` destination,// the source argument can also be of type string.bytes := make([]byte, 2) // []byte{0x0, 0x0}n = copy(bytes, "Go")fmt.Println(n) // 2fmt.Println(string(bytes)) // Go
Iteration
We can iterate over a slice using the for range
loop.
s := []string{"a", "b", "c"}// Iterate over a slice.for i, v := range s { // i: Index of the element. // v: A copy of element at this index. fmt.Println(i, v)}// If we only need the first item (index) we can omit the second.for i := range s { fmt.Println(i)}// If we only need the value we can use the blank identifier `_`// to discard the index.for _, v := range s { fmt.Println(v)}