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
- The & operator generates a pointer to its operand.
i := 177
p = &i
- 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
- 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
}
Output:
146
177
36
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 justp.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)
}
Output:
{1 2}
4 2
{3 2}
{1 2} {1 0} {0 0} &{1 2}
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)
}
Output:
Hello World
[Hello World]
[2 3 5 7 11 13]
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]
}
Output:
[2 3 5 7 11 13 17 19 23]
3
9
[3 5 7 11]
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)
}
Output:
[Ram Lakshman Parshuram Samvad]
[Ram Lakshman] [Lakshman Parshuram]
[Ram Krishna] [Krishna Parshuram]
[Ram Krishna Parshuram Samvad]
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)
}
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}]
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
these slice expressions are equivalent:
a[0:10]
a[:10]
a[0:]
a[:]
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)
}
Output:
[3 5 7]
[3 5]
[5]
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)
andcap(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)
}
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]
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!")
}
}
Output:
[] 0 0
nil!
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
- 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
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)
}
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]
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], " "))
}
}
Output:
X _ X
O _ X
_ _ O
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)
}
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]
- 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
- If you only want the index, you can omit the second variable.
for i := range pow
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)
}
}
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
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)
}
Output:
{40.68433 -74.39967}
map[Bell Labs:{40.68433 -74.39967} Google:{37.42202 -122.08408}]
Mutating Map
- Insert or update an element in map m:
m[key] = elem
- Retrieve an element:
elem = m[key]
- Delete an element:
delete(m, key)
- Test that a key is present with a two-value assignment:
elem, ok = m[key]
- If
key
is inm
,ok
istrue
. If not,ok
isfalse
. - If
key
is not in the map, thenelem
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)
}
Output:
The value: 177
The value: 146
The value: 0
The value: 0 Present? false
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
}
Output:
13
5
81
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)
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.
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!