Programming involves machine instructions working on data that is available in memory. So when you want to, say, add two numbers, then both those numbers have to be available in memory. And for that to happen, you should allocate some memory space for that. In Go, you can do that easily for most types with the initialization operator.
Here Go automatically allocates some memory for the variable i - the size of the memory allocated here is that required by a single integer. Since we have also said
Let’s represent it this way:
So i now holds the value 5. And j now holds the default value 0.
Note that you obtain the address of a variable by using the
The actual value of the address will differ from machine to machine and even on different executions of the same program as each machine could have a different memory layout and and also the location where it is allocated could be different.
You could ask the question, ‘so would my program behave differently on different machines since the addressing changes?’ It is true that the addresses will change, however, normal programs do not use the numeric value of the address for anything. Instead, what they usually use is the value referenced by the address. You can get the value at an address by using the
When a variable holds an address, it is called a pointer. So in the example
To illustrate, if we had
You can only take the pointer to a variable and not to a literal value or a constant as the following examples show.
One of the reasons for working with addresses is a matter of efficiency; we shall see more when we discuss pass-by-reference and pass-by-value. As a metaphor, imagine a page on Wikipedia, let’s say one about Paris: http://en.wikipedia.org/wiki/Paris. If you wanted to send that entire information to somebody, one way would be to copy the entire page into a document and send it to him, say via email or as a print out. An easier and much faster alternative would be to just send the link to the page, which is a unique url/reference to it. In this case, there are no redundant copies and both of you can read the latest page about Paris. If you sent the entire page, the former method, then it is similar to ‘pass-by-value’ - the entire value is being passed. If you sent only the link, the latter method, then it is similar to ‘pass-by-reference’ which is passing the address.
Both are useful depending on what is required in a situation. When you pass by reference, there is only a single copy of the target, and therefore any changes made by one person can be seen by all. When you pass by value, there are separate copies and what is changed by one person does not affect the original.
Full code
package main import "fmt" func main() { i := 5 var j int fmt.Println("i is: ", i) fmt.Println("j is: ", j) }
i is: 5
j is: 0
j is: 0
Here Go automatically allocates some memory for the variable i - the size of the memory allocated here is that required by a single integer. Since we have also said
i := 5
, the integer value 5 is assigned to that memory space after the space is allocated. For variable j
, no assignment has been stated. However, Go assigns a "zero-value" by default to most data types. For numeric fields, this is the value 0. Let’s represent it this way:
So i now holds the value 5. And j now holds the default value 0.
Default values of primitive types
Let’s do a short example to find the ‘zero-value’ or default value for some other known data types.Full code
package main import "fmt" func main() { var i int fmt.Println("default int is: ", i) var s string fmt.Println("default string is: ", s) var f float64 fmt.Println("default float64 is: ", f) var arInt [3]int fmt.Println("default int array is: ", arInt) var c complex64 fmt.Println("default complex64 is: ", c) }
default int is: 0
default string is:
default float64 is: 0
default int array is: [0 0 0]
default complex64 is: (0+0i)
default string is:
default float64 is: 0
default int array is: [0 0 0]
default complex64 is: (0+0i)
Addresses and memory location
When there is a value stored in memory, it is stored in a physical location. This location is called its address. Many programming languages, including Go, allows you to access the data in a location by specifying its location in memory.Full code
package main import "fmt" func main() { i := 5 fmt.Println("i is: ", i) fmt.Println("address of i is: ", &i) }
i is: 5
address of i is: 0xf840000040
address of i is: 0xf840000040
Note that you obtain the address of a variable by using the
&
symbol before the variable name. Let’s see some more examples.package main import "fmt" func main() { var i int fmt.Println("address of i is: ", &i) var s string fmt.Println("address of s is: ", &s) var f float64 fmt.Println("address of f is: ", &f) var c complex64 fmt.Println("address of c is: ", &c) }
address of i is: 0xf840000040
address of s is: 0xf8400013e0
address of f is: 0xf8400000f8
address of c is: 0xf8400000f0
address of s is: 0xf8400013e0
address of f is: 0xf8400000f8
address of c is: 0xf8400000f0
The actual value of the address will differ from machine to machine and even on different executions of the same program as each machine could have a different memory layout and and also the location where it is allocated could be different.
You could ask the question, ‘so would my program behave differently on different machines since the addressing changes?’ It is true that the addresses will change, however, normal programs do not use the numeric value of the address for anything. Instead, what they usually use is the value referenced by the address. You can get the value at an address by using the
*
symbol before the address. Let’s see some examples where we dereference and address to get its value.Full code
package main import "fmt" func main() { var i int fmt.Println("value of i is: ", i) fmt.Println("address of i is: ", &i) fmt.Println("value at address ", &i, " is: ", *(&i)) //value at (address of i) fmt.Println() var s string fmt.Println("value of s is: ", s) fmt.Println("address of s is: ", &s) fmt.Println("value at address ", &s, " is: ", *&s) ////value at address of i fmt.Println() var f float64 fmt.Println("value of f is: ", f) fmt.Println("address of f is: ", &f) fmt.Println("value at address ", &f, " is: ", *&f) fmt.Println() var c complex64 fmt.Println("value of c is: ", c) ptr := &c //address of c. fmt.Println("address of c is: ", ptr) fmt.Println("value at address ", ptr, " is: ", *ptr) //value at the address }
value of i is: 0
address of i is: 0xf840000040
value at address 0xf840000040 is: 0
value of s is:
address of s is: 0xf8400013b0
value at address 0xf8400013b0 is:
value of f is: 0
address of f is: 0xf8400000e8
value at address 0xf8400000e8 is: 0
value of c is: (0+0i)
address of c is: 0xf8400000b8
value at address 0xf8400000b8 is: (0+0i)
address of i is: 0xf840000040
value at address 0xf840000040 is: 0
value of s is:
address of s is: 0xf8400013b0
value at address 0xf8400013b0 is:
value of f is: 0
address of f is: 0xf8400000e8
value at address 0xf8400000e8 is: 0
value of c is: (0+0i)
address of c is: 0xf8400000b8
value at address 0xf8400000b8 is: (0+0i)
When a variable holds an address, it is called a pointer. So in the example
ptr := &c
, ptr
is a pointer and it holds the address the of c
. Putting it differently, ptr
is a pointer to the variable c
. You could also say that ptr
is a reference to the variable c
. All of these are valid, but they tend to be used in slightly different contexts. To illustrate, if we had
i := 5; ptr := &i
, we could roughly illustrate it as shown below. In using it, both i
and *ptr
refers to the integer value 5.You can only take the pointer to a variable and not to a literal value or a constant as the following examples show.
package main func main() { const i = 5 ptr := &i //error: cannot take the address of i ptr2 := &10 //error: cannot take the address of 10 }
The need for addresses/pointers/references
Why do we need the complexity of addresses, pointers and references? Why can we not just use the actual value?One of the reasons for working with addresses is a matter of efficiency; we shall see more when we discuss pass-by-reference and pass-by-value. As a metaphor, imagine a page on Wikipedia, let’s say one about Paris: http://en.wikipedia.org/wiki/Paris. If you wanted to send that entire information to somebody, one way would be to copy the entire page into a document and send it to him, say via email or as a print out. An easier and much faster alternative would be to just send the link to the page, which is a unique url/reference to it. In this case, there are no redundant copies and both of you can read the latest page about Paris. If you sent the entire page, the former method, then it is similar to ‘pass-by-value’ - the entire value is being passed. If you sent only the link, the latter method, then it is similar to ‘pass-by-reference’ which is passing the address.
Both are useful depending on what is required in a situation. When you pass by reference, there is only a single copy of the target, and therefore any changes made by one person can be seen by all. When you pass by value, there are separate copies and what is changed by one person does not affect the original.
Thank you, that the perfect expression of the subject, as always.
ReplyDeletethanks this was really helpful
ReplyDeleteExcellent refresher on the topic. I'm sure I'll be back in a tired state at some point. :)
ReplyDeleteFantastic write up. Some of the best explanations.
ReplyDeleteThis article is simple and very util for understanding GoLang
ReplyDeletethanks a lot. great article.
ReplyDeleteThat's the first time that I really understand what is a pointer. THANK YOU!
ReplyDeleteThanks for this post! It seems I start understanding what actually is a pointer (at last). But it would be great if someone gave a couple examples on when and how we should use pointers. And one more question: I saw somewhere in the Go code a pointer to a pointer, i.e., **a. Could someone please explain when, how and why should we use these? Thanks a lot!
ReplyDeleteIf you were to drop down to C, you would learn that an array is essentially syntactic sugar for a pointer. Since a string with this line of thought is a pointer, if you are to have an arrays of strings you are essentially working with a pointer to a pointer. But since pointer arithmetic is absent from Go, probably for your data structures
DeleteHello Author,
ReplyDeleteAwesome article... Very helpful for beginner
Perfect! Thanks.
ReplyDeleteGreat write-up. Thanks for sharing!
ReplyDelete