Arrays, Slices, Maps and Structs in Go

Explore the power of Go with our comprehensive guide on Arrays, Slices, Maps, and Structs. Learn effective data handling and boost your Go programming skills.
E
Edtoks15:45 min read

1. What are Advanced Data Types in Go?

In Go, advanced data types refer to more complex structures that allow developers to organize and manipulate data in a more flexible and efficient manner. Four key advanced data types in Go are Arrays, Slices, Maps, and Structures. Each has its unique characteristics and use cases.

2. Arrays

2.1 Definition

An array in Go is a fixed-size sequence of elements of the same type. The size of an array is determined at the time of declaration.

package main
import "fmt"
func main() {

// Declaration of an array with size 3

var numbers [3]int

fmt.Println(numbers)

}

Output is

[0 0 0]

2.2 Array Literals

Array literals provide a concise way to create arrays.

package main
import "fmt"
func main() {

// Array literal

numbers := [3]int{1, 2, 3}

fmt.Println(numbers) // Output: [1 2 3]

}

2.3 Accessing Elements

Accessing elements in an array is done using index notation.

package main
import "fmt"
func main() {

numbers := [3]int{1, 2, 3}

fmt.Println(numbers[0]) // Output: 1

}

2.4 Iterating Arrays

Iterating over an array can be done using a for loop.

package main
import "fmt"
func main() {

numbers := [3]int{1, 2, 3}
// Iterating the array
for i := 0; i < len(numbers); i++ {
    fmt.Println(numbers[i])
}
}

2.5 Creating with Make

Arrays are typically created with a fixed size at compile time. If you need a dynamic size, slices are more appropriate.

2.6 Operations on Arrays

Arrays in Go have a fixed size, so operations like appending or deleting elements aren't directly supported. However, you can modify individual elements or create a new array with the desired size.

2.7 Passing to Function

In Go, when you pass an array to a function, a copy of the array is made. Unlike slices, arrays are value types, and modifications made to the array inside the function do not affect the original array. Here's an example:

package main

import "fmt"

// Function that modifies an array
func modifyArray(arr [3]int) {
	arr[0] = 100
}

func main() {
	// Creating an array
	myArray := [3]int{1, 2, 3}

	// Passing the array to the function
	modifyArray(myArray)

	// Printing the original array
	fmt.Println("Original Array:", myArray)
}

Explanation:

  1. We define a function modifyArray that takes an array [3]int as a parameter.

  2. Inside the function, we attempt to modify the first element of the array to 100.

  3. In the main function, we create an array myArray with values {1, 2, 3}.

  4. We then call the modifyArray function, passing myArray as an argument.

  5. After the function call, we print the original array.

When you run this program, you'll notice that the original array remains unchanged. This is because the array is passed by value, and modifications made inside the function are applied to a copy of the original array. The output will be:

Original Array: [1 2 3]

Original Array: [1 2 3]

If you want to modify the original array, you can pass a pointer to the array as an argument. Here's an example:

package main

import "fmt"

// Function that modifies an array using a pointer
func modifyArray(arr *[3]int) {
	arr[0] = 100
}

func main() {
	// Creating an array
	myArray := [3]int{1, 2, 3}

	// Passing a pointer to the array to the function
	modifyArray(&myArray)

	// Printing the modified array
	fmt.Println("Modified Array:", myArray)
}

In this case, the function modifyArray takes a pointer *[3]int as a parameter, and modifications made inside the function affect the original array. The output will be:

Modified Array: [100 2 3]

2.8 Multidimensional Array

Multidimensional arrays can be created by nesting arrays inside arrays.

package main
import "fmt"
func main() {

// Multidimensional array

var matrix [3][3]int

fmt.Println(matrix) // Output: [[0 0 0] [0 0 0] [0 0 0]]

}

3. Slices

3.1 Definition

A slice in Go is a flexible, dynamically-sized view into an underlying array. Unlike arrays, slices are not of a fixed size.

package main
import "fmt"
func main() {

// Declaration of a slice

var mySlice []int

fmt.Println(mySlice) // Output: []

}

3.2 Slice Literals

Slice literals are like array literals, but without a specified length.

package main
import "fmt"
func main() {

// Slice literal

mySlice := []int{1, 2, 3}

fmt.Println(mySlice) // Output: [1 2 3]

}

3.3 Accessing Elements

Accessing elements in a slice is similar to accessing elements in an array.

package main
import "fmt"
func main() {

mySlice := []int{1, 2, 3}

fmt.Println(mySlice[0]) // Output: 1

}

3.4 Iterating Slices

Iterating over a slice can also be done using a for loop.

package main
import "fmt"
func main() {

mySlice := []int{1, 2, 3}
// Iterating the slice
for i, value := range mySlice {
    fmt.Println(i, value)
}
}

3.5 Creating with Make

Slices can be created using the make function.

package main
import "fmt"
func main() {

// Creating a slice with make

mySlice := make([]int, 3, 5)

fmt.Println(mySlice) // Output: [0 0 0]

fmt.Println(len(mySlice)) // Output: 3

fmt.Println(cap(mySlice)) // Output: 5

}

3.6 Operations on Slices

Slices support operations like appending, deleting, and modifying elements.

package main
import "fmt"
func main() {

mySlice := []int{1, 2, 3}
// Appending an element
mySlice = append(mySlice, 4)

// Deleting an element
mySlice = append(mySlice[:1], mySlice[2:]...)

fmt.Println(mySlice) // Output: [1 3 4]
}

3.7 Passing to Function

In Go, when you pass a slice to a function, you are passing a reference to the underlying array. Therefore, any modifications made to the elements of the slice inside the function will affect the original slice. Here's an example to illustrate this concept:

package main

import "fmt"

// Function that modifies a slice
func modifySlice(s []int) {
	// Modifying the first element of the slice
	s[0] = 100
}

func main() {
	// Creating a slice
	mySlice := []int{1, 2, 3}

	// Passing the slice to the function
	modifySlice(mySlice)

	// Printing the modified slice
	fmt.Println("Modified Slice:", mySlice)
}

Explanation:

  1. We define a function modifySlice that takes a slice []int as a parameter.

  2. Inside the function, we modify the first element of the slice to 100.

  3. In the main function, we create a slice mySlice with values {1, 2, 3}.

  4. We then call the modifySlice function, passing mySlice as an argument.

  5. After the function call, we print the modified slice.

When you run this program, you'll see that the original slice is modified:

Modified Slice: [100 2 3]

Modified Slice: [100 2 3]

This is because slices are reference types, and when passed to a function, changes to the underlying array are visible outside the function. It's important to note that the length and capacity of the slice remain the same after the modification.

In summary, when you pass a slice to a function in Go, you are working with a reference to the original underlying array, allowing you to modify elements and see those modifications outside the function.

3.8 Multidimensional Slices

Multidimensional slices can be created similarly to multidimensional arrays.

package main
import "fmt"
func main() {

// Multidimensional slice

var matrix [][]int

matrix = append(matrix, []int{1, 2, 3})

matrix = append(matrix, []int{4, 5, 6})

fmt.Println(matrix) // Output: [[1 2 3] [4 5 6]]

}

4. Maps

4.1 Definition

A map in Go is an unordered collection of key-value pairs.

package main
import "fmt"
func main() {

// Declaration of a map

myMap := make(map[string]int)

fmt.Println(myMap) // Output: map[]

}

4.2 Map Literals

Map literals provide a concise way to create maps.

package main
import "fmt"
func main() {

// Map literal

myMap := map[string]int{"a": 1, "b": 2, "c": 3}

fmt.Println(myMap) // Output: map[a:1 b:2 c:3]

}

4.3 Accessing Elements

Accessing elements in a map is done using the key.

package main
import "fmt"
func main() {

myMap := map[string]int{"a": 1, "b": 2, "c": 3}

fmt.Println(myMap["b"]) // Output: 2

}

4.4 Iterating Maps

Iterating over a map can be done using a for loop.

package main
import "fmt"
func main() {

myMap := map[string]int{"a": 1, "b": 2, "c": 3}
// Iterating the map
for key, value := range myMap {
    fmt.Println(key, value)
}
}

4.5 Creating with Make

Maps can be created using the make function.

package main
import "fmt"
func main() {

// Creating a map with make

myMap := make(map[string]int)

fmt.Println(myMap) // Output: map[]

}

4.6 Operations on Maps

Maps support operations like adding, deleting, and checking the existence of keys.

package main
import "fmt"
func main() {

myMap := map[string]int{"a": 1, "b": 2, "c": 3}
// Adding a key-value pair
myMap["d"] = 4

// Deleting a key
delete(myMap, "b")

// Checking key existence
value, exists := myMap["c"]

fmt.Println(myMap) // Output: map[a:1 c:3 d:4]
fmt.Println(value, exists) // Output: 3 true
}

4.7 Passing to Function

In Go, when you pass a map to a function, you're passing a reference to the original map. Any modifications made to the map inside the function will directly affect the original map. Here's an example to illustrate this concept:

package main

import "fmt"

// Function that modifies a map
func modifyMap(m map[string]int) {
	// Adding a new key-value pair to the map
	m["newKey"] = 42
}

func main() {
	// Creating a map
	myMap := map[string]int{"a": 1, "b": 2}

	// Passing the map to the function
	modifyMap(myMap)

	// Printing the modified map
	fmt.Println("Modified Map:", myMap)
}

Explanation:

  1. We define a function modifyMap that takes a map map[string]int as a parameter.

  2. Inside the function, we add a new key-value pair to the map.

  3. In the main function, we create a map myMap with keys "a" and "b".

  4. We then call the modifyMap function, passing myMap as an argument.

  5. After the function call, we print the modified map.

When you run this program, you'll see that the original map is modified:

Modified Map: map[a:1 b:2 newKey:42

This is because maps are reference types in Go. When you pass a map to a function, you are working with a reference to the underlying data structure, and changes made to the map inside the function are reflected in the original map.

It's important to note that if you pass a nil map to a function and attempt to modify it, a runtime panic will occur. Ensure that the map is initialized before passing it to functions that modify it.

5. Structs

5.1 Definition

A struct in Go is a composite data type that groups together variables (fields) under a single name.

package main
import "fmt"
// Definition of a struct

type Person struct {

FirstName string

LastName  string

Age       int

}
func main() {

// Declaration of a struct variable

var john Person

fmt.Println(john) // Output: { 0}

}

5.2 Declaring Struct

Structs are declared with the type keyword.

package main
import "fmt"
// Declaration of a struct

type Person struct {

FirstName string

LastName  string

Age       int

}
func main() {

// Declaration of a struct variable

var john Person

fmt.Println(john) // Output: { 0}

}

5.3 Creating Instances of Structs

Instances of structs are created using literal syntax.

package main
import "fmt"
// Declaration of a struct

type Person struct {

FirstName string

LastName  string

Age       int

}
func main() {

// Creating an instance of a struct

john := Person{"John", "Doe", 30}

fmt.Println(john) // Output: {John Doe 30}

}

5.4 Accessing Structure Members

Struct members are accessed using dot notation.

package main
import "fmt"
// Declaration of a struct

type Person struct {

FirstName string

LastName  string

Age       int

}
func main() {

// Creating an instance of a struct

john := Person{"John", "Doe", 30}
// Accessing struct members
fmt.Println(john.FirstName) // Output: John
}

5.5 Create using new

The new keyword can be used to create an instance of a struct, returning a pointer to the newly allocated memory.

package main
import "fmt"
// Declaration of a struct

type Person struct {

FirstName string

LastName  string

Age       int

}
func main() {

// Creating an instance of a struct using new

john := new(Person)

john.FirstName = "John"

john.LastName = "Doe"

john.Age = 30

fmt.Println(*john) // Output: {John Doe 30}

}

5.6 Nested Structures

Structs can be nested within other structs.

package main
import "fmt"
// Declaration of nested structs

type Address struct {

City  string

State string

}
type Person struct {

FirstName string

LastName  string

Age       int

Address   Address

}
func main() {

// Creating an instance of a struct with nested structs

john := Person{

FirstName: "John",

LastName:  "Doe",

Age:       30,

Address: Address{

City:  "New York",

State: "NY",

},

}

fmt.Println(john) // Output: {John Doe 30 {New York NY}}

}

5.7 Use field tags in the definition of structures

Field tags can be added to struct fields to provide additional information, often used for serialization.

package main
import (

"fmt"

"reflect"

)
// Declaration of a struct with field tags

type Person struct {

FirstName string json:"first_name"

LastName  string json:"last_name"

Age       int    json:"age"

}
func main() {

// Accessing field tags using reflection

john := Person{"John", "Doe", 30}

t := reflect.TypeOf(john)

for i := 0; i < t.NumField(); i++ {

field := t.Field(i)

fmt.Println(field.Tag.Get("json"))

}

}

5.8 Passing to Function

In Go, when you pass a struct to a function, you are passing a copy of the struct by default. However, you can pass a pointer to the struct if you want the function to modify the original struct. Here's an example to demonstrate both scenarios:

package main

import "fmt"

// Definition of a simple Person struct
type Person struct {
	FirstName string
	LastName  string
	Age       int
}

// Function that modifies a struct (passing by value)
func modifyStructByValue(p Person) {
	p.Age = 31
}

// Function that modifies a struct (passing by reference)
func modifyStructByReference(p *Person) {
	p.Age = 31
}

func main() {
	// Creating a struct instance
	john := Person{"John", "Doe", 30}

	// Passing the struct by value to a function
	modifyStructByValue(john)
	fmt.Println("After modifyStructByValue:", john)

	// Passing the struct by reference to a function
	modifyStructByReference(&john)
	fmt.Println("After modifyStructByReference:", john)
}

Explanation:

  1. We define a simple Person struct with FirstName, LastName, and Age fields.

  2. We have two functions, modifyStructByValue and modifyStructByReference, to demonstrate passing the struct by value and by reference, respectively.

  3. modifyStructByValue takes a Person parameter, which is passed by value. Any modifications inside the function do not affect the original struct.

  4. modifyStructByReference takes a pointer to a Person parameter, allowing it to modify the original struct directly.

  5. In the main function, we create an instance of the Person struct named john with an initial age of 30.

  6. We call modifyStructByValue and modifyStructByReference functions successively, passing the john struct as an argument.

  7. We print the state of the john struct after each function call.

When you run this program, you'll see the following output:

After modifyStructByValue: {John Doe 30}
After modifyStructByReference: {John Doe 31}

As demonstrated, passing a struct by value does not modify the original struct, while passing a struct by reference (using a pointer) allows modifications to affect the original struct. Choose the approach that fits your use case and performance considerations.

5.9 Pointers to Structures

Pointers to structs can be used to pass a reference to a struct, allowing modifications to affect the original struct.

package main
import "fmt"
// Declaration of a struct

type Person struct {

FirstName string

LastName  string

Age       int

}
func modifyPerson(p *Person) {

p.Age = 31

}
func main() {

// Creating an instance of a struct

john := Person{"John", "Doe", 30}
// Passing a pointer to the struct
modifyPerson(&amp;john)

fmt.Println(john.Age) // Output: 31
}

5.10 Creating anonymous structures

Anonymous structures are unnamed and are often used for short-lived purposes.

package main
import "fmt"
func main() {

// Anonymous structure

person := struct {

FirstName string

LastName  string

Age       int

}{

FirstName: "John",

LastName:  "Doe",

Age:       30,

}

fmt.Println(person) // Output: {John Doe 30}

}

Conclusions

These comprehensive examples cover the basics of advanced data types in Go, including arrays, slices, maps, and structures. Understanding these data types and their operations is crucial for effective and idiomatic Go programming.

Let's keep in touch!

Subscribe to keep up with latest updates. We promise not to spam you.