27. Pointers — Homework solutions
The .go solution files are in
exercises/27/homework/solutions/.
Problem 1 — Point with move
Problem. A point with
distance and move.
How to think about it. Start from the chapter's
point. move changes the fields, so it needs a
pointer receiver, and distance gets one too, to match.
a.move(1, 1) works because a is a variable,
and Go takes its location for you.
Worked solution.
package main
import (
"fmt"
"math"
)
type point struct {
x float64
y float64
}
func (p *point) distance(other point) float64 {
dx := p.x - other.x
dy := p.y - other.y
return math.Sqrt(dx*dx + dy*dy)
}
func (p *point) move(dx float64, dy float64) {
p.x = p.x + dx
p.y = p.y + dy
}
func main() {
a := point{x: 0, y: 0}
b := point{x: 3, y: 4}
fmt.Println(a.distance(b)) // 5
a.move(1, 1)
fmt.Println(a.x, a.y) // 1 1
fmt.Printf("%.3f\n", a.distance(b)) // 3.606
}Common mistakes.
- Writing
func (p point) move(...)with no*. It compiles, but the move changes a copy, andastays where it was. - Forgetting to add
"math"to the imports. Go stops withundefined: math.
Problem 2 — Character
Problem. A character with damage, healing and a report line.
How to think about it. Two rules go with
hp: never below 0, and never above maxHP. Each
one has a single place to live, an if at the end of the
method that changes hp. report calls
isAlive on the same receiver.
Worked solution.
package main
import "fmt"
type character struct {
name string
hp int
maxHP int
}
func newCharacter(name string, hp int) *character {
return &character{name: name, hp: hp, maxHP: hp}
}
func (c *character) takeDamage(amount int) {
c.hp = c.hp - amount
if c.hp < 0 {
c.hp = 0
}
}
func (c *character) heal(amount int) {
c.hp = c.hp + amount
if c.hp > c.maxHP {
c.hp = c.maxHP
}
}
func (c *character) isAlive() bool {
return c.hp > 0
}
func (c *character) report() {
fmt.Printf("%s: %d / %d HP (alive: %v)\n", c.name, c.hp, c.maxHP, c.isAlive())
}
func main() {
keiko := newCharacter("Keiko", 100)
keiko.report()
keiko.takeDamage(30)
keiko.report()
keiko.takeDamage(80)
keiko.report()
keiko.heal(20)
keiko.report()
}Output:
Keiko: 100 / 100 HP (alive: true)
Keiko: 70 / 100 HP (alive: true)
Keiko: 0 / 100 HP (alive: false)
Keiko: 20 / 100 HP (alive: true)
Common mistakes.
- Letting
hpgo below 0 or abovemaxHP. The twoifchecks intakeDamageandhealhold the value in range. - Setting
hpinnewCharacterand forgettingmaxHP. Its zero value is0, so the firsthealdropshpto0.
Problem 3 — Rectangle
Problem. A rectangle with area and
perimeter.
How to think about it. No method changes the rectangle, and it has only two small fields. Rule 3 from the chapter applies, so value receivers fit. Pointer receivers would also work, and the output would be the same.
Worked solution.
package main
import "fmt"
type rectangle struct {
w int
h int
}
func (r rectangle) area() int {
return r.w * r.h
}
func (r rectangle) perimeter() int {
return 2 * (r.w + r.h)
}
func main() {
r1 := rectangle{w: 3, h: 4}
r2 := rectangle{w: 10, h: 2}
fmt.Println(r1.area()) // 12
fmt.Println(r1.perimeter()) // 14
fmt.Println(r2.area()) // 20
fmt.Println(r2.perimeter()) // 24
}Common mistakes.
- Keeping
wandhin variables outside the functions. With two rectangles, the second would overwrite the first. The fields belong to each value.
Challenge — Dog and friend
Problem. A dog whose friend is a pointer to another
dog, or nil.
How to think about it. A *dog field
means "leads to a dog, or to nothing". newDog leaves it
out, so it starts as nil. describe must check
for nil before reading d.friend.name.
Worked solution.
package main
import "fmt"
type dog struct {
name string
friend *dog
}
func newDog(name string) *dog {
return &dog{name: name}
}
func (d *dog) describe() {
if d.friend == nil {
fmt.Printf("%s has no friend.\n", d.name)
return
}
fmt.Printf("%s is friends with %s.\n", d.name, d.friend.name)
}
func main() {
rex := newDog("Rex")
lassie := newDog("Lassie")
rex.describe()
lassie.describe()
rex.friend = lassie
rex.describe()
lassie.describe()
rex.friend.name = "Lady"
rex.describe()
lassie.describe()
}Output:
Rex has no friend.
Lassie has no friend.
Rex is friends with Lassie.
Lassie has no friend.
Rex is friends with Lady.
Lady has no friend.
The last two lines show the sharing. rex.friend and
lassie lead to the same dog, so changing the name through
one shows through the other. A dog field in place of the
*dog would not even compile, with
invalid recursive type: dog refers to itself, because a dog
would have to contain a whole dog, which contains a whole dog, without
end. A pointer has a fixed size.
Common mistakes.
- Reading
d.friend.namewith nonilcheck. The first call todescribecrashes withinvalid memory address or nil pointer dereference.
Done?
You can now change a value through a pointer, and choose a receiver for a method. Next, Many structs together puts structs in slices and maps, where the copy from Chapter 26 comes back as a trap.