The pointer is a concept of storing the memory address of a value rather than storing an actual value for the efficient use of memory. It's a powerful feature of programming for efficient uses of memory and Go inherited this feature from C. Pointer allows to pass the address of the values rather than actual values to interact between goroutines.
The
* (asterisk) Operator is a dereferencing operator and is used to declare the pointer variable and access the data value stored in the memory address.
Address operator&(
ampersand) operator is used to return the address of the variable or access the address of a variable pointed by the pointer in Golang.
The pointer will be declared by prefixing the dereferencing operator (*) to a data type and the address operator(&) will be used to get the address of the actual data to initialize the pointer.
Example:
var blogTitle * string // Declared the pointer for a string type.
var title = "Learn Programming with Sharad"
fblogTitle = &name.
In the above example: the variable 'blogTitle' was declared as a string pointer. That means the pointer is pointing to any address that will be storing the string value.
And, in the last line, the pointer is initialized, or the address value of the string variable title is assigned.
By assigning the address of the variable 'title', the pointer variable 'blogTitle' only stores the memory address of the string content "Learn Programming with Sharad" rather than storing the actual string value.
Default pointer value
Suppose the pointer is not assigned to any variable's address. In that case, it will have a default 'nil' memory address i.e. it is not actually pointing to any of the data addresses.
Example:
package main
import (
"fmt"
)
func main() {
fmt.Println("Pointer examples")
var blogTitle *string // declare the pointer for a string type.
fmt.Println("Printing variable blogTitle: ", blogTitle)
fmt.Println("Printing variable blogTitle's memory address: ", &blogTitle)
// declare and assign string values to a variable title
var title = "Learn programming with Sharad"
fmt.Println("Printing variable title's value: ", title)
fmt.Println("Printing variable title's memory address: ", &title)
// assign the memory address of the title to the blogTitle pointer variable
blogTitle = &title
fmt.Println("Printing variable blogTitle's value: ", blogTitle)
fmt.Println("Printing variable blogTitle pointing to data value: ", *blogTitle)
}
In Go, the data type of a pointer variable can either be explicitly defined or inferred by assigning the address of another variable, making the pointer variable's data type implicit.
Example: Define pointer with variable type
var birthYear *int // Only integer types of data address can be assigned to the variable test.
var year = 2021
birthYear = &year
fmt.Println("Printing variable birthYear pointing to data value: ", *birthYear)
In this example, we have declared the pointer explicitly to hold the address of the integer variable only. If we try to assign the address of a non-int type variable then it will throw the compilation error.
Example: initialize the variable with memory address assignment
currentYear := &year //Here the compiler determines that the currentYear as a pointer variable because we are ssigning the address of another variable.
fmt.Println("Printing variable currentYear's value: ", currentYear)
fmt.Println("Printing variable currentYear memory address: ", ¤tYear)
fmt.Println("Printing variable currentYear's value pointing the actual data: ", *currentYear)
In this example, the currentYear variable will be, by default, a pointer variable with the address of integer type variable 'year'.
We can assign a pointer variable to another pointer variable as well, in that case, we need to use a double dereferencing operator to get the actual data values.
Example: pointer holding another pointer currentYear := &birthYear //Here the compiler determines that the currentYear as a pointer variable because we are ssigning the address of another variable.
fmt.Println("Printing variable currentYear's value pointing the actual data: ", **currentYear)
Different use cases of pointers:
Pointer to Functions:
In Golang, we can create a function variable that stores the memory address of a function. The function pointer can be used to pass the function as an argument to other functions as a callback function.
Example: Add a file named callbackfunction.go under the pointers folder.
package main
// Define a function type
type CountLenght func(string) int
// Define functions that match the function type signature
func NameLenght(name string) int {
return len(name)
}
func AddressLenght(address string) int {
return len(address)
}
func DateLenght(date string) int {
return len(date)
}
Declare a variable of the callback function 'Countlenght' and call different functions sung the pointer.
// Call back function
// Declare a function pointer variable
var countLength CountLenght
// Assign function addresses to the function pointer variable
countLength = NameLenght
fmt.Println("The Length of the name:", countLength("LearnProgramming")) // Call the function 'NameLenght' using the pointer variable
countLength = AddressLenght
fmt.Println("The Length of the Address:", countLength("123 Main St")) // Call the function 'AddressLenght' using the pointer variable
countLength = DateLenght
fmt.Println("The Length of the Date:", countLength("2024-01-01")) // Call the function 'DateLenght' using the pointer variable
Pointer Parameter to functionThe Golang function can accept the pointer as a parameter. Rather than passing the actual value, the pointer parameter will be more efficient in memory consumption because it avoids duplicating the data and allows the functions to access the value of the parameter directly.
Example: Add the below function into the name_formater.go file under string_formater module:
// function to update the created date
func UpdateCreatedDate(person *Person, updatedDate time.Time) {
if !updatedDate.IsZero() {
person.CreatedDate = updatedDate
}
}
This function accepts the struct pointer as a parameter and modifies the struct filed CreatedDate with the new value.
Pointers to Structs:
Structructs pointers are efficient to work with its fields. Rather than copying the entire 'struct' into different memory, using a pointer will be highly efficient and fast.
Example: In the example above(Pointer Parameter to Function) the function parameter is a pointer to struct Person. Using the struct pointer, the function has access to all the fields of the struct Person to manipulate the data in it.
Method with Pointer receiver
In Golang a method can operate with a pointer to a struct. The truck pointer allows the method to easily work/manipulate the struct fields as required.
Example: Address struct pointer is a receiver for the struct Format.
// Address struct
type Address struct {
HouseNumber string
StreetName string
City string
State string
ZipCode string
}
func (a *Address) Format() string {
return a.HouseNumber + " " + a.StreetName + ", " + a.City + ", " + a.State + " " + a.ZipCode
}
Pointer to Pointer:
Golang supports the double-pointer as well. The double pointer is nothing but a pointer variable that stores the address of another pointer variable. The double pointer allows multi-level memory uses and can be beneficial for dynamic memory allocation, passing pointers by reference, and pointer chaining.
Operators with a Pointer:
Other than using simple pointer-to-pointer stored data comparations(i.e. '==', '!='), Go doesn't allow using other operations like arithmetic, boolean, and logical operators without dereferencing the pointer variable.
Memory Management with Pointers
We can allocate and deallocate(garbage collector deallocate) memory using the pointers dynamically for memory management. The dynamic allocation and deallocation of memory gives flexibility to the programmer to better manage the memory while dealing with large data operations.
Memory Allocation
The built-in function New(type) will allocate the memory in Golang. The function allocates the memory required to store 'type' data and provides a pointer reference to use that allocated memory.
Memory Deallocation
The memory deallocation in Golang is handled by the garbage collector automatically. When the allocated memory variable is no longer in use, the memory is eligible for the garbage collector to be freed or collected/reclaimed.
There is no direct way to deallocate memory held by the pointer. Still, we can set the pointer variable to nil to make the memory held by the pointer unreachable so that the garbage collector can reclaim the memory. This approach can be useful for memory management, especially when intending to reuse the pointer variable later in the program.
Best practice for pointer uses
Use with struct:
Pointers are efficient and easily manageable only if we use them in the right place. Normally, it is beneficial if we use large datasets i.e. struct, UDT(user-defined type), etc. Struct pointers allow access to its field which enables the program to work on the struct without copying into a new memory location.
Large and shared Data:
With the use of proper channels, the use of pointers with large shared data will be more beneficial.
Avoid using pointers on:
- Basic data types like int, float, etc because Golang by default uses pass-by values for those types.
- Avoid using reference type in go i.e. slices and maps.
- Avoid using on pointer as a function argument if the function is not actually modifying the argument values.
- Using pointers in concurrent execution can lead to the race condition.
- Don't use a pointer if the data is immutable, it is unnecessary to use the pointer if we can't modify the data.
Reference
https://go.dev/tour/methods/4
https://www.geeksforgeeks.org/pointers-in-golang/
No comments:
Post a Comment