27. Pointer — Solusi PR

File solusi .go ada di exercises/27/homework/solutions/.

Soal 1 — Titik dengan move

Soal. point dengan distance dan move.

Cara berpikir. Mulai dari point di bab tadi. move mengubah field, jadi butuh receiver pointer, dan distance juga memakainya supaya seragam. a.move(1, 1) berhasil karena a adalah variabel, dan Go mengambil lokasinya untukmu.

Solusi yang dikerjakan.

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
}

Kesalahan umum.

  • Menulis func (p point) move(...) tanpa *. Kode ini bisa dikompilasi, tetapi perpindahannya mengubah salinan, dan a tetap di tempatnya.
  • Lupa menambahkan "math" ke import. Go berhenti dengan undefined: math.

Soal 2 — Karakter

Soal. Karakter dengan serangan, penyembuhan, dan baris laporan.

Cara berpikir. Ada dua aturan untuk hp: tidak pernah di bawah 0, dan tidak pernah di atas maxHP. Masing-masing punya satu tempat, yaitu sebuah if di akhir metode yang mengubah hp. report memanggil isAlive pada receiver yang sama.

Solusi yang dikerjakan.

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)

Kesalahan umum.

  • Membiarkan hp turun di bawah 0 atau naik di atas maxHP. Dua pemeriksaan if di takeDamage dan heal menjaga nilainya dalam rentang.
  • Mengisi hp di newCharacter dan melupakan maxHP. Nilai nolnya 0, jadi heal pertama menjatuhkan hp ke 0.

Soal 3 — Persegi panjang

Soal. Persegi panjang dengan area dan perimeter.

Cara berpikir. Tidak ada metode yang mengubah persegi panjangnya, dan field-nya hanya dua dan kecil. Aturan 3 dari bab tadi berlaku, jadi receiver nilai cocok. Receiver pointer juga bisa, dan output-nya akan sama.

Solusi yang dikerjakan.

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
}

Kesalahan umum.

  • Menyimpan w dan h di variabel di luar fungsi. Dengan dua persegi panjang, yang kedua akan menimpa yang pertama. Field itu milik setiap nilai.

Tantangan — Anjing dan temannya

Soal. Anjing yang temannya berupa pointer ke anjing lain, atau nil.

Cara berpikir. Field *dog berarti "mengarah ke seekor anjing, atau tidak ke mana-mana". newDog tidak mengisinya, jadi nilainya mulai dari nil. describe harus memeriksa nil sebelum membaca d.friend.name.

Solusi yang dikerjakan.

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.

Dua baris terakhir menunjukkan pembagiannya. rex.friend dan lassie mengarah ke anjing yang sama, jadi mengubah nama lewat yang satu terlihat lewat yang lain. Field dog sebagai ganti *dog bahkan tidak bisa dikompilasi, dengan pesan invalid recursive type: dog refers to itself, karena seekor anjing harus berisi seekor anjing utuh, yang berisi seekor anjing utuh lagi, tanpa akhir. Pointer punya ukuran yang tetap.

Kesalahan umum.

  • Membaca d.friend.name tanpa pemeriksaan nil. Pemanggilan describe pertama crash dengan invalid memory address or nil pointer dereference.

Selesai?

Sekarang kamu bisa mengubah nilai lewat pointer, dan memilih receiver untuk sebuah metode. Berikutnya, Banyak struct bersama menempatkan struct di dalam slice dan map, tempat salinan dari Bab 26 kembali sebagai jebakan.