Array is one of the oldest and most important data structures in programming. It is an aggregate type which stores variables of the same type in most languages. In this article, we will explore arrays in Go and look at practical examples of working with arrays. We will also look at slices which are a more flexible implementation of an array in Go, so grab a cup of coffee at the front of your screens and follow me through this interesting journey of demystifying arrays and slices.
In order to follow through this article:-
- You need to have a basic understanding of programming languages.
- You need to download the Go compiler from the official website and follow the directions to install it based on your operating system being either Windows, MacOS or any of the Linux distributions.
- You need a code editor like visual studio, atom or sublime text. Alternatively, you can download an Integrated Development Environment (IDE). An IDE is an improved code editor with added qualities customized to help you write and execute codes easier. Golang is the most popular IDE used for developing Go programs.
- You need to have an understanding of Go basic data types like int, strings, and float.
How to Work with Arrays in Golang [Complete Tutorial]
Go, also known as Golang, is a programming language developed by Rob Pike, Robert Griesemer and Ken Thompson at Google in 2009. It is a statically typed and compiled language with a lot of similarities to older languages like C and C++. Go was created to solve some of the problems and complexities experienced in older programming languages by implementing features such as garbage collection and inbuilt concurrency.
The name “Golang” is a mixture of Go and lang (from language) to create a more distinguished name. It was adopted because of the former domain name of Go, which was golang.org. Through the remainder of this article, I will refer to Go or Golang where suitable, to prevent mixing it up with generic words.
A pointer refers to a variable which stores the memory address or location of another variable. As we know, every variable is stored in a memory location, and every memory location has an address. The pointer stores the address of a variable so the variable can easily be referenced and returned, modified or manipulated. In most languages, the address of a variable is returned using the ampersand (&) operator placed before the variable. Most programming languages recognize the concept of pointers and they are used in many forms for more low level computation.
When a pointer is declared, the data type of the variable it refers to must also be defined. In Go, a pointer is declared using the asterisk (*) character before a data type in the general form:
var <var_name> *<data_type>
We can also assign the address of another variable to a variable, after which the compiler automatically infers that we intend to store a pointer type. The default value of a pointer is a nil pointer.
Ubuntu 20.04Linux environment.
Array is a term in programming used to represent an aggregate data structure storing data of the same type. Each data is stored in contiguous memory locations and are called elements. The elements of an array are assessed using its index from 0 to N-1 where N represents the number of elements in the array (its length). Each element of an array, being of the same data type, will be stored with the same number of bits, meaning that the size of an array is the product of its length and the size of a single element.
For example, an array of five 8-bit (1 byte) integers will have a size of is (8 * 5) bits. Array is one of the most important data structures and its concept is built into almost every programming language. It can be used to implement other data structures like lists and strings. A string is simply an immutable array of characters, which explains why they are also assessed with an index. In most programming languages, arrays are of a fixed size, meaning they can’t shrink or expand. Since they are stored in contiguous memory, the next memory location may not be available when trying to expand a defined array.
Arrays in GO
An array in Go is a fixed length sequence of data or variables. It is an aggregate data type which contains data of similar type and is declared using [N]T where N is the length of the array and T is the common data type of its elements.
After declaring an array, each element stores the zero value of its data type and equivalent memory is allocated for them. An array can be declared and initialized with its elements on the same line. Also, replacing the length of an array with the variadic operator (...) tells the compiler to decipher its length from the initialized elements. An array can also be defined with its constant index and value separated by a semicolon.
An array in Go uses a 0-indexing format, which means the first element has an index of 0 and the last element has an index of N-1, where N is the length of the array. The value in a memory location of an array is fetched using its index, and can be modified to store a different value. Trying to access an index out of range of its array will return a panic. The for-range loop can range over an array to print its indexes and values.
Since an array has a fixed size, its length cannot be changed during runtime. Typically, arrays are used to store constant values that could be referenced during runtime. An array is not used regularly in Go programs because of this limitation, instead, a slice which supports dynamic memory allocation is used.
Slices in Go
A slice is a variable length sequence of elements containing the same data type. A slice is declared like an array but without its length in a square bracket. It can also be taken from a segment of an array using a slicing operation in the form M[i:j] where M is the array, i and j represent the start and end of the slice from the array. The elements of the slice will contain elements from index i to j-1. When performing a slicing operation, i and/or j can be excluded, and the compiler will interpret them as the start or end of the parent array.
A slice stores a reference to a subsequence of an array which is called the slice's underlying array. A slice is made up of three components: a pointer, length and capacity, where the pointer points to the part of the array accessible by the slice, the length refers to the number of elements accessible by the slice, and the capacity refers to how large the slice can grow (the width of the underlying array). The inbuilt len() and cap() functions return the length and capacity of the slice respectively. Declaring a slice directly will automatically create an underlying array and return a slice of it.
A slice can also be created from another slice, using the slicing operation described above. Modifying one of the values of a slice will change that value in the underlying array and other parent slices since the slice stores a reference/pointer. A slice can also be declared using the inbuilt make() function which takes the data type of each element of the slice in the form [ ]T, the initial length of the slice, and an optional capacity value. If a value for cap is not indicated, the slice will have the same capacity as its length.
The zero value of a slice is a nil slice which has no underlying array, and has len 0 and cap 0. A nil slice though must not be confused with an empty slice which also has a len of 0 and cap of 0. In practice, it is better to check if a slice has a length of zero, than checking if it equals nil because the slice may not be equal to nil but have a length of 0.
The append() function
Values can be added to a slice using the inbuilt append() function. The function takes a slice and at least one value of the same type to be appended to the end of the slice, and returns the updated slice:
[ ]T = append([ ]T, T)
We can pass an infinite number of values to be appended to the slice.
Using a variadic operator (...) after a slice to be appended to the slice will break the slice into its arguments and pass each to be added to the slice. This is possible because the append function is a variadic function that can take any number of arguments.
If we append to a slice a number of values that makes it exceed its capacity (the length of the underlying array), a new underlying array is created and a slice of it is returned. The capacity of the new slice will be 2 times the former capacity to prevent regular allocation of new areas. This behavior ensures that slices are more efficient, giving them a small average insertion time.
A slice provides more flexibility than arrays since it allows dynamic memory allocation during run time. An array on the other hand is more rigid and sticks to its already defined size during runtime. A slice is used more regularly in most programs but a slice cannot exist without an array, therefore a good understanding of arrays is necessary to write very efficient programs with slices.
A slice and array are useful concepts in Go and can be used to solve specific problems where necessary. A popular application of slices is in the easy conversion of strings to slice of bytes ([ ]byte) to prevent continuous memory allocations and copying. The strings package has a lot of similar methods as the bytes package.