Golang 103: Arrays and Slices in Golang

Golang 103: Arrays and Slices in Golang

Arrays and Slices

If you are coming from any other language then you might be knowing what are arrays, right? Arrays in Go are very similar to that. But let us talk about arrays and slices in Go.

Arrays

What it is?

Let us take an example and understand it. Suppose you have to store the marks of 3 students. What you will do to achieve this in Go maybe something like this:-

package main

import "fmt"

func main() {
    stu1_marks := 98    
    stu2_marks := 76.8    
    stu3_marks := 89.9
}

This is fine and you can do this. There is nothing wrong with it. But what if you need to store 10 or maybe 100 or more students' marks? Then will we create 100 or more such variables? No. Because first of all it will be very hard to do, and will not be scalable, or maintainable at all.

So, comes into the picture - Arrays. Arrays are a collection of data of the same type. If we want to store a collection of data of the same type then we will use arrays.

Characteristics of Arrays:-

  1. Arrays are homogenous i.e. they store data of the same data type.
  2. Arrays are fixed length i.e. once they are declared and initialized their size does not grow or shrink.
  3. Each element in arrays are stored at continuous blocks of memory.
  4. Arrays are 0-indexed means that positioning in the array starts from 0. So, the first element of arrays is at 0 index, 2nd at index 1, and so on.

Indexing image of Arrays

Declaring an array

We can declare an array in the following ways:-

var array_name [length]type

Note - This will create an array initialized with 0 values. There is nothing as uninitialized in Go.

Or

var array_name [length]type = [length]type{item1, item2, item3, .....}

Or

array_name := [length]int{item1, item2, item3, ....}

So we can declare array storing marks of 5 students something like this:-

var marks [5]int

And if we want to initialize it at the same time we can do the following:-

var marks [5]int = [5]int{97, 85, 70, 55, 100}

Or

marks := [5]int{97, 85, 70, 55, 100}

Note - Here, we can see that there are 5 elements in the array. So, Go is smart enough to compute this and thus we can replace the length with '...'. For eg:-

marks := [...]int{97, 85, 70, 55, 100}
// Or 
// var marks [5]int = [...]int{97, 85, 70, 55, 100}

But this can only be done when we initialize the array at the same time while declaring it.

Indexing

We can access array elements using indexing. Index in Go starts with 0 as mentioned earlier.

For eg. :-

package main

import "fmt"

func main() {
    marks := [...]int{97, 85, 70, 55, 100}

    fmt.Println(marks)
    fmt.Println(marks[0])
    fmt.Println(marks[1])
    fmt.Println(marks[2])
    fmt.Println(marks[3])
    fmt.Println(marks[4])

    marks[2] = 60  // Since arrays are mutable we can change value 
    // at any position via indexing

    fmt.Println(marks[2])
    fmt.Println(marks)
}

Output:-

[97 85 70 55 100]
97
85
70
55
100
60
[97 85 60 55 100]

Important points regarding arrays:-

  1. In an array, if an array does not initialize explicitly, then the default value of this array is 0.
  2. You can find the length of the array using len() method.
    fmt.Println(len(marks))  // Output - 5
    
  3. In Go language, an array is of value type not of reference type. So when the array is assigned to a new variable, then the changes made in the new variable do not affect the original array. As shown in the below example:-

    package main
    import "fmt"
    func main() {
    
     my_array := [...]int{100, 200, 300, 400, 500}
     fmt.Println("Original array(Before):", my_array)
    
     new_array := my_array
    
     fmt.Println("New array(before):", new_array)
    
     new_array[0] = 500
    
     fmt.Println("New array(After):", new_array)
    
     fmt.Println("Original array(After):", my_array)
    }
    /* 
    Output -
    Original array(Before): [100 200 300 400 500]
    New array(before): [100 200 300 400 500]
    New array(After): [500 200 300 400 500]
    Original array(After): [100 200 300 400 500]
    */
    
  4. In an array, if the element type of the array is comparable, then the array type is also comparable. So we can directly compare two arrays using the == operator. As shown in the below example:-

    package main
    import "fmt"
    func main() {
    
     arr1 := [3]int{9, 7, 6}
     arr2 := [...]int{9, 7, 6}
     arr3 := [3]int{9, 5, 3}
    
     fmt.Println(arr1 == arr2)
     fmt.Println(arr2 == arr3)
     fmt.Println(arr1 == arr3)
    
     // This will give an error because the
     // type of arr1 and arr4 is a mismatch
     /*
        arr4:= [4]int{9,7,6}
        fmt.Println(arr1==arr4)
     */
    }
    /*
    Output -
    true
    false
    false
    */
    

Slices

What it is?

The problem with arrays is that they are fixed lengths. To tackle this problem there is something known as slices in Go. Slices are similar to arrays as they are also collections of data of the same type. But they differ in the way that they have variable lengths. They are allocated memory dynamically.

Behind the scenes, arrays and slices are tightly coupled in a way that slice is nothing but a reference to an array.

Ways to create a slice

Declaring a slice

A slice is declared just like an array but we don't have to give length in the case of slices. For eg.:-

var slice1 []int // This will create a nil slice with 0 length and capacity and not a slice initialized with 0s
//or
var slice2 []int = []int{1, 2, 3, 4, 5}
//or
slice3 := []string{"ABC", "def"}

Using an array or existing slice

We can create a new slice from an existing array or slice using slicing as follows:-

array_name[low:high]
// or
slice_name[low:high]

Here, low is by default the 0th index, and high is the length of the array or slice. Whatever we give as high slicing is always done up till -1 of that value (for eg.- arr[1:3] will result in a slice from index 1 to 2).

package main

import "fmt"

func main() {

    // Creating an array
    arr := [4]int{1, 2, 3, 4}

    // Creating slices from the given array
    var my_slice_1 = arr[1:2]
    my_slice_2 := arr[0:]
    my_slice_3 := arr[:2]
    my_slice_4 := arr[:]

    // Display the result
    fmt.Println("My Array: ", arr)
    fmt.Println("My Slice 1: ", my_slice_1)
    fmt.Println("My Slice 2: ", my_slice_2)
    fmt.Println("My Slice 3: ", my_slice_3)
    fmt.Println("My Slice 4: ", my_slice_4)
}

/* 
Output -
My Array:  [1 2 3 4]
My Slice 1:  [2]
My Slice 2:  [1 2 3 4]
My Slice 3:  [1 2]
My Slice 4:  [1 2 3 4]

Using make function

You can also create a slice using the make() function which is provided by the go library. This function takes three parameters, i.e, type, length, and capacity. Here, capacity value is optional. It assigns an underlying array with a size that is equal to the given capacity and returns a slice that refers to the underlying array. Generally, the make() function is used to create an empty slice. Here, empty slices are those slices that contain an empty array reference. For example:-

var slice_1 = make([]int, 4) // Syntax - var slice_name = make(type of slice, length)
// or
slice_2 := make([]string, 4, 7) // Syntax - slice_name := make(type of slice, length, capacity)

make() is better to use if we can estimate the length of the slice we want, since otherwise whenever we will add more elements to the slice it will copy the elements each time which will be inefficient and expensive in terms of memory usage.

Components of a slice

A slice has three components:-

  1. Pointer - The pointer is used to point to the first element of the array that is accessible through the slice. Here, the pointed element doesn't need to be the first element of the array.
  2. Length - The length is the total no. of elements the slice is pointing to of the underlying array. Can be determined using len().
  3. Capacity - The capacity is the maximum no. of elements the slice can contain or point to of the array. This changes as the no. of elements increases. Can be determined using a cap().

Let us understand this with the help of an example:-

package main

import "fmt"

func main() {
    // Creating an array
    arr := [6]string{"This", "is", "the", "blog", "on", "Golang"}

    // Display array
    fmt.Println("Array:", arr)

    // Creating a slice
    myslice := arr[1:5]

    // Display slice
    fmt.Println("Slice:", myslice)

    // Display the length of the slice
    fmt.Printf("Length of the slice: %d", len(myslice))

    // Display the capacity of the slice
    fmt.Printf("\nCapacity of the slice: %d", cap(myslice))
}

/* 
Output -
Array: [This is the blog on Golang]
Slice: [is the blog on]
Length of the slice: 4
The capacity of the slice: 5
*/

Behind the scene, this is what happens:-

Slice background implementation

Since a slice just points to an array of some length and if we increase the size of the slice it just copies the old content to a new array of bigger size and points to this new array now. This we can see from the below example:-

package main

import "fmt"

func main() {
    var sli []int = []int{1, 2, 3, 4}
    fmt.Println(&sli[0]) // & operator is used to get the address of memory location of the object

    sli = append(sli, 123)
    fmt.Println(&sli[0])
}

/*
Output -
0xc0000b8000
0xc0000bc000
*/

Some characteristics of slices

  1. As we already know that slice is a reference type it can refer to an underlying array. So if we change some elements in the slice, then the changes will also take place in the referenced array. Or in other words, if you make any changes in the slice, then it will also reflect in the array as shown in the below example:-

    package main
    import "fmt"
    func main() {
    
    arr := [6]int{55, 66, 77, 88, 99, 22}
    slc := arr[0:4]
    
    fmt.Println("Original_Array: ", arr)
    fmt.Println("Original_Slice: ", slc)
    
    slc[0] = 100
    slc[1] = 189578
    
    fmt.Println("\nNew_Array: ", arr)
    fmt.Println("New_Slice: ", slc)
    }
    /*
    Output - 
    Original_Array:  [55 66 77 88 99 22]
    Original_Slice:  [55 66 77 88]
    New_Array:  [100 189578 77 88 99 22]
    New_Slice:  [100 189578 77 88]
    */
    
  2. In Slice, you can only use the == operator to check whether the given slice is nill or not. If you try to compare two slices with the help of the == operator then it will give you an error as shown in the below example:-

    package main
    import "fmt"
    func main() {
    
     s1 := []int{12, 34, 56}
     var s2 []int
    
     // If you try to run this commented
     // code compiler will give an error
     /*s3 := []int{23, 45, 66}
     fmt.Println(s1 == s3)
     */
    
     // Checking if the given slice is nil or not
     fmt.Println(s1 == nil)
     fmt.Println(s2 == nil)
    }
    /*
    Output - 
    false
    true
    */
    

Multi-dimensional Arrays and Slices

We can create multi-dimensional arrays and slices in Go too. These are slices or arrays in which the individual element is itself an N-dimensional slice or array respectively.

For eg. -

package main
import "fmt"
func main() {
    // Creating a 2-dimensional
    // array using the var keyword
    // and initializing a 2
    // -dimensional array using index
    var arr1 [2][2]int
    arr1[0][0] = 100
    arr1[0][1] = 200
    arr1[1][0] = 300
    arr1[1][1] = 400

    fmt.Println(arr1)
    fmt.Println(arr1[0][0]) // Indexing in multi-dimensional arrays

    // Creating and initializing
    // 2-dimensional slice
    // Using shorthand
    sli := [][]string{{"C#", "C", "Python"},
        {"Java", "Scala", "Perl"},
        {"C++", "Go", "HTML"}}

    fmt.Println(sli)
    fmt.Println(sli[1][0]) // Indexing in multi-dimensional slices
}
/*
Output -
[[100 200] [300 400]]
100
[[C# C Python] [Java Scala Perl] [C++ Go HTML]]
Java

General Syntax -

var array_name [length1][length2][length3].....type
//or
array_name := [length1][length2][length3].....type{{{item1, item2},{item1, item2}}}
var slice_name [][][].....type
//or
slice_name := [][][].....type{{{item1, item2},{item1, item2}}}

Wrap Up

So, with this, we end our discussion on arrays and slices in Go. I hope you enjoyed reading this blog and got something out of it. More will come in this series of Golang.

Feel Free to comment down below if there are mistakes😊️. Also, you can connect with me on -

LinkedIn - My LinkedIn Handle

Twitter - My Twitter Handle

Github - My Github profile