Pointers
A pointer stores the memory address of a value located in computer memory.
Therefore, we can retrieve and update a value through its memory address.
Operators
* & (ampersand) symbol finds the address of the variable.
* *Type denotes a pointer type (A pointer to a 'Type' value).
You only can store the memory address of the value which type is the same type
* * indirection operator symbol finds the value of pointed memory address.
Experiment
1. Initial:
var p *byte
var counter byte = 100
Address:
byte
counter
↓
100
| ___ | ___ | ___ | ___ | ___ | ___ | ___ | ___ |
0X 1 2 3 4 5 6 7 8
2. Get the memory address of the counter variable and assign to pointer variable:
var p *byte
var counter byte = 100
p = &counter
Address:
byte *byte
counter p
↓ ↓
100 3
| ___ | ___ | ___ | ___ | ___ | ___ | ___ | ___ |
0x 1 2 3 4 5 6 7 8
☝
| |
|___________|
NOTE:
* '&' operator will get the memory address 0x3 of the counter variable
* Go will allocate memory address 0x5 to the pointer variable
* Assign 0x3 to the p pointer variable
3. Create a new variable to store the value of the pointed memory address:
var p *byte
var counter byte = 100
p = &counter
v = *p
Address:
byte *byte byte
counter p v
↓ ↓ ↓
100 3 100
| ___ | ___ | ___ | ___ | ___ | ___ | ___ | ___ |
0x 1 2 3 4 5 6 7 8
☝
| |
|___________|
NOTE:
* Go will allocate memory address 0x7 to the v variable.
* '*' indirection operator will find the value of the pointed memory address 0x3 which is stored in pointer variable p.
* 100 will be stored in the memory address 0x7 of v variable.
4. Change the value of variable v to see if variable counter got changed or not:
var p *byte
var counter byte = 100
p = &counter
v =*p
v = 200
Address:
byte *byte byte
counter p v
↓ ↓ ↓
100 3 200
| ___ | ___ | ___ | ___ | ___ | ___ | ___ | ___ |
0x 1 2 3 4 5 6 7 8
☝
| |
|___________|
NOTE:
* counter variable and v variable are independent. (*p only return value only)
5. Change the value of variable counter to see what happened.:
var p *byte
var counter byte = 100
p = &counter
v = *p
v = 200
counter++
v2 = *p
Address:
byte *byte byte byte
counter p v v2
↓ ↓ ↓ ↓
101 3 200 101
| ___ | ___ | ___ | ___ | ___ | ___ | ___ | ___ |
0x 1 2 3 4 5 6 7 8
☝
| |
|___________|
NOTE:
* The value of counter is 101 now
* '*p' will get the value of pointed memory which is stored in the pointer variable p
6. Update the value of the counter variable through *p:
var p *byte
var counter byte = 100
p = &counter
v = *p
v = 200
counter++
v2 = *p
*p = 25
Address:
byte *byte byte byte
counter p v v2
↓ ↓ ↓ ↓
25 3 200 101
| ___ | ___ | ___ | ___ | ___ | ___ | ___ | ___ |
0x 1 2 3 4 5 6 7 8
☝
| |
|___________|
NOTE:
* Using '*p', we can update the value of the counter variable indirectly.
More Examples
1. Empty pointer is nil.
And we can use fmt.Printf and %p verb to show pointer value.
Ex:
var p *int
fmt.Printf("pointer p is %v\n", p)
fmt.Printf("pointer p its address %p\n", p)
Result:
pointer p is <nil>
pointer p its address 0x0
2. Pointer variable has its own memory address.
Pointer variable store the memory address of other variable.
Using indirect operator '*', we can get the value of pointed memory address..
Ex:
var (
counter int
p *int
)
counter = 100
p = &counter
fmt.Printf("counter: value is %d\n", counter)
fmt.Printf("counter: memory address is %p\n", &counter)
fmt.Printf("pointer p: value is %p\n", p)
fmt.Printf("pointer p: memory address is %p\n", &p)
fmt.Printf("pointer p: value of pointed memory address is %d\n", *p)
Result:
counter: value is 100
counter: memory address is 0xc0000120b0
pointer p: value is 0xc0000120b0
pointer p: memory address is 0xc000054020
pointer p: value of pointed memory address is 100
3. Comparing pontoniers will compare the value they pointed.
Ex:
company := "apple"
p1 := &company
p2 := p1
fmt.Printf("(p1 == p2) => %v\n", p1 == p2)
fmt.Printf("p1=> address: %p, pointed address: %p,
pointed value: %s\n", &p1, p1, *p1)
fmt.Printf("p2=> address: %p, pointed address: %p,
pointed value: %s\n", &p2, p2, *p2)
Result:
p1 == p2 => true
p1=> address: 0xc00009c028, pointed address: 0xc000096020,
pointed value: apple
p2=> address: 0xc00009c030, pointed address: 0xc000096020,
pointed value: apple
Work with functions
From the previous topic 'Functions', we have discussed how to confine variables in function to update variables instead of defining variables in global level.
Ex:
func main() {
var counter int
counter = 100
// Use the returned value to update the counter variable
counter = passValue(counter)
fmt.Printf("counter: %d\n", counter)
}
func passValue(counter int) int {
counter++
// Need to return the value
return counter
}
Result:
counter: 101
But after learning pointers, we can adjust the example as shown below.
Ex:
func main() {
var counter int
counter = 100
// Passing memory address of the counter variable
passPointerValue(&counter)
fmt.Printf("counter: %d\n", counter)
}
// The input is the pointer variable
func passPointerValue(counter *int) {
// We can use the indirection operator to update the value of
// the pointed memory address
*counter++
}
Result:
counter: 101
Work with Composite type
1. Array
As arrays are plain values, Go will duplicate the values and pass them to the function variables.
As a result, any changes to the values within the functions will have no effect on the function callers' values.
Consider the following as an example.
Ex:
func main() {
arr := [...]int{1, 2, 3}
fmt.Printf("main -> arr.memory: %p\n", &arr)
fmt.Printf("main -> arr: %v\n", arr)
increaseArrElement(arr)
fmt.Printf("main -> arr: %v\n", arr)
}
func increaseArrElement(arr [3]int) {
for i := range arr {
arr[i] += 1
}
fmt.Printf("func -> arr.memory: %p\n", &arr)
fmt.Printf("func -> arr: %v\n", arr)
}
Result:
main -> arr.memory: 0xc0000180d8
main -> arr: [1 2 3]
func -> arr.memory: 0xc000018120
func -> arr: [2 3 4]
main -> arr: [1 2 3]
Variables within and outside the function have distinct memory addresses.
And the changes will be limited to the copied arr.
We can use pointers to link them.
Ex:
func main() {
arr := [...]int{1, 2, 3}
fmt.Printf("main -> arr.memory: %p\n", &arr)
fmt.Printf("main -> arr: %v\n", arr)
increaseArrElement(&arr)
fmt.Printf("main -> arr: %v\n", arr)
}
func increaseArrElement(arr *[3]int) {
for i := range arr {
arr[i] += 1
}
fmt.Printf("func -> arr.memory: %p\n", arr)
fmt.Printf("func -> arr: %v\n", *arr)
}
Result:
main -> arr.memory: 0xc0000a4000
main -> arr: [1 2 3]
func -> arr.memory: 0xc0000a4000
func -> arr: [2 3 4]
main -> arr: [2 3 4]
For the best practice, try to use Slice rather than Pointers with Array.
2. Slice
Slice carries the slice header, which contains the memory address of the backing array.
Go will copy the slice value and assign it to the function variable.
As a result, even if two variables have distinct memory addresses, if we alter the slice variable within the function, the change will be reflected outside the variable because they belong to the same backing array.
Ex:
func main() {
slice := []string{"h", "e", "l", "l", "o"}
fmt.Printf("main -> slice.memory: %p\n", &slice)
fmt.Printf("main -> slice.header: (%+v)\n",
(*reflect.SliceHeader)(unsafe.Pointer(&slice)))
fmt.Printf("main -> slice: %v\n", slice)
capitalSlice(slice)
fmt.Printf("main -> slice: %v\n", slice)
}
func capitalSlice(slice []string) {
for i, _ := range slice {
slice[i] = strings.ToUpper(slice[i])
}
fmt.Printf("func -> slice.memory: %p\n", &slice)
fmt.Printf("func -> slice.header: (%+v)\n",
(*reflect.SliceHeader)(unsafe.Pointer(&slice)))
fmt.Printf("func -> slice: %v\n", slice)
}
Result:
main -> slice.memory: 0xc0000a4000
main -> slice.header: (&{Data:824634400768 Len:5 Cap:5})
main -> slice: [h e l l o]
func -> slice.memory: 0xc0000a4030
func -> slice.header: (&{Data:824634400768 Len:5 Cap:5})
func -> slice: [H E L L O]
main -> slice: [H E L L O]
But it does not work if we append or remove slice since the Len and Cap are bare values.
Ex:
func main() {
slice := []string{"Hello"}
fmt.Printf("main -> slice.memory: %p\n", &slice)
fmt.Printf("main -> slice.header: (%+v)\n",
(*reflect.SliceHeader)(unsafe.Pointer(&slice)))
fmt.Printf("main -> slice: %v\n", slice)
appendSlice(slice)
fmt.Printf("main -> slice: %v\n", slice)
}
func appendSlice(slice []string) {
slice = append(slice, "Frank")
fmt.Printf("func -> slice.memory: %p\n", &slice)
fmt.Printf("func -> slice.header: (%+v)\n",
(*reflect.SliceHeader)(unsafe.Pointer(&slice)))
fmt.Printf("func -> slice: %v\n", slice)
}
Result:
main -> slice.memory: 0xc0000a4000
main -> slice.header: (&{Data:824634335264 Len:1 Cap:1})
main -> slice: [Hello]
func -> slice.memory: 0xc0000a4030
func -> slice.header: (&{Data:824634499075 Len:2 Cap:2})
func -> slice: [Hello Frank]
main -> slice: [Hello]
If the backing array's maximum capacity is exceeded, Go will assign a new one.
As a result, two variables will connect to distinct backing arrays, and the changes will not be synchronized.
We can use pointers to resolve it, but it is not that common.
Ex:
func main() {
slice := []string{"Hello"}
fmt.Printf("main -> slice.memory: %p\n", &slice)
fmt.Printf("main -> slice.header: (%+v)\n",
(*reflect.SliceHeader)(unsafe.Pointer(&slice)))
fmt.Printf("main -> slice: %v\n", slice)
appendSlice(&slice)
fmt.Printf("main -> slice.memory: %p\n", &slice)
fmt.Printf("main -> slice.header: (%+v)\n",
(*reflect.SliceHeader)(unsafe.Pointer(&slice)))
fmt.Printf("main -> slice: %v\n", slice)
}
func appendSlice(slice *[]string) {
data := *slice
*slice = append(data, "Frank")
fmt.Printf("func -> slice.memory: %p\n", slice)
fmt.Printf("func -> slice.header: (%+v)\n",
(*reflect.SliceHeader)(unsafe.Pointer(slice)))
fmt.Printf("func -> slice: %v\n", *slice)
}
Result:
main -> slice.memory: 0xc000010018
main -> slice.header: (&{Data:824633802864 Len:1 Cap:1})
main -> slice: [Hello]
func -> slice.memory: 0xc000010018
func -> slice.header: (&{Data:824634179616 Len:2 Cap:2})
func -> slice: [Hello Frank]
main -> slice.memory: 0xc000010018
main -> slice.header: (&{Data:824634179616 Len:2 Cap:2})
main -> slice: [Hello Frank]
3. Map
Map value is a pointer.
As a result, the changes will be synchronized because they point to the same memory address.
Ex:
func main() {
dict := map[string]string{
"Hi": "嗨",
"Welcome": "歡迎",
}
fmt.Printf("main -> dict.memory: %p\n", &dict)
fmt.Printf("main -> dict: %v\n", dict)
changeMap(dict)
fmt.Printf("main -> dict: %v\n", dict)
}
func changeMap(dict map[string]string) {
dict["Hi"] = "嗨嗨"
fmt.Printf("func -> dict.memory: %p\n", &dict)
fmt.Printf("func -> dict: %v\n", dict)
}
Result:
main -> dict.memory: 0xc000054020
main -> dict: map[Hi:嗨 Welcome:歡迎]
func -> dict.memory: 0xc000054030
func -> dict: map[Hi:嗨嗨 Welcome:歡迎]
main -> dict: map[Hi:嗨嗨 Welcome:歡迎]
4. Struct
The behavior of passing Struct value to functions varies according to the type of struct fields.
If it's a basic type or an array, changes within the functions have no effect on the variable outside.
Ex:
func main() {
p1 := person{
name: "Fank",
age: 18,
}
fmt.Printf("main -> struct.memory: %p\n", &p1)
fmt.Printf("main -> struct: %v\n", p1)
incr(p1)
fmt.Printf("main -> struct: %v\n", p1)
}
func incr(p person) {
p.age++
fmt.Printf("func -> struct.memory: %p\n", &p)
fmt.Printf("func -> struct: %v\n", p)
}
Result:
main -> struct.memory: 0xc000010018
main -> struct: {Fank 18}
func -> struct.memory: 0xc000010048
func -> struct: {Fank 19}
main -> struct: {Fank 18}
We can use pointers to adjust previous example.
Ex:
func main() {
p1 := person{
name: "Fank",
age: 18,
}
fmt.Printf("main -> struct.memory: %p\n", &p1)
fmt.Printf("main -> struct: %v\n", p1)
incr(&p1)
fmt.Printf("main -> struct: %v\n", p1)
}
func incr(p *person) {
p.age++
fmt.Printf("func -> struct.memory: %p\n", p)
fmt.Printf("func -> struct: %v\n", *p)
}
Result:
main -> struct.memory: 0xc0000a4000
main -> struct: {Fank 18}
func -> struct.memory: 0xc0000a4000
func -> struct: {Fank 19}
main -> struct: {Fank 19}
If the field we want to change is slice or map, then the changes will be synced.
Ex:
func main() {
p1 := person{
name: "Fank",
age: 18,
title: []rune{'m', 'a', 's', 't', 'e', 'r'},
}
fmt.Printf("main -> struct.memory: %p\n", &p1)
fmt.Printf("main -> struct.title: %v\n", string(p1.title))
upper(p1)
fmt.Printf("main -> struct.title: %s\n", string(p1.title))
}
func upper(p person) {
for i, _ := range p.title {
p.title[i] = unicode.ToUpper(p.title[i])
}
fmt.Printf("func -> struct.memory: %p\n", &p)
fmt.Printf("func -> struct.title: %s\n", string(p.title))
}
Result:
main -> struct.memory: 0xc000108090
main -> struct.title: master
func -> struct.memory: 0xc00007e0f0
func -> struct.title: MASTER
main -> struct.title: MASTER
No comments:
Post a Comment