I think you got confused with pointer object (e.g. pa
, with pointer &a
) and the format used to printout the data. There are no differences. Consider the amended codes with struct
and tidy
and uniformed printout.
package main
import "fmt"
func main() {
a := 1
pa := &a
b := []int{1, 2, 3}
pb := &b
c := struct{
id int
}{
id: 2,
}
pc := &c
fmt.Printf("Name:a | Type:%-20T | Pointer:%-20p | Value:%v\n", a, &a, a)
fmt.Printf("Name:pa | Type:%-20T | Pointer:%-20p | Value:%p\n", pa, &pa, pa)
fmt.Printf("Name:b | Type:%-20T | Pointer:%-20p | Value:%v\n", b, &b, b)
fmt.Printf("Name:pb | Type:%-20T | Pointer:%-20p | Value:%p\n", pb, &pb, pb)
fmt.Printf("Name:c | Type:%-20T | Pointer:%-20p | Value:%v\n", c, &c, c)
fmt.Printf("Name:pc | Type:%-20T | Pointer:%-20p | Value:%p\n", pc, &pc, pc)
}
This will output:
Name:a | Type:int | Pointer:0x414020 | Value:1
Name:pa | Type:*int | Pointer:0x40c138 | Value:0x414020
Name:b | Type:[]int | Pointer:0x40a0e0 | Value:[1 2 3]
Name:pb | Type:*[]int | Pointer:0x40c140 | Value:0x40a0e0
Name:c | Type:struct { id int } | Pointer:0x414030 | Value:{2}
Name:pc | Type:*struct { id int } | Pointer:0x40c148 | Value:0x414030
Program exited.
Basic Object and Memory
Whenever you create a variable, you literally created an object of type allocated on memory. Example:
a
is type int
, an integer object, with memory pointing at 0x414020
, holding value 1
.
pa
is type int*
, an integer pointer object, with memory pointing at 0x40c138
, holding value of 0x414020
which is pointer of a
.
Obtaining an Object’s Pointer
There is only one way to obtain any established object: using the &
prefix symbol. So, to obtain the memory pointer of:
a
, you use &a
.
pa
, you use &pa
.
Printing Pointer
The correct format of printing pointer is always and strictly %p
. This includes printing object pointer’s value that holds pointer of another object (e.g. pa
with %p
instead of the debugging version %v
).
NOTE: In the example above, I style it to always use up 20 columns for pretty printing the output so it was transforms from %p
to %-20p
.
Why do we Use Pointers
So that we manage data at one memory location and also keeping function as function. By default, if the parameter is not a pointer (pass by pointer), the function
actually clones the variable instead (pass by value). Here is an animation about the pointer:
Notice that fillCup
is now behaving exactly like a function, altering the dataset we gave to it.
More read up: Pass by reference vs pass by value
Slice & Struct
They are still treated as an objects just like others. However, one thing about slice is that slice itself is a “container” of pointers pointing to a bunch of memories location that formulates its “dynamic arrays” capabilities (See Slice Internal: Go Slices: usage and internals - The Go Programming Language). Hence, operating on slice is like operating onto a list of pointers by its own nature.
Hence, if you track the history of slice, there was a (very long) discussion about whether to use slice pointer or slice as function parameters. That’s another long story.