Video Coming Soon...
20: Pointers
WARNING This exercise is in DRAFT status, so there may be errors. If you find any, please email me at help@learncodethehardway.com so I can fix them.
Pointers can be confusing but Go has a few features that make them slightly easier to use. In this exercise I'll do my best to explain pointers, then I'll give you a puzzle to solve to learn about them more, and finally I'll give you a "cheat sheet" of phrases to remember that should help you use pointers until you understand them better.
A Pointer is Just a Number
In the 90s my computer had about 1GB or memory in it. Your computer today most likely has 8GB or 16GB, but in the 90s 1GB was more common. When I write "gigabytes" I'm saying that my 90s computer had 1 billion bytes of memory available to it. If we numbered every byte in my computer, it would go from byte number 0 all the way to byte number 1,000,000,000.
We could say that my 90s computer's memory was a gigantic array
with 1 billion uint8
elements in it. That meant if I wanted the byte at index 129 I could write memory[129]
and if I wanted the one at 768,928 I could write memory[768928]
to get that byte.
In Go I can also use a variable to access the contents of our fake memory[]
. I could do something like this:
View Source file ex20a/main.go Only
package main
import "fmt"
func main() {
memory := []string{
"apple",
"orange",
"grape",
"durian",
}
pointer := 1
a_fruit := memory[pointer]
fmt.Println("the fruit is", a_fruit)
}
In this example I'm pretending the computer's memory has strings with names of fruits so you don't get confused between the contents of memory
and the value of pointer
. The effect is the same though, where Go allows me to get the string at any point using the pointer
variable.
That's all a point is. It's a big number set to some location inside the computer's memory, and Go provides some handy syntax to access their targets.
Address-of Operator &
When you create a pointer you use the &
(ampersand) operator to get the "address of" something. You use it like this:
pointer := &memory[1]
Let's break each part of this down:
pointer
-- This is a variable like any other.:=
-- This assigns and declares a variable like all the other times.&
-- This tells Go to get the address of the thing after it.memory
-- PAY ATTENTION the&
is NOT getting the address ofmemory
. The "thing" after&
is actuallymemory
plus...[1]
-- ...the index intomemory
you want.
This gives you the address of the memory
element at index 1. You could also write it with parenthesis if that helps:
pointer := &(memory[1])
Value-of Operator *
Having a pointer to something is only useful if you can get the contents, or the "value of" where it's pointing. You do that with the *
(asterisk) operator in front of a pointer like this:
a_fruit := *pointer
Here's how Go processes this line:
a_fruit
-- You want to make a variable nameda_fruit
that is...:=
-- declared and assign it from...*
-- the value of...pointer
-- whateverpointer
is targeting.
When I code I actually say things in my head like, "the value of X" and "the address of X" when I write the &
(address-of) and *
(value-of) characters. I suggest you do that too, or even write a comment above each line until it becomes natural.
Pointer Version
Knowing this I can now show you a pointer version of our fruit memory code:
View Source file ex20b/main.go Only
package main
import "fmt"
func main() {
memory := []string{
"apple",
"orange",
"grape",
"durian",
}
pointer := &(memory[1])
a_fruit := *pointer
fmt.Println("the fruit is", a_fruit)
}
This should work the same, and what I recommend is to study how these are similar, but the syntax for pointer
is different in each.
Pointers as Struct Fields
Now that you know how to make a pointer and access you can learn how Go helps when a struct has a field pointer. Imagine you have this struct:
type Person struct {
Home *Address
}
In this code pretend that Address
is another struct
and I want to use a pointer. In other languages I would have to use special syntax to access the value of this, but in Go you just use the Person
struct like normal:
// print out frank's home street
fmt.Println(frank.Home.Street)
This makes it a lot easier to work with struct
types, but it also makes it so you can change your mind later. If you decide that Home
shouldn't be a pointer anymore you just change the struct
and everything should mostly keep working.
Pointers as Function Parameters
You can also use a pointer as a function parameter, and in this usage it allows you to "pass by reference." The phrase "pass by reference" simply means that you give a function a pointer to something for one of thre reasons:
- The function will "construct" or alter the parameter, so it needs the pointer in order to access the memory directly.
- The variable is something huge like an image and passing it normally would be too slow (or crash).
- You want both to let the function construct/change it and it's a large piece of data.
To create a pointer parameter just add the *
to the type like this:
func LoadPerson(out_person *Person) err {
// alter person and return error if fail
}
If you didn't do this then LoadPerson
would get a copy of out_person
so all of the changes would be lost.
You would then use the &
operator to give LoadPerson
the pointer it needs:
zed := Person{}
err := LoadPerson(&zed)
if err != nil { log.Fatal(err) }
However it's more common that you'd use the new()
function to create the Person
and get a pointer:
zed := new(Person)
// no & needed because new returns a pointer
err := LoadPerson(zed)
if err != nil { log.Fatal(err) }
Puzzle Time: Linked Lists
It's time for a puzzle using what you know about pointers and a description of a data structure from Wikipedia. A "data structure" is a configuration of struct
types that implement some useful way of storing and accessing data. A "Linked List" is one of the first data structures someone learns, and you're going to make one and manually use it.
First step is to go and read about Linked list on Wikipedia. Wikipedia's descriptions of algorithms are usually correct, but you have to do some work to figure them out. This is the first part of the challenge.
The second part of the challenge is to take the code below and manually do the following with a linked list:
- Manually create a list with 5 element in it.
- Then get the 3rd one.
- Then remove the first one.
- Then count the total.
I've given you the struct
so you won't be lost too much, but you could take this challenge further and try to recreate this starter code from scratch.
The Code
The starting code for your Linked List adventures is:
View Source file ex20/main.go Only
package main
import (
"fmt"
)
type Node struct {
Data any
Next *Node
}
type LinkedList struct {
Front *Node
End *Node
}
func main() {
list := new(LinkedList)
fmt.Println("Your code goes here.", list.Front)
}
The Practice
- Break It -- Try to use
++
increment on pointers and see what happens. Why did the Go designers do that? Try to cause other errors related to pointers as well. Can you assign a*int
pointer to a*int64
? - Change It -- Do as much as you can with this Linked List structure manually. Manually doing the operations mentioned in the Wikipedia article will help you in the next exercise.
- Recreate It -- You probably did this already, but if not, try to recreate the sample code from memory.
Study Drills
- There's another data structure called a Double Linked List. See if you can do the same "manual operations" but on this data structure instead.
Register for Learn Go the Hard Way
Register today for the course and get the all currently available videos and lessons, plus all future modules for no extra charge.