05. Arrays and Slices
๐ค Delve into the world of Go arrays and slices! This guide unlocks the fundamentals, from creation to advanced operations, empowering you to efficiently manage data structures in Go. ๐
What we will learn in this post?
- ๐ Arrays in Go
- ๐ Slices Fundamentals
- ๐ Creating Slices
- ๐ Slice Operations
- ๐ Slice Internals
- ๐ Multi-dimensional Slices
- ๐ Conclusion!
Go Arrays ๐
Arrays in Go are like fixed-size containers for storing elements of the same type. Think of them as a row of boxes, where each box holds a value.
Declaring and Initializing
You declare an array by specifying its size and the data type it will hold. For example:
1
var numbers [5]int // An array of 5 integers
You can initialize it when declaring:
1
numbers := [5]int{10, 20, 30, 40, 50}
Or, you can let Go infer the size:
1
numbers := [...]int{10, 20, 30, 40, 50} // Size is determined by the number of elements
Zero Values and Accessing Elements
If you donโt initialize the array, elements are assigned their zero value (0 for integers, โโ for strings, etc.).
1
var names [3]string // Defaults to ["", "", ""]
Access elements using their index (starting from 0):
1
2
fmt.Println(numbers[0]) // Output: 10
numbers[1] = 25 // Changing the value at index 1
Key Points to Remember ๐ค
- Fixed Size: Once declared, the size of an array cannot be changed.
- Value Type: When you assign one array to another, a copy is created. Changes to the new array wonโt affect the original.
1
2
3
4
arr1 := [2]int{1, 2}
arr2 := arr1 // arr2 is a copy of arr1
arr2[0] = 5 // Modifying arr2
fmt.Println(arr1, arr2) // Output: [1 2] [5 2]
To learn more:
Slices: Your Dynamic Array Windows in Go ๐ช
// Practical Example: Using arrays for sensor data
1
2
3
4
5
6
7
8
9
package main
import "fmt"
func main() {
var temperatures [7]float64 // Weekly temperature readings
temperatures[0] = 22.5
temperatures[1] = 23.0
// ...
fmt.Println("Monday's temperature:", temperatures[0])
}
Slices in Go are like flexible windows into an underlying array. They let you work with portions of an array without copying the data. Think of them as a dynamic view ๐ฎ.
// Practical Example: Array for error codes
1
2
var errorCodes = [3]int{404, 500, 403}
fmt.Println("First error code:", errorCodes[0])
Slices vs. Arrays: Whatโs the Difference? ๐ง
Hereโs the lowdown: // Practical Example: Array copy for backup
1
2
3
4
original := [2]string{"active", "pending"}
backup := original
backup[1] = "archived"
fmt.Println("Original:", original, "Backup:", backup)
- Arrays:
- Fixed size. You define the size when you declare them, and it canโt change.
- Value type. When you assign one array to another, you get a completely new copy.
- Slices: // Practical Example: Slices for user input
1
2
3
users := []string{"Alice", "Bob"}
users = append(users, "Charlie")
fmt.Println("All users:", users)
Dynamic size. Slices can grow or shrink, making them more adaptable. * Reference type. Slices donโt store the data directly; they hold a pointer to a section of an underlying array. Assigning a slice copies the pointer, not the data itself.
// Practical Example: Slices for log entries
1
2
3
logs := []string{}
logs = append(logs, "Started server", "Received request")
fmt.Println("Logs:", logs)
Why Slices Rule ๐
Slices are generally preferred in Go because of their flexibility. You rarely use arrays directly. Check out this resource for more details: Go Slices: usage and internals.
1
2
3
4
5
6
7
// Array (fixed size)
var myArray [3]int = [3]int{1, 2, 3}
// Slice (dynamic size)
mySlice := []int{1, 2, 3} // Creates a slice backed by an anonymous array
mySlice = append(mySlice, 4) // Adding an element to the slice
println(len(mySlice))
// Practical Example: Slice for product prices
1
2
prices := []float64{9.99, 14.99, 7.50}
fmt.Println("Prices:", prices)
// Practical Example: Pre-allocating slice for batch processing
1
2
3
4
5
batch := make([]int, 0, 100)
for i := 0; i < 50; i++ {
batch = append(batch, i)
}
fmt.Println("Batch size:", len(batch))
// Practical Example: Slicing for pagination
1
2
3
allItems := []string{"A", "B", "C", "D", "E", "F"}
page := allItems[2:5]
fmt.Println("Page items:", page)
Slices make it easier to work with collections of data that change over time โฑ๏ธ.
// Practical Example: Variadic function using slices
1
2
3
4
5
6
7
8
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
fmt.Println("Sum:", sum(1, 2, 3, 4))
Letโs explore how to create slices in Go! ๐
Creating Slices in Go
// Practical Example: Monitoring slice capacity
1
2
3
4
5
data := make([]int, 0, 2)
for i := 0; i < 5; i++ {
data = append(data, i)
fmt.Printf("Len: %d, Cap: %d\n", len(data), cap(data))
}
Slices are dynamic arrays in Go, offering flexibility. Here are the common ways to create them:
Slice Literals
Imagine an array: [A, B, C, D, E, F]
.
Below is a visual diagram showing the flow of slice creation and usage in Go. This helps clarify how slices reference underlying arrays and how operations are performed:
graph LR
FD_DECL["Function Declaration"]:::javaStyle --> FD_CALL["Function Call"]:::jdbcStyle
FD_CALL --> FD_EXEC{"Function Body Execution"}:::driverStyle
FD_EXEC --> FD_RET["Return Value"]:::dbStyle
FD_RET --> FD_USE["Using Return Value"]:::useStyle
classDef javaStyle fill:#ff4f81,stroke:#c43e3e,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef jdbcStyle fill:#6b5bff,stroke:#4a3f6b,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef driverStyle fill:#ffd700,stroke:#d99120,color:#222,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef dbStyle fill:#00bfae,stroke:#005f99,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef useStyle fill:#ff9800,stroke:#f57c00,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
class FD_DECL javaStyle;
class FD_CALL jdbcStyle;
class FD_EXEC driverStyle;
class FD_RET dbStyle;
class FD_USE useStyle;
linkStyle default stroke:#e67e22,stroke-width:3px;
1
2
mySlice := make([]int, 5, 10) // Creates a slice of ints, length 5, capacity 10.
// Resources: https://go.dev/ref/spec#Making_slices_maps_and_channels
// Practical Example: Slicing for pagination
1
2
matrix := [][]int{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}
Choosing the right method depends on your needs: literals for known values, make
for controlled size and capacity, and slicing for creating views of existing data.
// Practical Example: Named return value for slice modification
1
2
3
4
5
6
7
func updateFirst(slice []int, value int) (updated []int) {
slice[0] = value
updated = slice
return
}
nums := []int{1, 2, 3}
fmt.Println("Updated slice:", updateFirst(nums, 99))
Working with Slices in Go ๐
Letโs explore some common and super useful slice operations in Go! Slices are like flexible arrays. // Practical Example: Defer with slices
func process(items []string) {
defer fmt.Println("Processing complete!")
for _, item := range items {
fmt.Println("Item:", item)
}
}
process([]string{"A", "B", "C"})
Essential Slice Actions ๐ ๏ธ
Adding Elements:
append()
grows your slice.1 2
nums := []int{1, 2, 3} nums = append(nums, 4, 5) // nums is now [1 2 3 4 5]
// Practical Example: Closure for matrix row sum
1
2
3
4
5
6
7
8
9
matrix := [][]int{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}
rowSum := func(row []int) int {
sum := 0
for _, v := range row {
sum += v
}
return sum
}
fmt.Println("Sum of first row:", rowSum(matrix[0]))
// Practical Example: Higher-order function for matrix processing
1
2
3
4
5
6
7
8
9
10
11
func applyToMatrix(matrix [][]int, fn func(int) int) [][]int {
for i := range matrix {
for j := range matrix[i] {
matrix[i][j] = fn(matrix[i][j])
}
}
return matrix
}
double := func(x int) int { return x * 2 }
result := applyToMatrix(matrix, double)
fmt.Println("Doubled matrix:", result)
Duplicating:
copy()
creates a new slice with the same content.1 2 3
original := []int{10, 20} duplicate := make([]int, len(original)) copy(duplicate, original) // duplicate is now [10 20]
Checking Size:
len()
gives you the number of elements.cap()
shows the sliceโs capacity (total space allocated).1 2 3
mySlice := []string{"a", "b"} fmt.Println(len(mySlice)) // Output: 2 fmt.Println(cap(mySlice)) // Output: usually 2 (or more, depends on creation)
Slicing:
[start:end]
creates a sub-slice.1 2
letters := []string{"A", "B", "C", "D", "E"} sub := letters[1:4] // sub is now ["B" "C" "D"]
Slice Growth and Memory ๐ง
Slices grow dynamically! When you append()
beyond capacity, Go reallocates a new, bigger underlying array, copies the old data, and then adds the new element. This can be performance-intensive. To minimize reallocations, you can use make()
to create a slice with an initial capacity if you know how large it will grow. Tip: Avoid appending in loops if you can pre-allocate!
- Resourses: for more in depth guide check the Go official Documentation.
Hereโs an explanation of Go slices with diagrams, tailored for easy understanding:
Understanding Go Slices: A Visual Guide ๐
Go slices are powerful! Think of them like flexible views into an array. Internally, they hold three things:
- A pointer โก๏ธ to the starting element of the underlying array.
- A length ๐ representing the number of elements the slice currently holds.
- A capacity ๐ฆ indicating the total space available in the underlying array from the sliceโs starting point.
How Slices Work ๐ ๏ธ
Imagine an array: [A, B, C, D, E, F]
.
graph LR
subgraph Array
A[A] --> B[B]
B --> C[C]
C --> D[D]
D --> E[E]
E --> F[F]
end
A slice mySlice := myArray[1:4]
(taking elements from index 1 up to, but not including, 4) would:
- Point to element
B
. - Have a length of 3 (B, C, D).
- Have a capacity of 5 (B, C, D, E, F).
graph LR
subgraph Array
A[A] --> B[B]
B --> C[C]
C --> D[D]
D --> E[E]
E --> F[F]
end
subgraph Slice
Pointer --> B
Length --> 3
Capacity --> 5
end
Modifications and Array Allocations โ๏ธ
Modifying mySlice
can affect the underlying array and other slices that share it. For example:
1
2
3
myArray := [6]string{"A", "B", "C", "D", "E", "F"}
mySlice := myArray[1:4] // mySlice is [B, C, D]
mySlice[0] = "X" // Changes myArray[1] to "X"
If you append
to mySlice
and its length is less than its capacity, the underlying array is used. If the length would exceed the capacity, a new, larger array is allocated, the data is copied, and the slice pointer is updated. The original array remains unchanged!
1
mySlice = append(mySlice, "G") // If capacity is enough, no new array
For more in-depth information, refer to the official Go blog on slices: Go Slices: usage and internals.
Okay, letโs explore multi-dimensional slices in Go! ๐
Slices of Slices in Go: Multi-Dimensional Arrays
Go doesnโt have built-in multi-dimensional arrays like some languages. Instead, you use slices of slices. Think of it as a slice where each element is another slice.
Creating 2D Slices (Matrices)
You can initialize them in a few ways:
Directly:
{% raw %}
1
matrix := [][]int{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}
Using
make
:1 2 3 4 5 6
rows := 3 cols := 4 matrix := make([][]int, rows) // Create 'rows' number of rows for i := range matrix { matrix[i] = make([]int, cols) // Initialize each row with 'cols' elements }
Accessing Elements
Accessing elements is straightforward: matrix[row][column]
. For example, matrix[0][1]
would access the element at row 0, column 1 (which is 2
in our first example).
Hereโs the breakdown:
matrix[0]
gives you the first[]int
(the first row).[1]
then picks the second element from that slice.
Example
1
2
3
4
5
6
7
8
9
10
11
```go
package main
import "fmt"
func main() {
matrix := [][]int{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}
fmt.Println(matrix[1][2]) // Output: 6
}
```
For more in depth explanation:
Conclusion
Fantastic! We made it! ๐ฅณ Now itโs your chance to chime in. What are your biggest takeaways? Any questions? Leave a comment below and letโs discuss! Iโm excited to hear what you have to say! ๐๐