DEV Community

Sahil Sojitra
Sahil Sojitra

Posted on

Demystifying Go's Syntax: A Sarcastic Journey through Pointers, Structs, Arrays, Slices, and Maps | Part - 2

In the world of programming languages, Golang (also known as Go) has gained significant popularity for its simplicity, efficiency, and robustness. With its unique syntax and powerful features, Golang offers developers a versatile toolkit to build scalable and high-performance applications. In this blog, we will dive deep into some of the key aspects of Golang syntax, namely pointers, struct, arrays, slices & maps. Let's explore each topic in detail:

Pointers:

Pointers play a crucial role in Golang and understanding them is essential for effective memory management and manipulating data. This section will cover the basics of pointers, including their declaration, referencing, and dereferencing. We will explore how pointers enable pass-by-reference and allow us to modify variables directly in memory.

  • Go has pointers. A pointer holds the memory address of a value.
  • The type *T is a pointer to a T value. Its zero value is nil.
var p *int
Enter fullscreen mode Exit fullscreen mode
  • The & operator generates a pointer to its operand.
i := 177
p = &i
Enter fullscreen mode Exit fullscreen mode
  • The * operator denotes the pointer's underlying value.
fmt.Println(*p) // read i through the pointer p
*p = 146        // set i through the pointer p
Enter fullscreen mode Exit fullscreen mode
  • This is known as "dereferencing" or "indirecting".

Consider the following example:

package main

import "fmt"

func main() {

    i, j := 146, 5256 // initialize the i,j 

    p := &i         // point to i
    fmt.Println(*p) // read i through the pointer
    *p = 177        // set i through the pointer
    fmt.Println(i)  // see the new value of i

    p = &j           // point to j
    *p = *p / 146   // divide j through the pointer
    fmt.Println(j) // see the new value of j
}
Enter fullscreen mode Exit fullscreen mode

Output:

146
177
36
Enter fullscreen mode Exit fullscreen mode

Structs

In Go, a struct is a composite data type that allows you to define your own custom data structure by grouping together different types of values into a single entity. It is similar to a class in object-oriented programming languages but without methods.

To define a struct in Go, you use the type keyword followed by the name of the struct and a set of fields enclosed in curly braces. Here's an example:

  • A struct is a collection of fields.
  • Struct fields are accessed using a dot.
  • Struct fields can be accessed through a struct pointer. To access the field X of a struct when we have the struct pointer p we could write (*p).X. However, that notation is cumbersome, so the language permits us instead to write just p.X, without the explicit dereference.
  • A struct literal denotes a newly allocated struct value by listing the values of its fields. You can list just a subset of fields by using the Name: syntax.
package main

import "fmt"

// Structs  
type Vertex struct {
    X int
    Y int
}

var (
    v1 = Vertex{1, 2}  // has type Vertex
    v2 = Vertex{X: 1}  // Y:0 is implicit
    v3 = Vertex{}      // X:0 and Y:0
    q  = &Vertex{1, 2} // has type *Vertex
)

func main() {

    // Struct
    fmt.Println(Vertex{1, 2})

    // Struct Fields
    v := Vertex{1, 2}
    v.X = 4
    fmt.Println(v.X,v.Y)

    // Pointers to Struct
    f := Vertex{1, 2}
    p := &f
    p.X = 3
    fmt.Println(f)

    // Struct Literals
    fmt.Println(v1,v2, v3, q)   
}
Enter fullscreen mode Exit fullscreen mode

Output:

{1 2}
4 2
{3 2}
{1 2} {1 0} {0 0} &{1 2}
Enter fullscreen mode Exit fullscreen mode

Arrays

In Go, an array is a fixed-size collection of elements of the same type. The size of an array is specified at compile time and cannot be changed during runtime. Here's an example of declaring and using an array:

package main

import "fmt"

func main() {
    var a [2]string
    a[0] = "Hello"
    a[1] = "World"
    fmt.Println(a[0], a[1])
    fmt.Println(a)

    primes := [6]int{2, 3, 5, 7, 11, 13}
    fmt.Println(primes)
}
Enter fullscreen mode Exit fullscreen mode

Output:

Hello World
[Hello World]
[2 3 5 7 11 13]
Enter fullscreen mode Exit fullscreen mode

Slices

Slices are dynamic, resizable, and more flexible than arrays in Go. They are built on top of arrays and provide additional functionality. Slices do not have a fixed size and can grow or shrink as needed. Here's an example:

package main

import "fmt"

func main() {
    primes := []int{2, 3, 5, 7, 11, 13}

    primes = append(primes, 17)   // Appending elements to the slice
    primes = append(primes, 19)
    primes = append(primes, 23)

    fmt.Println(primes)            // Output: [2 3 5 7 11 13 17 19 23]
    fmt.Println(primes[1])         // Output: 3
    fmt.Println(len(primes))       // Output: 9 (length of the slice)

    // Slicing a slice to create a new slice
    newSlice := primes[1:5]         // Contains elements at indices 1 and 4 (inclusive:exclusive)
    fmt.Println(newSlice)           // Output: [3 5 7 11]
}
Enter fullscreen mode Exit fullscreen mode

Output:

[2 3 5 7 11 13 17 19 23]
3
9
[3 5 7 11]
Enter fullscreen mode Exit fullscreen mode

Slices are like references to arrays

  • A slice does not store any data, it just describes a section of an underlying array.
  • Changing the elements of a slice modifies the corresponding elements of its underlying array.
  • Other slices that share the same underlying array will see those changes.
package main

import "fmt"

func main() {
    names := [4]string{
        "Ram",
        "Lakshman",
        "Parshuram",
        "Samvad",
    }
    fmt.Println(names)

    a := names[0:2] // 0 to 1
    b := names[1:3] // 1 to 2
    fmt.Println(a, b)

    b[0] = "Krishna"
    // Changing the element of a Slice b
    // Modifies the corresponding elements of its underlying array names
    fmt.Println(a, b)
    fmt.Println(names)
}
Enter fullscreen mode Exit fullscreen mode

Output:

[Ram Lakshman Parshuram Samvad]
[Ram Lakshman] [Lakshman Parshuram]
[Ram Krishna] [Krishna Parshuram]
[Ram Krishna Parshuram Samvad]
Enter fullscreen mode Exit fullscreen mode

Slice literals

A slice literal is like an array literal without the length.

This is an array literal:
[3]bool{true, true, false}

And this creates the same array as above, then builds a slice that references it:
[]bool{true, true, false}

package main

import "fmt"

func main() {
    q := []int{2, 3, 5, 7, 11, 13}
    fmt.Println(q)

    r := []bool{true, false, true, true, false, true}
    fmt.Println(r)

    s := []struct {
        i int
        b bool
    }{
        {2, true},
        {3, false},
        {5, true},
        {7, true},
        {11, false},
        {13, true},
    }
    fmt.Println(s)
}
Enter fullscreen mode Exit fullscreen mode

Output:

[2 3 5 7 11 13]
[true false true true false true]
[{2 true} {3 false} {5 true} {7 true} {11 false} {13 true}]
Enter fullscreen mode Exit fullscreen mode

Slice Defaults

  • When slicing, you may omit the high or low bounds to use their defaults instead.
  • The default is zero for the low bound and the length of the slice for the high bound.

For the array

var a [10]int
Enter fullscreen mode Exit fullscreen mode

these slice expressions are equivalent:

a[0:10]
a[:10]
a[0:]
a[:]
Enter fullscreen mode Exit fullscreen mode
package main

import "fmt"

func main() {
    s := []int{2, 3, 5, 7, 11, 13}

    s = s[1:4] // [3 5 7]
    fmt.Println(s)

    s = s[:2] // [3 5]
    fmt.Println(s)

    s = s[1:] // [5]
    fmt.Println(s)
}
Enter fullscreen mode Exit fullscreen mode

Output:

[3 5 7]
[3 5]
[5]
Enter fullscreen mode Exit fullscreen mode

Slice length & capacity

  • A slice has both a length and a capacity.
  • The length of a slice is the number of elements it contains.
  • The capacity of a slice is the number of elements in the underlying array, counting from the first element in the slice.
  • The length and capacity of a slice s can be obtained using the expressions len(s) and cap(s).
package main

import "fmt"

func main() {
    s := []int{2, 3, 5, 7, 11, 13}
    printSlice(s)

    // Slice the slice to give it zero length.
    s = s[:0]
    printSlice(s)

    // Extend its length.
    s = s[:4]
    printSlice(s)

    // Drop its first two values.
    s = s[2:]
    printSlice(s)
}

func printSlice(s []int) {
    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
Enter fullscreen mode Exit fullscreen mode

Output:

len=6 cap=6 [2 3 5 7 11 13]
len=0 cap=6 []
len=4 cap=6 [2 3 5 7]
len=2 cap=4 [5 7]
Enter fullscreen mode Exit fullscreen mode

Nil Slices

  • The zero value of a slice is nil.
  • A nil slice has a length and capacity of 0 and has no underlying array.
package main

import "fmt"

func main() {
    var s []int
    fmt.Println(s, len(s), cap(s))
    if s == nil {
        fmt.Println("nil!")
    }
}
Enter fullscreen mode Exit fullscreen mode

Output:

[] 0 0
nil!
Enter fullscreen mode Exit fullscreen mode

Creating a slice with make

  • Slices can be created with the built-in make function; this is how you create dynamically-sized arrays.
  • The make function allocates a zeroed array and returns a slice that refers to that array:
a := make([]int, 5)  // len(a)=5
Enter fullscreen mode Exit fullscreen mode
  • To specify a capacity, pass a third argument to make:
b := make([]int, 0, 5) // len(b)=0, cap(b)=5

b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:]      // len(b)=4, cap(b)=4
Enter fullscreen mode Exit fullscreen mode

Here's an example:

package main

import "fmt"

func main() {
    a := make([]int, 5)
    printSlice("a", a)

    b := make([]int, 0, 5)
    printSlice("b", b)

    c := b[:2]
    printSlice("c", c)

    d := c[2:5]
    printSlice("d", d)
}

func printSlice(s string, x []int) {
    fmt.Printf("%s len=%d cap=%d %v\n",
        s, len(x), cap(x), x)
}
Enter fullscreen mode Exit fullscreen mode

Output:

a len=5 cap=5 [0 0 0 0 0]
b len=0 cap=5 []
c len=2 cap=5 [0 0]
d len=3 cap=3 [0 0 0]
Enter fullscreen mode Exit fullscreen mode

Slices of Slices

Slices can contain any type, including other slices.

package main

import (
    "fmt"
    "strings"
)

func main() {
    // Create a tic-tac-toe board.
    board := [][]string{
        []string{"_", "_", "_"},
        []string{"_", "_", "_"},
        []string{"_", "_", "_"},
    }

    // The players take turns.
    board[0][0] = "X"
    board[2][2] = "O"
    board[1][2] = "X"
    board[1][0] = "O"
    board[0][2] = "X"

    for i := 0; i < len(board); i++ {
        fmt.Printf("%s\n", strings.Join(board[i], " "))
    }
}
Enter fullscreen mode Exit fullscreen mode

Output:

X _ X
O _ X
_ _ O
Enter fullscreen mode Exit fullscreen mode

Appending to a Slice

To append elements to a slice in Go, you can use the built-in append() function. The append() function takes a slice and one or more elements as arguments, and it returns a new slice with the appended elements. Here's an example:

package main

import "fmt"

func main() {
    var s []string
    printSlice(s)

    // append works on nil slices.
    s = append(s, "Ram")
    printSlice(s)

    // The slice grows as needed.
    s = append(s, "Lakshman")
    printSlice(s)

    // We can add more than one element at a time.
    s = append(s, "Parshuram", "Samvad", "Krishna", "Balarama")
    printSlice(s)
}

func printSlice(s []string) {
    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
Enter fullscreen mode Exit fullscreen mode

Output:

len=0 cap=0 []
len=1 cap=1 [Ram]
len=2 cap=2 [Ram Lakshman]
len=6 cap=6 [Ram Lakshman Parshuram Samvad Krishna Balarama]
Enter fullscreen mode Exit fullscreen mode
  • The range form of the for loop iterates over a slice or map.
  • When ranging over a slice, two values are returned for each iteration. The first is the index, and the second is a copy of the element at that index.
  • You can skip the index or value by assigning to _.
for i, _ := range pow
for _, value := range pow
Enter fullscreen mode Exit fullscreen mode
  • If you only want the index, you can omit the second variable.
for i := range pow
Enter fullscreen mode Exit fullscreen mode

Here's an example:

package main

import "fmt"

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

func main() {
    for i, v := range pow {
        fmt.Printf("2**%d = %d\n", i, v)
    }

    fmt.Println()

    pow := make([]int, 10)
    for i := range pow {
        pow[i] = 1 << uint(i) // == 2**i
    }
    for _, value := range pow {
        fmt.Printf("%d ", value)
    }
}
Enter fullscreen mode Exit fullscreen mode

Output:

2**0 = 1
2**1 = 2
2**2 = 4
2**3 = 8
2**4 = 16
2**5 = 32
2**6 = 64
2**7 = 128

1 2 4 8 16 32 64 128 256 512 
Enter fullscreen mode Exit fullscreen mode

Maps

In Go, a map is a built-in data structure that represents an unordered collection of key-value pairs. It is similar to dictionaries or hash tables in other programming languages. Maps provide an efficient way to store and retrieve values based on unique keys. Here's an example of using maps in Go:

package main

import "fmt"

type Vertex struct {
    Lat, Long float64
}

var m = map[string]Vertex{
    "Bell Labs": Vertex{
        40.68433, -74.39967,
    },
    "Google": Vertex{
        37.42202, -122.08408,
    },
}

func main() {
    fmt.Println(m["Bell Labs"])
    fmt.Println(m)  
}
Enter fullscreen mode Exit fullscreen mode

Output:

{40.68433 -74.39967}
map[Bell Labs:{40.68433 -74.39967} Google:{37.42202 -122.08408}]
Enter fullscreen mode Exit fullscreen mode

Mutating Map

  • Insert or update an element in map m:
m[key] = elem
Enter fullscreen mode Exit fullscreen mode
  • Retrieve an element:
elem = m[key]
Enter fullscreen mode Exit fullscreen mode
  • Delete an element:
delete(m, key)
Enter fullscreen mode Exit fullscreen mode
  • Test that a key is present with a two-value assignment:
elem, ok = m[key]
Enter fullscreen mode Exit fullscreen mode
  • If key is in m, ok is true. If not, ok is false.
  • If key is not in the map, then elem is the zero value for the map's element type.
package main

import "fmt"

func main() {
    m := make(map[string]int)

    m["Answer"] = 177
    fmt.Println("The value:", m["Answer"])

    m["Answer"] = 146
    fmt.Println("The value:", m["Answer"])

    delete(m, "Answer")
    fmt.Println("The value:", m["Answer"])

    v, ok := m["Answer"]
    fmt.Println("The value:", v, "Present?", ok)
}
Enter fullscreen mode Exit fullscreen mode

Output:

The value: 177
The value: 146
The value: 0
The value: 0 Present? false
Enter fullscreen mode Exit fullscreen mode

Function Values

  • Functions are values too. They can be passed around just like other values.
  • Function values may be used as function arguments and return values.
package main

import (
    "fmt"
    "math"
)

func compute(fn func(float64, float64) float64) float64 {
    return fn(3, 4)
}

func main() {
    hypot := func(x, y float64) float64 {
        return math.Sqrt(x*x + y*y)
    }
    fmt.Println(hypot(5, 12)) // sqrt(5^2 + 12^2) = sqrt(144 + 25) = 13

    fmt.Println(compute(hypot)) // sqrt(3^2 + 12^2) = sqrt(16+9) = 5

    fmt.Println(compute(math.Pow)) // pow(3,4) = 3^4 = 81
}
Enter fullscreen mode Exit fullscreen mode

Output:

13
5
81
Enter fullscreen mode Exit fullscreen mode
Thank you for diving into this chapter of the blog! We've covered a lot of ground, but the journey doesn't end here. The next chapter awaits, ready to take you further into the depths of our topic.
To continue reading and explore the next chapter, simply follow this link: Link To Next Chapter

"Unlock the enigmatic world of pointers, structs, arrays, slices, and maps – where syntax humorously baffles and amuses!✨👀"

Top comments (2)

Collapse
 
utsavdesai26 profile image
Utsav Desai

Excitedly anticipating Part 2! The lighthearted approach to explaining Go's syntax in terms of pointers, structs, arrays, and more adds a fun twist to learning. Looking forward to delving deeper into the world of Go programming.

Collapse
 
bhavypatel45 profile image
Bhavypatel45

This blog post on Go's syntax is both informative and entertaining! I really enjoyed the sarcastic journey through pointers, structs, arrays, slices, and maps. The article did a great job of breaking down these concepts in a way that is easy to understand, even for beginners. I appreciated the humorous tone, which made the learning experience more enjoyable. The explanations and examples provided helped clarify the syntax and usage of these important elements in Go. Thank you for making this topic engaging and accessible. I look forward to reading more content like this!