Pointers are one of the most powerful idea in programming, but this is also one of the most complicated structure as well. This is very useful for efficiently manipulating memory values.
Overview
In this lesson, we discuss pointers which are used to reference the memory address of a variable or data structure. When a variable is declared, a certain amount of memory is allocated based on the variable data type during the program execution. So, every time we execute the code, we may get different memory location. Pointer is something that holds the memory address of a variable. They do not only hold memory addresses but also point to where the memory is allocated and allows us to modify the value stored in that memory location. The same thing happens for functions as well, they are stored in memory. So, we can referene those functions and use them using pointers.
In Go, everything other than slice and map are value types. If you’re passing around a variable to a function, the compiler will create a local variable for that function. That means it will actually copy the contents of the original variable into a new variable and pass that to the function as a local variable. If we are passing a large struct, it may take some time and memory copying these values into a new struct. Instead if we pass these variable as a pointer, it will simply copy the memory address which will be smaller and lot more efficient for your application.
There are two main operators when working with pointers.
- address of operator (
&) gives the memory address of a variable. To use this, we precede&before the name of the variable. - derefernce operator (
*) is used to get the value at a given memory address. This operator is preceded before the memory address.
Declaring and Initializing a Pointer
The syntax for declaring a pointer variable looks like this.
1var <pointer_name> *<data_type>
In the code below, we declare pointer variable for various data types.
1var ptr_id *int
2var ptr_name *string
Uninitialized pointer get default value of nil. This means that this pointer points to nothing.
Example:
1package main
2
3import "fmt"
4
5func main() {
6 var a *int
7 var str *string
8 fmt.Println(a) // <nil>
9 fmt.Println(str) // <nil>
10}
Output:
<nil>
<nil>
For assigning values to pointer variable, we use the address of operator.
1var ptr_id *int
2var ptr_name *string
3var id int
4var address string
5ptr_id = &id
6ptr_name = &address
Once we have assigned pointer variable, we can use dereference operator to extract value from that pointer.
Example:
1package main
2
3import "fmt"
4
5func main() {
6 var a int = 50
7 var a_ptr = &a
8 fmt.Printf("%T %v \n", a_ptr, &a)
9 fmt.Printf("%T %v\n", *(&a), *(a_ptr))
10}
This code gives a random memory address in the first print statement. We can see the type of pointer as *int. Here, we used a_ptr as a variable and we used &a and assigned the address of a to this variable. This variable a_ptr is assigned to be of type *int using dynamic typing.
Output:
*int 0xc000016058
int 50
Note: If we try to dereference a pointer that has value
nil, it will give an error. To avoid this it’s always best to verify if the pointer has been assigned anything using conditional check.
1if a_ptr != nil {
2 // do something with a_ptr
3}
We can also create a pointer variable using built-in function new. This sets the pointer to zero value. So, dereferencing this pointer will give zero value for respective data type.
1package main
2
3import "fmt"
4
5func main() {
6 new_ptr := new(int) // sets to zero value
7 fmt.Println("new_ptr", *new_ptr)
8}
Output:
new_ptr 0
Modify Variable value using Pointer
Pointers are actually reference to memory and if we can create another pointer by dereferencing the same variable, we get the same memory address as you can see in the below example. Also note that if we modify the value at a given address, all pointers referencing that memory address will have new value after the change. This modification using dereference operator is a powerful concept.
Example:
1package main
2
3import "fmt"
4
5func main() {
6 greeting := "Hello"
7 var ptr *string
8 ptr = &greeting
9 fmt.Println(ptr, *ptr)
10
11 var ptr2 *string = &greeting
12 fmt.Println(ptr2, *ptr2)
13
14 ptr3 := &greeting
15 fmt.Println(ptr3, *ptr3)
16 *ptr3 = "Hi"
17 fmt.Println(greeting, *ptr, *ptr2, *ptr3)
18}
In your case, the output below might refer to different memory address.
Output:
0xc000014280 Hello
0xc000014280 Hello
0xc000014280 Hello
Hi Hi Hi Hi
As you can see in the output above, all pointers refer to same memory address and when we modified the value in that address, it is reflected in all pointers as well as original variable greeting.
Argument - Pass by Value
Now that we are aware of pointers, we can understand argument passing by value. In this case, the parameters are copied into another local variable and different location of the memory. That copy is accessible only within the scope of the function and when we modify that variable, it will modify only the copy and will not change the original variable we passed when calling the function.
Example:
1package main
2
3import "fmt"
4
5func main() {
6 number := 10
7 fmt.Println("number =", number)
8 increment(number)
9 fmt.Println("number =", number) // still remains the same
10 input := "hello"
11 fmt.Println("input =", input)
12 concatenate(input)
13 fmt.Println("input =", input)
14}
15
16func increment(number int) {
17 number += 1 // this modifies the copy of parameter
18 fmt.Println("Inside increment function, input:", number)
19}
20
21func concatenate(input string) {
22 input += " there"
23 fmt.Println("Inside concatenate function, input:", input)
24}
As we can see from the output below, inside the function increment the value of number variable was changed to 3, but outside that function, it still has the same value of 2. This is pass by value where a copy of number variable is created inside the increment function.
Output:
number = 10
Inside increment function, input: 11
number = 10
input = hello
Inside concatenate function, input: hello there
input = hello
Argument - Pass by Reference
On the other hand, when a function parameter is passed by reference, the actual memory address is passed into the function call as a pointer. So, if the function modifies the value of this variable, the value in the calling function also changes.
Example:
1package main
2
3import "fmt"
4
5func main() {
6 input := "hello"
7 fmt.Println("input =", input)
8 concatenate_ptr(&input)
9 fmt.Println("input =", input)
10}
11
12func concatenate_ptr(input *string) {
13 *input += " there"
14}
In this case, we are passing input variable using their memory address (pointer). This makes it pass by reference. So, when we modify the variable inside the function, it modifies the original value.
Output:
input = hello
input = hello there
Slices, maps and other complex data types are passed by reference. So, if we modify the slice in the function, it will modify the original slice as well as the underlying original array.
Example:
1package main
2
3import "fmt"
4
5func main() {
6 arr := [...]int{1, 2, 3, 4}
7 fmt.Println("arr =", arr)
8 slice := arr[:3]
9 fmt.Println("slice =", slice)
10 update_slice(slice)
11 fmt.Println("slice =", slice)
12 fmt.Println("arr =", arr)
13}
14
15func update_slice(slice []int) {
16 slice[1] = 200
17}
Output:
arr = [1 2 3 4]
slice = [1 2 3]
slice = [1 200 3]
arr = [1 200 3 4]
Maps also, if we modify inside the function, they get updated outside the function scope.
Example:
1package main
2
3import "fmt"
4
5func main() {
6 language := make(map[string]string)
7 language["USA"] = "English"
8 language["Spain"] = "Spanish"
9 language["France"] = "French"
10 language["Canada"] = "English"
11 fmt.Println(language)
12 update_map(language) // update Canada key
13 fmt.Println(language)
14}
15
16func update_map(m map[string]string) {
17 m["Canada"] = "French"
18}
Output:
map[Canada:English France:French Spain:Spanish USA:English]
map[Canada:French France:French Spain:Spanish USA:English]
Conclusion
In this lesson, we saw how we can use pointers to modify the values of a variable or any memory address. This also explains how we can pass variables by value or by reference. Slice, Maps and other complex data types are, by default, pass by reference.


Comments