Pointers in Golang

Pointers

Pointers in the Go programming language, often called Golang, are special variables that store the memory address of other variables. They are particularly significant because they allow direct access to memory and facilitate efficient data manipulation. In Golang, memory addresses are represented in hexadecimal format (e.g., starting with 0x, like 0xAB1234).

Why Are Pointers Necessary?

To appreciate the necessity of pointers, it is essential to first understand the concept of variables. Variables are named storage locations in memory where data is kept. Accessing the stored data requires knowledge of its memory address. However, manually managing memory addresses in hexadecimal format is cumbersome and error-prone. Variables help simplify this process by allowing access to data through meaningful names rather than memory addresses.

In Golang, it is possible to assign hexadecimal values to variables using literal expressions. These hexadecimal numbers may represent memory addresses but are not inherently pointers unless they are associated with the address of another variable.

Example: Storing Hexadecimal Values

The following example demonstrates how hexadecimal values can be stored in variables. These are not pointers, as they do not refer to the memory address of another variable:

// Golang program to showcase storing hexadecimal values
package main

import "fmt"

func main() {
    // Assigning hexadecimal values to variables
    a := 0x1A
    b := 0x7B

    // Displaying the values
    fmt.Printf("Type of variable a: %T\n", a)
    fmt.Printf("Value of a in hexadecimal: %X\n", a)
    fmt.Printf("Value of a in decimal: %d\n", a)

    fmt.Printf("Type of variable b: %T\n", b)
    fmt.Printf("Value of b in hexadecimal: %X\n", b)
    fmt.Printf("Value of b in decimal: %d\n", b)
}

Output:

Type of variable a: int
Value of a in hexadecimal: 1A
Value of a in decimal: 26
Type of variable b: int
Value of b in hexadecimal: 7B
Value of b in decimal: 123
Understanding Pointers

A pointer is a specialized variable that not only holds the memory address of another variable but also allows accessing or modifying the value at that address. It is declared using the * operator, known as the dereferencing operator, while the & operator is used to retrieve the address of a variable.

Pointer Declaration: A pointer is declared using the following syntax:

var pointerName *DataType

For example, the following code snippet declares a pointer of type int:

Example:

var numPtr *int

Pointer Initialization: To initialize a pointer, assign it the memory address of another variable using the & operator. Here’s an example:

// Golang program to demonstrate pointer initialization
package main

import "fmt"

func main() {
    // Normal variable declaration
    num := 42

    // Pointer declaration and initialization
    var ptr *int = &num

    // Displaying values
    fmt.Println("Value of num:", num)
    fmt.Println("Address of num:", &num)
    fmt.Println("Value stored in ptr:", ptr)
}

Output:

Value of num: 42
Address of num: 0x1040a124
Value stored in ptr: 0x1040a124

Key Points About Pointers

1. Default Value: An uninitialized pointer has a default value of nil.

Example: Nil Pointer

// Golang program to demonstrate nil pointers
package main

import "fmt"

func main() {
    var ptr *int
    fmt.Println("Value of ptr:", ptr)
}

Output:

Value of ptr: <nil>

2. Single-Line Declaration and Initialization: Pointers can be declared and initialized in one line:

ptr := &num

3. Type Restrictions: A pointer’s type determines which variable addresses it can store. For instance, a pointer of type *string can only store addresses of string variables.

4. Type Inference: Using the var keyword, you can declare pointers without explicitly specifying their types. The compiler infers the type based on the variable being referenced.

Example: Type Inference

// Golang program to demonstrate type inference
package main

import "fmt"

func main() {
    num := 100
    ptr := &num

    fmt.Println("Value of num:", num)
    fmt.Println("Address of num:", &num)
    fmt.Println("Value stored in ptr:", ptr)
}

Output:

Value of num: 100
Address of num: 0x1040a124
Value stored in ptr: 0x1040a124

5. Shorthand Syntax: The := operator simplifies pointer declaration and initialization:

Example: Shorthand Syntax

// Golang program to demonstrate shorthand pointer syntax
package main

import "fmt"

func main() {
    num := 75
    ptr := &num

    fmt.Println("Value of num:", num)
    fmt.Println("Address of num:", &num)
    fmt.Println("Value stored in ptr:", ptr)
}

Output:

Value of num: 75
Address of num: 0x1040a124
Value stored in ptr: 0x1040a124
Dereferencing Pointers

The * operator, also called the dereferencing operator, is used to access or modify the value at the address a pointer is pointing to.

Example: Dereferencing a Pointer

// Golang program to demonstrate dereferencing a pointer
package main

import "fmt"

func main() {
    num := 60
    ptr := &num

    fmt.Println("Value of num:", num)
    fmt.Println("Value at address stored in ptr:", *ptr)

    // Modifying the value through the pointer
    *ptr = 90
    fmt.Println("Updated value of num:", num)
}

Output:

Value of num: 60
Value at address stored in ptr: 60
Updated value of num: 90

Passing Pointers to a Function in Go

Pointers in Go programming language (Golang) are variables used to store the memory address of another variable. Similar to variables, you can also pass pointers to functions. There are two primary methods to achieve this, as described below:

1. Define a pointer and pass it to the function
In the program below, a function modifyValue accepts an integer pointer as its parameter. This means the function is designed to accept only pointer-type arguments. The function modifies the value of the variable num. Initially, the value of num is 42. However, after the function call, the value is updated to 1234, as shown in the output.

// Go program to define a pointer
// and pass it to a function
package main

import "fmt"

// Function accepting an integer pointer
func modifyValue(ptr *int) {

    // Changing the value using dereferencing
    *ptr = 1234
}

// Main function
func main() {

    // Defining a normal variable
    var num = 42

    fmt.Printf("Initial value of num: %d\n", num)

    // Declaring a pointer variable and assigning
    // the memory address of num to it
    var numPointer *int = &num

    // Passing the pointer to the function
    modifyValue(numPointer)

    fmt.Printf("Value of num after function call: %d\n", num)
}

Output:

Initial value of num: 42
Value of num after function call: 1234

2. Pass the address of the variable directly to the function
In this example, instead of creating a separate pointer variable to store the memory address of num (as shown in the first method), we directly pass the address of num to the function. This approach produces the same result as the first method.

// Go program to directly pass the address
// of a variable to a function
package main

import "fmt"

// Function accepting an integer pointer
func modifyValue(ptr *int) {

    // Modifying the value through dereferencing
    *ptr = 1234
}

// Main function
func main() {

    // Defining a normal variable
    var num = 42

    fmt.Printf("Initial value of num: %d\n", num)

    // Passing the address of the variable num directly
    modifyValue(&num)

    fmt.Printf("Value of num after function call: %d\n", num)
}

Output:

Initial value of num: 42
Value of num after function call: 1234

Pointer to a Struct in Golang

In Go, structs allow us to define custom data types that group related fields together. By using pointers with structs, we can efficiently manage memory and directly modify the original struct data without unnecessary duplication.

Why Use Pointers with Structs?

Using pointers with structs offers several advantages:

  1. Efficient Memory Management: Large structs can be passed by reference to avoid copying them.
  2. Direct Modification: Pointers allow functions to modify the original struct values.
  3. Performance Optimization: Reducing memory usage can significantly improve application performance.

Example:

package main

import "fmt"

// Defining a struct
type Student struct {
    id   int
    name string
}

func main() {
    // Creating an instance of the struct
    s1 := Student{id: 101, name: "John"}

    fmt.Println("Initial struct:", s1)
}
Declaring a Pointer to a Struct

There are two primary ways to create a pointer to a struct in Go:

  1. Using the & operator to obtain the memory address of an existing struct.
  2. Using the new function, which allocates memory and returns a pointer to the struct.
Using the & Operator

With this method, we use the & operator to obtain the address of an existing struct.

Example:

package main

import "fmt"

// Defining a struct
type Book struct {
    title  string
    author string
}

func main() {
    // Creating an instance of the struct
    b1 := Book{title: "Go Programming", author: "Alice"}

    // Creating a pointer to the struct
    bookPointer := &b1

    // Accessing fields using the pointer
    fmt.Println("Title:", bookPointer.title)  // Go automatically dereferences
    fmt.Println("Author:", bookPointer.author)

    // Modifying struct values using the pointer
    bookPointer.title = "Advanced Go Programming"
    fmt.Println("Updated struct:", b1)
}

Output:

Title: Go Programming
Author: Alice
Updated struct: {Advanced Go Programming Alice}

Here, bookPointer := &b1 assigns the memory address of b1 to bookPointer. Changes to bookPointer.title directly modify the original b1 struct since both reference the same memory location.

Using the new Function

The new function creates a pointer to a newly allocated struct and initializes it with zero values.

Example:

package main

import "fmt"

// Defining a struct
type Car struct {
    model string
    year  int
}

func main() {
    // Creating a pointer to a new instance of Car
    carPointer := new(Car)

    // Setting fields through the pointer
    carPointer.model = "Tesla Model 3"
    carPointer.year = 2023

    fmt.Println("Struct created with new:", *carPointer)
}

Output:

Struct created with new: {Tesla Model 3 2023}
Accessing Struct Fields Through a Pointer

In Go, you don’t need to explicitly dereference a pointer (using *) when accessing struct fields. For example, personPointer.name works directly without requiring (*personPointer).name. This feature simplifies pointer usage and improves code readability.

By understanding and utilizing pointers with structs, you can write Go code that is both memory-efficient and performance-optimized.

Go Pointer to Pointer (Double Pointer)

Pointers in Go Programming Language

Pointers in Go (Golang) are special variables designed to store the memory address of another variable. A pointer can refer to variables of any type, including another pointer, creating a chain of pointers. When we declare a pointer to another pointer, the first pointer holds the address of the second pointer. This concept is known as “Double Pointers.”

Declaring a Pointer to Pointer in Go

To declare a pointer to a pointer, the process is almost identical to declaring a single pointer. However, an additional * is added before the pointer name. This typically applies when defining the pointer variable using the var keyword along with its type. The example below demonstrates this concept more clearly.

Example 1

In the following program, the pointer2 stores the address of pointer1. Dereferencing pointer2 (i.e., *pointer2) retrieves the address of the variable value, which is equivalent to the value of pointer1. Further dereferencing using **pointer2 yields the actual value of the variable value.

// Go program demonstrating Pointer to Pointer
package main

import "fmt"

func main() {
    // Declare a variable of integer type
    var number int = 500

    // Declare a pointer of integer type
    var pointer1 *int = &number

    // Declare a pointer to a pointer and store the address of pointer1
    var pointer2 **int = &pointer1

    fmt.Println("The Value of Variable number is =", number)
    fmt.Println("Memory Address of Variable number is =", &number)

    fmt.Println("Value stored in pointer1 is =", pointer1)
    fmt.Println("Memory Address of pointer1 is =", &pointer1)

    fmt.Println("Value stored in pointer2 is =", pointer2)

    // Dereferencing pointer to pointer
    fmt.Println("Value at the address of pointer2 (*pointer2) is =", *pointer2)

    // Double pointer dereference to access the value of variable number
    fmt.Println("Value at the address referenced by pointer2 (**pointer2) is =", **pointer2)
}

Output:

The Value of Variable number is = 500
Memory Address of Variable number is = 0x4a2d30
Value stored in pointer1 is = 0x4a2d30
Memory Address of pointer1 is = 0x4c3a50
Value stored in pointer2 is = 0x4c3a50
Value at the address of pointer2 (*pointer2) is = 0x4a2d30
Value at the address referenced by pointer2 (**pointer2) is = 500

Example 2

The following example modifies the value of the variable by updating it through the pointers. The value of number is altered by dereferencing pointer1 and pointer2.

// Go program illustrating modification through Pointer to Pointer
package main

import "fmt"

func main() {
    // Declare a variable of integer type
    var data int = 800

    // Declare a pointer of integer type
    var ptr1 *int = &data

    // Declare a pointer to a pointer and store the address of ptr1
    var ptr2 **int = &ptr1

    fmt.Println("Initial Value of Variable data is =", data)

    // Modify the value of data through ptr1
    *ptr1 = 900
    fmt.Println("Value of data after updating via ptr1 =", data)

    // Modify the value of data through ptr2
    **ptr2 = 1000
    fmt.Println("Value of data after updating via ptr2 =", data)
}

Output:

Initial Value of Variable data is = 800
Value of data after updating via ptr1 = 900
Value of data after updating via ptr2 = 1000

Comparing Pointers in Golang

In the Go programming language, pointers are variables used to store the memory address of another variable. Pointers in Golang are also referred to as special variables. Variables are typically used to store data at a specific memory address in the system. This memory address is represented in hexadecimal format (e.g., starting with 0x like 0xFFAAF, etc.).

In Go, you can compare two pointers to determine their equality. Two pointer values are considered equal only if they point to the same memory location or if both are nil. You can compare pointers in Go using the == and != operators.

== Operator: The == operator returns true if both pointers reference the same variable or memory address; otherwise, it returns false.

Syntax:

pointer_1 == pointer_2

Example:

// Go program to demonstrate the
// comparison of two pointers using == operator
package main

import "fmt"

func main() {
    valA := 1234
    valB := 4321

    // Creating and assigning pointers
    var ptr1 *int
    ptr1 = &valA
    ptr2 := &valB
    ptr3 := &valA

    // Comparing pointers using == operator
    result1 := &ptr1 == &ptr2
    fmt.Println("Does ptr1's address equal ptr2's address? :", result1)

    result2 := ptr1 == ptr2
    fmt.Println("Does ptr1 point to the same variable as ptr2? :", result2)

    result3 := ptr1 == ptr3
    fmt.Println("Does ptr1 point to the same variable as ptr3? :", result3)

    result4 := ptr2 == ptr3
    fmt.Println("Does ptr2 point to the same variable as ptr3? :", result4)

    result5 := &ptr3 == &ptr1
    fmt.Println("Does ptr3's address equal ptr1's address? :", result5)
}

Output:

Does ptr1's address equal ptr2's address? : false
Does ptr1 point to the same variable as ptr2? : false
Does ptr1 point to the same variable as ptr3? : true
Does ptr2 point to the same variable as ptr3? : false
Does ptr3's address equal ptr1's address? : false

!= Operator: The != operator returns false if two pointers point to the same variable or memory address, and true if they point to different variables or addresses.

Syntax:

pointer_1 != pointer_2

Example:

// Go program to demonstrate the
// comparison of two pointers using != operator
package main

import "fmt"

func main() {
    valX := 9876
    valY := 6543

    // Creating and assigning pointers
    var ptrA *int
    ptrA = &valX
    ptrB := &valY
    ptrC := &valX

    // Comparing pointers using != operator
    result1 := &ptrA != &ptrB
    fmt.Println("Are ptrA's and ptrB's addresses different? :", result1)

    result2 := ptrA != ptrB
    fmt.Println("Do ptrA and ptrB point to different variables? :", result2)

    result3 := ptrA != ptrC
    fmt.Println("Do ptrA and ptrC point to different variables? :", result3)

    result4 := ptrB != ptrC
    fmt.Println("Do ptrB and ptrC point to different variables? :", result4)

    result5 := &ptrC != &ptrA
    fmt.Println("Are ptrC's and ptrA's addresses different? :", result5)
}

Output:

Are ptrA's and ptrB's addresses different? : true
Do ptrA and ptrB point to different variables? : true
Do ptrA and ptrC point to different variables? : false
Do ptrB and ptrC point to different variables? : true
Are ptrC's and ptrA's addresses different? : true

Output:

Square root of 16: 4.00
Square root of 25: 5.00
Square root of 36: 6.00
Square root of 49: 7.00
Square root of 64: 8.00
Index of substring: 5
Current Unix time: 1737138000

Comparing Pointers in Golang

In the Go programming language, defer statements postpone the execution of a function, method, or anonymous function until the surrounding function completes. In simpler terms, while the arguments of a deferred function or method call are evaluated immediately, the execution itself is deferred until the enclosing function returns. You can define a deferred function, method, or anonymous function by using the defer keyword.

Syntax:

// For a function
defer func func_name(parameter_list Type) return_type {
    // Code
}

// For a method
defer func (receiver Type) method_name(parameter_list) {
    // Code
}

// For an anonymous function
defer func(parameter_list) (return_type) {
    // Code
}()

Key Points:

  1. The Go language allows multiple defer statements in the same program, and they execute in LIFO (Last-In, First-Out) order, as illustrated in Example 2.
  2. The arguments of defer statements are evaluated immediately when the statement is encountered, but the function itself executes only when the surrounding function returns.
  3. Defer statements are commonly used for tasks like closing files, closing channels, or handling program panics gracefully.

Example:

// Go program demonstrating the concept of defer statements
package main

import "fmt"

// Function to calculate the difference
func difference(a1, a2 int) int {
    res := a1 - a2
    fmt.Println("Difference:", res)
    return 0
}

// Function to display a message
func greet() {
    fmt.Println("Welcome to Go programming!")
}

// Main function
func main() {

    // Normal call to difference() function
    difference(100, 40)

    // Deferred call to difference() function
    defer difference(200, 50)

    // Calling greet() function
    greet()
}

Output:

Difference: 60
Welcome to Go programming!
Difference: 150

Methods in Golang

Go methods are similar to functions but with a significant difference: they have a receiver argument that allows access to the receiver’s properties. The receiver can either be a struct type or a non-struct type, but both must be part of the same package. You cannot define methods for types from other packages or for built-in types like int or string, as the compiler will generate an error.

Syntax:

func (receiver_name Type) method_name(parameter_list) (return_type) {
    // Method implementation
}

Example:

package main

import "fmt"

// Defining a struct
type car struct {
    brand string
    year  int
}

// Defining a method with a struct receiver
func (c car) details() {
    fmt.Println("Brand:", c.brand)
    fmt.Println("Year:", c.year)
}

func main() {
    // Creating an instance of the struct
    vehicle := car{brand: "Toyota", year: 2022}

    // Calling the method
    vehicle.details()
}

Output:

Brand: Toyota
Year: 2022
Methods with Struct Type Receiver

When defining a method, the receiver can be a struct type. The receiver is accessible within the method. The earlier example demonstrates this with a struct type receiver.

Methods with Non-Struct Type Receiver

Go supports defining methods with non-struct type receivers, provided the type and the method definition exist in the same package. However, you cannot define methods for types from another package (e.g., int, string).

Example:

package main

import "fmt"

// Creating a custom type based on float64
type measurement float64

// Defining a method with a non-struct receiver
func (m measurement) double() measurement {
    return m * 2
}

func main() {
    value := measurement(3.5)
    result := value.double()

    fmt.Printf("Double of %.1f is %.1f\n", value, result)
}

Output:

Double of 3.5 is 7.0
Methods with Pointer Receiver

In Go, methods can also have pointer receivers, enabling modifications to the original data. This capability is unavailable with value receivers.

Syntax:

func (receiver *Type) method_name(parameters...) return_type {
    // Code to modify data
}

Example:

package main

import "fmt"

// Defining a struct
type animal struct {
    species string
}

// Method with pointer receiver to modify data
func (a *animal) rename(newSpecies string) {
    a.species = newSpecies
}

func main() {
    pet := animal{species: "Cat"}

    fmt.Println("Before:", pet.species)

    // Calling the method to rename
    pet.rename("Dog")

    fmt.Println("After:", pet.species)
}

Output:

Before: Cat
After: Dog
Methods Accepting Both Pointer and Value

In Go, methods can accept both value and pointer receivers. Depending on how the method is invoked, Go automatically handles the conversion between pointers and values.

Example:

package main

import "fmt"

type book struct {
    title string
}

// Method with pointer receiver
func (b *book) setTitle(newTitle string) {
    b.title = newTitle
}

// Method with value receiver
func (b book) displayTitle() {
    fmt.Println("Title:", b.title)
}

func main() {
    novel := book{title: "Untitled"}

    // Calling pointer receiver method with value
    novel.setTitle("1984")
    fmt.Println("After pointer method:", novel.title)

    // Calling value receiver method with pointer
    (&novel).displayTitle()
}

Output:

After pointer method: 1984
Title: 1984

Difference Between Method and Function

AspectMethodFunction
Contains a receiverYesNo
Allows same name with different typesYesNo
Usable as a first-order objectNoYes