Golang: Pointers – Detailed Overview
What pointers are? Pointers examples in Golang. * and & operators. Pointer as an argument to a function. Functions - arguments by value and by reference.
What is a pointer?
Shortly, the pointer is a variable which stores an address of another variable, where some data is stored.
A Pointer Example
Let’s take the simplest example where a pointer is used:
package main
import "fmt"
func main() {
a := 1
b := &a
fmt.Println("A: ", a)
fmt.Println("B: ", b)
fmt.Println("B: ", *b)
}
Here:
- a variable
a
is created with the integer type and value1
- a variable
b
is created with the pointer to the integer type (see below) - and data output:
- first just the
a
value - the
a
value(!) or content of theb
variable - finally, we are getting the value of the
a
, to which theb
is pointed to (will look at the*
and&
operators bit later)расммотрим ниже)
- first just the
Run the code:
$ go run pointers_example.go
A: 1
B: 0xc0000140e8
B: 1
On the second line, we are seeing the memory address, where the b
pointer is pointed.
On the third line – we got value from this memory address.
The pointer could be initialized in a more amply way with types specification instead of using :=
so the code will look like:
...
func main() {
var a int = 1
var b *int = &a
fmt.Println("A: ", a)
fmt.Println("B: ", b)
fmt.Println("B: ", *b)
}
...
In the var b *int = &a
line, we set that the b
variable is a pointer to the integer data.
In the same way, a pointer to the string data could be created just with the *string
instead of the *int
in its data type:
...
var c *string
fmt.Printf("The C var type: %T, default value: %v\n", c, c)
var d string = "This is a string"
c = &d
fmt.Printf("The C var type: %T, default value: %v, string value: %s\n", c, c, *c)
...
Here:
- the
c
variable is created as a pointer to thestring
data - using
Printf()
‘s modifiers thec
variable’s data type (%T
) and its value (%v
) are displayed - the
d
variable is created with thestring
data type and “This is a string
” value - for the
c
variable, the memory address of thed
variable is set - using
Printf()
‘s modifiers, thec
variable’s data type (%T
), its value (%v
), and the value from the memory address which is stored in thec
Run:
$ go run pointers_example.go
The C var type: *string, default value: <nil>
The C var type: *string, default value: 0xc0000101e0, string value: This is a string
* and & operators
We already used them in examples above but let’s take a closer look.
The *
operator is a dereference operator.
The dereference here means that we are getting a value not of a pointer (which stores an address) but from the memory address where this pointer is… Well – pointed to
Let's go back to our previous example:
...
fmt.Printf("The C var type: %T, default value: %v, string value: %s\n", c, c, *c)
...
Here:
default value: %v
with theс
– displays a value which is stored in thec
variable – a memory address, wherec
is pointed tostring value: %s
with the*с
– displays a value which is got after calling the memory from thec
variable
The &
operator returns a variable’s memory address.
For example, let’s add to our previous example one more line and lets display addresses using Printf()
with %p
modifier:
...
var c *string
fmt.Printf("The C var type: %T, default value: %v\n", c, c)
var d string = "This is a string"
c = &d
fmt.Printf("The C var type: %T, default value: %v, string value: %s\n", c, c, *c)
fmt.Printf("The D adress: %p\nThe C address: %p\nThe C value: %v\n", &d, &c, c)
...
Check:
$ go run pointers_example.go
The C var type: *string, default value: <nil>
The C var type: *string, default value: 0xc0000101e0, string value: This is a string
The D adress: 0xc0000101e0
The C address: 0xc00000e028
The C value: 0xc0000101e0
Here we got 0xc0000101e0
value as the d
variable address, 0xc00000e028
as the address of the c
variable, but the c
itself stores address of the d
variable – 0xc0000101e0
.
Actually, a data initialization in the c
pointer variable is done by getting the address of the d
variable:
...
c = &d
...
The new() Function
To define and initialize a pointer using the var pointername *type
notation – you can use the builtin new()
Go
function which accepts a data type as the first argument and will return a pointer to a memory allocated for a variable’s data:
...
a := 1
b := new(int)
fmt.Printf("A: %d, B: %v, %v\n", a, b, *b)
b = &a
fmt.Printf("A: %d, B: %v, %v\n", a, b, *b)
...
Here:
- the
a
variable is created with value1
- the
b
variable is created which will hold a pointer to the memory returned by thenew()
function and which keeps0
for now, as already allocated memory can’t holdnil
- the
b
‘s value is updated with thea
‘s address
Check:
$ go run pointers_example.go
A: 1, B: 0xc000014100, 0
A: 1, B: 0xc0000140e8, 1
Changing a Pointer’s Value
Well, this is not correct to say “changing a pointer’s value” as a pointer variable stores a memory address – not a value itself.
But using a pointer, we can change this value in a memory location to which this pointer is pointed to.
For example:
...
a := 1
b := &a
fmt.Println("A: ", a)
fmt.Println("B: ", *b)
*b = 2
fmt.Println("B: ", *b)
...
Here:
- the
a
variable has value1
- the
b
variable has thea
‘s address - the
a
variable value displayed - the value displayed take from the address where the
b
is pointed to - we are changing the value in this memory to the
2
- and displays the new value
Run the code:
$ go run pointers_example.go
A: 1
B: 1
B: 2
Passing a Pointer as a Function’s Argument
You can pass a pointer to a function as its argument.
For example:
package main
import "fmt"
func setVal(b *int) {
*b = 2
}
func main() {
a := 1
b := &a
fmt.Println("Init values")
fmt.Println("A: ", a)
fmt.Println("B: ", *b)
setVal(b)
fmt.Println("Changed values")
fmt.Println("A: ", a)
fmt.Println("B: ", *b)
}
Here, we are creating the a
setVal()
function which accepts an integer pointer as an argument and will change a value in the address passed with this pointer.
Check it:
$ go run pointers_example.go
Init values
A: 1
B: 1
Changed values
A: 2
B: 2
After calling the setVal()
– the a
and b
will display a new value.
Even more – we could pass just an address of the a
‘s variable to the setVal()
:
...
func setVal(b *int) {
*b = 2
}
func main() {
a := 1
fmt.Println("Init values")
fmt.Println("A: ", a)
setVal(&a)
fmt.Println("Changed values")
fmt.Println("A: ", a)
}
The result is:
$ go run pointers_example.go
Init values
A: 1
Changed values
A: 2
Functions: Passing Arguments by Value and by Reference
A bit offtopic here, but using the example above the difference between passing argument by value and argument by reference also can be displayed.
Let’s update this example:
...
func setVal(b *int, c int) {
*b = 2
c = 4
fmt.Printf("B from setVal(). Poiner to: %p, val: %v\n", b, *b)
fmt.Printf("C from setVal(). Addr: %p, val: %v\n", &c, c)
}
func main() {
a := 1
b := &a
c := 3
fmt.Println("Init values")
fmt.Printf("A from main(). Addr: %p, val: %v\n", &a, a)
fmt.Printf("B from main(). Poiner to: %p, val: %v\n", b, *b)
fmt.Printf("C from main(). Addr: %p, val: %v\n", &c, c)
fmt.Println("Changed values")
setVal(b, c)
fmt.Printf("A from main(). Addr: %p, val: %v\n", &a, a)
fmt.Printf("B from main(). Poiner to: %p, val: %v\n", b, *b)
fmt.Printf("C from main(). Addr: %p, val: %v\n", &c, c)
}
Here:
- get the
a
variable address and its value - get the address which is stored in the
b
variable and then the data which is stored on this address - get the
c
variable address and its value - call the
setVal()
function and pass thea
‘s value by the reference in theb
and thec
– as a value - in the
setVal()
getting the address which is stored in theb
variable and the value which is stored there - in the
setVal()
getting thec
variable address and the value which is stored in this memory area - in the
main()
getting thea
‘s address and the value stored there - in the
main()
getting the address which is stored in theb
variable and value from there - in the
main()
getting thec
variable address and the value which is stored there
Run it:
$ go run pointers_example.go
Init values
A from main(). Addr: 0xc0000140e8, val: 1
B from main(). Poiner to: 0xc0000140e8, val: 1
C from main(). Addr: 0xc000014100, val: 3
Changed values
B from setVal(). Poiner to: 0xc0000140e8, val: 2
C from setVal(). Addr: 0xc000014130, val: 4
A from main(). Addr: 0xc0000140e8, val: 2
B from main(). Poiner to: 0xc0000140e8, val: 2
C from main(). Addr: 0xc000014100, val: 3
Here:
- the
a
is stored in the0xc0000140e8
location and has value1
- the
b
is pointed to the same location0xc0000140e8
and returns the same1
value - the
c
is stored in the0xc000014100
location with the3
value - the
setVal()
is called - the
b
in thesetVal()
still pointed to the0xc0000140e8
location with the2
as its value - the
c
in thesetVal()
got its new address0xc000014130
where the4
is stored - the
a
in themain()
now keeps the2
value from the same0xc0000140e8
location - the
b
in themain()
still the same as it is in thesetVal()
– points to the same location and returns the same value - in the
main()
for thec
variable, nothing was changed as thec
in thesetVal()
has own address0xc000014130
, but thec
in themain()
uses the0xc000014100
location
Actually, that’s all you need to know to better understand what pointers are and how to use them.