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 integers
var names []string // Slice of strings
var 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 as
numbers = array[0:len(array)]
numbers = array[:len(array)] // The low index defaults to 0
numbers = 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 to
slice = 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] // nil
slice[1] // nil
slice[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) // 6
s1 := slice[:0]
len(s1) // 0
cap(s1) // 6
// A slice's length can be extended by re-slicing it.
s1 = s1[:4]
len(s1) // 4
cap(s1) // 6
s2 := s[:3]
len(s2) // 3
cap(s2) // 6
s3 := slice[2:]
len(s3) // 2
cap(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) // 0
cap(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 element
last := slice[len(slice)-1] // Access the last element
n := numbers[0][1] // Access the 2nd element of the 1st slice
slice[1] = 10 // Set the value of the 2nd element
numbers[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] = 10
p[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 elements
slice := 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] = 44
fmt.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 element
fmt.Println(n) // 5
fmt.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) // 2
fmt.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) // 2
fmt.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)
}