Arrays and Slices

Learn about arrays and slices in Go, including how to declare, initialize, and manipulate them. We'll cover the differences between arrays and slices and when to use each.


Arrays and Slices in Go

Introduction

This guide explores arrays and slices, fundamental data structures in Go. We'll learn how to declare, initialize, and manipulate them. We'll also highlight the key differences between arrays and slices and when to use each.

Arrays

An array is a fixed-size sequence of elements of the same type. Arrays are declared with a specific length, and this length cannot be changed after the array is created.

Declaration and Initialization

Here's how to declare and initialize an array:

 package main

import "fmt"

func main() {
    // Declare an array of 5 integers, initialized to zero values.
    var numbers [5]int

    // Declare and initialize an array with specific values.
    var fruits [3]string = [3]string{"apple", "banana", "cherry"}

    // Short-hand declaration and initialization (type inference).
    colors := [3]string{"red", "green", "blue"}

    // Initialize array with ellipsis (...) to let Go determine the size.
    countries := [...]string{"USA", "Canada", "Mexico"}

    fmt.Println("Numbers:", numbers)
    fmt.Println("Fruits:", fruits)
    fmt.Println("Colors:", colors)
    fmt.Println("Countries:", countries)
    fmt.Println("Length of Countries:", len(countries)) // Get the length of the array
} 

Accessing and Modifying Array Elements

You can access and modify elements using their index (starting from 0):

 package main

import "fmt"

func main() {
    numbers := [5]int{10, 20, 30, 40, 50}

    fmt.Println("First element:", numbers[0]) // Access the first element
    numbers[2] = 35                           // Modify the third element
    fmt.Println("Modified array:", numbers)
} 

Arrays are Values

In Go, arrays are values, not references. When you assign one array to another, a complete copy of the array is created. Changes to the copied array will not affect the original array.

 package main

import "fmt"

func main() {
    arr1 := [3]int{1, 2, 3}
    arr2 := arr1 // Copy arr1 to arr2

    arr2[0] = 100 // Modify arr2

    fmt.Println("arr1:", arr1) // arr1 remains unchanged
    fmt.Println("arr2:", arr2)
} 

Slices

A slice is a dynamically-sized, flexible view into the elements of an array. Unlike arrays, slices do not have a fixed size. They are built on top of arrays and provide a more powerful and convenient way to work with sequences of data.

Declaration and Initialization

Here's how to declare and initialize slices:

 package main

import "fmt"

func main() {
    // Declare an uninitialized slice (nil slice).
    var numbers []int

    // Create a slice using make with a specified length.
    primes := make([]int, 5) // Creates a slice of length 5, initialized to zero values

    // Create a slice using make with a specified length and capacity.
    scores := make([]int, 0, 10) // Length 0, capacity 10

    // Slice literal initialization.
    names := []string{"Alice", "Bob", "Charlie"}

    fmt.Println("Numbers:", numbers)
    fmt.Println("Primes:", primes)
    fmt.Println("Scores:", scores)
    fmt.Println("Names:", names)
    fmt.Println("Length of Names:", len(names)) // Get the length of the slice
    fmt.Println("Capacity of Names:", cap(names)) // Get the capacity of the slice
} 

Slicing Arrays

Slices are often created by "slicing" an existing array. This creates a new slice that refers to a portion of the original array. Changes made to the underlying array through the slice will affect the slice, and vice-versa.

 package main

import "fmt"

func main() {
    arr := [5]int{1, 2, 3, 4, 5}

    // Create a slice from index 1 (inclusive) to index 4 (exclusive).
    slice1 := arr[1:4]

    // Create a slice from the beginning to index 3 (exclusive).
    slice2 := arr[:3]

    // Create a slice from index 2 (inclusive) to the end.
    slice3 := arr[2:]

    // Create a slice of the entire array.
    slice4 := arr[:]

    fmt.Println("Array:", arr)
    fmt.Println("Slice1:", slice1)
    fmt.Println("Slice2:", slice2)
    fmt.Println("Slice3:", slice3)
    fmt.Println("Slice4:", slice4)

    arr[1] = 20 // Modify the underlying array
    fmt.Println("Modified Array:", arr)
    fmt.Println("Slice1 after array modification:", slice1) // slice1 is affected
} 

Appending to Slices

The append() function is used to add elements to a slice. If the slice has enough capacity, the new element is added to the existing underlying array. If not, a new, larger underlying array is allocated, and the slice is updated to point to the new array.

 package main

import "fmt"

func main() {
    numbers := []int{1, 2, 3}
    fmt.Println("Original Slice:", numbers, "Length:", len(numbers), "Capacity:", cap(numbers))

    // Append a single element.
    numbers = append(numbers, 4)
    fmt.Println("After appending 4:", numbers, "Length:", len(numbers), "Capacity:", cap(numbers))

    // Append multiple elements.
    numbers = append(numbers, 5, 6, 7)
    fmt.Println("After appending 5, 6, 7:", numbers, "Length:", len(numbers), "Capacity:", cap(numbers))

    // Append another slice.
    moreNumbers := []int{8, 9}
    numbers = append(numbers, moreNumbers...) // Use ... to unpack the slice
    fmt.Println("After appending moreNumbers:", numbers, "Length:", len(numbers), "Capacity:", cap(numbers))
} 

Copying Slices

The copy() function is used to copy elements from one slice to another. It copies the minimum of the lengths of the source and destination slices.

 package main

import "fmt"

func main() {
    source := []int{1, 2, 3, 4, 5}
    destination := make([]int, 3) // Create a destination slice with length 3

    numCopied := copy(destination, source) // Copies up to len(destination) elements

    fmt.Println("Source:", source)
    fmt.Println("Destination:", destination)
    fmt.Println("Number of elements copied:", numCopied)
} 

Length and Capacity

  • Length: The number of elements currently in the slice. Obtained using len(slice).
  • Capacity: The total amount of memory allocated for the underlying array. Obtained using cap(slice). A slice can be extended up to its capacity without reallocating the underlying array.

Differences Between Arrays and Slices

FeatureArraySlice
SizeFixed size at compile time.Dynamically sized.
MutabilitySize cannot be changed.Size can be changed using append().
Underlying DataStores its own data.Is a view into an underlying array.
TypeThe size is part of the array's type (e.g., [3]int is different from [4]int).The slice type doesn't include size (e.g., []int).
Passing to FunctionsArrays are passed by value (a copy is created).Slices are passed by reference (the underlying array is not copied, but the slice header is).

When to Use Arrays and Slices

  • Arrays: Use arrays when you know the exact size of the data structure at compile time and you don't need to resize it. Examples include:
    • Representing a fixed-size grid in a game.
    • Storing a fixed number of configuration values.
  • Slices: Use slices in most cases, as they provide more flexibility and convenience. Examples include:
    • Reading data from a file or network stream.
    • Building a list of results from a database query.
    • Implementing dynamic data structures like stacks and queues.