Go Programming: Slices
Slices are a fundamental data structure in Go, providing a dynamic, flexible way to work with sequences of elements. They are built on top of arrays but offer more convenience and power.
What are Slices?
- Dynamic Size: Unlike arrays, which have a fixed size defined at compile time, slices can grow or shrink dynamically at runtime.
- Reference Type: Slices are reference types. This means that when you assign a slice to a new variable, you're copying the slice header (pointer, length, and capacity), not the underlying array data. Changes made through one slice can affect other slices that share the same underlying array.
- Underlying Array: A slice is essentially a descriptor that points to a contiguous segment of an underlying array. This array can be created specifically for the slice, or it can be a portion of an existing array.
- Length and Capacity:
- Length: The number of elements currently stored in the slice.
len(slice) - Capacity: The maximum number of elements the slice can hold without reallocating the underlying array.
cap(slice)
- Length: The number of elements currently stored in the slice.
Creating Slices
There are several ways to create slices:
Slice Literals:
// Create a slice of integers mySlice := []int{1, 2, 3, 4, 5} // Create an empty slice of strings emptySlice := []string{}Using
make():// Create a slice of integers with length 3 and capacity 5 mySlice := make([]int, 3, 5) // [0 0 0] (length 3, capacity 5) // Create a slice of strings with length and capacity 4 stringSlice := make([]string, 4) // ["", "", "", ""] (length 4, capacity 4)Slicing an Array:
myArray := [5]int{10, 20, 30, 40, 50} mySlice := myArray[1:4] // [20 30 40] (length 3, capacity 4)myArray[start:end]creates a slice referencing a portion of the array.startis inclusive,endis exclusive.- If
startis omitted, it defaults to 0. - If
endis omitted, it defaults to the length of the array.
Slice Operations
Accessing Elements: Use indexing like arrays.
mySlice := []int{1, 2, 3} firstElement := mySlice[0] // firstElement is 1Modifying Elements:
mySlice := []int{1, 2, 3} mySlice[1] = 10 // mySlice is now [1 10 3]append(): Adds elements to the end of a slice. If the capacity is reached, a new underlying array is allocated, and the elements are copied.mySlice := []int{1, 2, 3} mySlice = append(mySlice, 4, 5) // mySlice is now [1 2 3 4 5]copy(): Copies elements from one slice to another.src := []int{1, 2, 3} dst := make([]int, len(src)) copy(dst, src) // dst is now [1 2 3]len()andcap(): Return the length and capacity of a slice, respectively.mySlice := []int{1, 2, 3, 4, 5} length := len(mySlice) // length is 5 capacity := cap(mySlice) // capacity is 5
Slice Examples
package main
import "fmt"
func main() {
// Create a slice
numbers := []int{1, 2, 3}
// Append elements
numbers = append(numbers, 4, 5)
fmt.Println("After append:", numbers) // Output: After append: [1 2 3 4 5]
// Slice a portion of the slice
subSlice := numbers[1:4]
fmt.Println("Subslice:", subSlice) // Output: Subslice: [2 3 4]
// Modify the subslice (affects the original slice)
subSlice[0] = 100
fmt.Println("Modified subslice:", subSlice) // Output: Modified subslice: [100 3 4]
fmt.Println("Original slice:", numbers) // Output: Original slice: [1 100 3 4 5]
// Create a slice with make
names := make([]string, 0, 3) // Length 0, Capacity 3
names = append(names, "Alice")
names = append(names, "Bob")
names = append(names, "Charlie")
fmt.Println("Names:", names) // Output: Names: [Alice Bob Charlie]
fmt.Println("Length:", len(names)) // Output: Length: 3
fmt.Println("Capacity:", cap(names)) // Output: Capacity: 3
// Appending beyond capacity causes reallocation
names = append(names, "David")
fmt.Println("After appending David:", names) // Output: After appending David: [Alice Bob Charlie David]
fmt.Println("New Capacity:", cap(names)) // Output: New Capacity: 6 (typically doubles)
}
Important Considerations
Zero Value: The zero value of a slice is
nil. Anilslice has a length and capacity of 0 and no underlying array.Slice Growth: When
append()exceeds the capacity, a new underlying array is allocated, and the existing elements are copied. This can be expensive, especially for large slices. Consider pre-allocating capacity withmake()if you know the approximate size of the slice beforehand.Sharing Underlying Arrays: Be mindful that multiple slices can share the same underlying array. Modifying one slice can affect others. If you need independent copies, use
copy()to create a new slice with its own underlying array.rangeLoop: Slices can be easily iterated over using therangekeyword.mySlice := []string{"apple", "banana", "cherry"} for index, value := range mySlice { fmt.Printf("Index: %d, Value: %s\n", index, value) }
Slices are a powerful and versatile data structure in Go. Understanding their behavior, especially regarding length, capacity, and underlying arrays, is crucial for writing efficient and correct Go programs.