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 value 1
- 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 the b
variable - finally, we are getting the value of the
a
, to which the b
is pointed to (will look at the *
and &
operators bit later)расммотрим ниже)
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 the string
data - using
Printf()
‘s modifiers the c
variable’s data type (%T
) and its value (%v
) are displayed - the
d
variable is created with the string
data type and “This is a string
” value - for the
c
variable, the memory address of the d
variable is set - using
Printf()
‘s modifiers, the c
variable’s data type (%T
), its value (%v
), and the value from the memory address which is stored in the c
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 the c
variable – a memory address, where c
is pointed to string value: %s
with the *с
– displays a value which is got after calling the memory from the c
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 value 1
- the
b
variable is created which will hold a pointer to the memory returned by the new()
function and which keeps 0
for now, as already allocated memory can’t hold nil
- the
b
‘s value is updated with the a
‘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 value 1
- the
b
variable has the a
‘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 the a
‘s value by the reference in the b
and the c
– as a value - in the
setVal()
getting the address which is stored in the b
variable and the value which is stored there - in the
setVal()
getting the c
variable address and the value which is stored in this memory area - in the
main()
getting the a
‘s address and the value stored there - in the
main()
getting the address which is stored in the b
variable and value from there - in the
main()
getting the c
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 the 0xc0000140e8
location and has value 1
- the
b
is pointed to the same location 0xc0000140e8
and returns the same 1
value - the
c
is stored in the 0xc000014100
location with the 3
value - the
setVal()
is called - the
b
in the setVal()
still pointed to the 0xc0000140e8
location with the 2
as its value - the
c
in the setVal()
got its new address 0xc000014130
where the 4
is stored - the
a
in the main()
now keeps the 2
value from the same 0xc0000140e8
location - the
b
in the main()
still the same as it is in the setVal()
– points to the same location and returns the same value - in the
main()
for the c
variable, nothing was changed as the c
in the setVal()
has own address 0xc000014130
, but the c
in the main()
uses the 0xc000014100
location
Actually, that’s all you need to know to better understand what pointers are and how to use them.
Similar Posts
DevOps, cloud and infrastructure engineer. Love Linux, OpenSource, and AWS.