Small Changes

たった1日で基本が身に付く! Go言語 超入門@5日目


Metadata


昨日ポインタをやり切らなかったので今日でやり切ります。

明日から旅行なので楽しんできます!

CHAPTER 6 データを直接指し示すポインタ

レシーバを構造体のポインタにする

今日の話を理解するうえでかなり重要

構造体のインスタンスメソッドを定義する際は以下のように書いた

// 構造体のインスタンスのメソッド
func (構造体のインスタンス名(レシーバ) 構造体のデータ型) describe(引数 引数のデータ型) 戻り値のデータ型 {
    - // 処理
}

通常のメソッドとの違いはメソッド名の前に(構造体のインスタンス名(レシーバ) 構造体のデータ型)を定義する点が違った。

6章のセクション3では以下のような表現が出てくる

// *someapp 型に対してメソッドを定義する
func (app *someapp) open() {
    - // 処理
    - app.use++
}

*someappsomeappというポインタ型である

ここでは、someappというポインタ型に対してopenというメソッドを定義している

そして、関数の中では*(app).useとすべきところを、app.useと書くことが可能。(&appがレシーバの前提)

そしてそして、さらに重要なのが呼び出し方!

func main() {
    - app1 := app{"りんご", 1, true}
    - (&app1).open() // 本来はこう書かないといけないが、
    - app1.open()    // このように&を省略できる
}

コンパイラがメソッドの定義をみて、暗黙的に変換してくれるようだ。

セクション3の例題の概要

構造体someappはusername、use、isopenというフィールドを持つ

openメソッド

  • isopenがtreuの場合は、アプリを開いている場合の処理を行う

  • falseの場合は、アプリを開く処理を行う

    • app.useの値が2を超えていたら、それ以上の処理は行わない

closeメソッド

  • isopenがtrueの場合は、アプリを閉じる処理を行う

最初にsomeappインスタンスを作成するnewapp関数

  • useの値は1、isopenはtrue

    • すでに開いている状態 かつ 1度試用済みの状態から始まる

レシーバに渡したインスタンスと渡す前のレシーバは別物

変数から変数に値を渡すと別の場所に保存されるのと同じことがレシーバでも起きる。

レシーバに渡す前のアドレスとレシーバに渡った後では別物になっていることがわかる。

つまり同じインスタンスに対して変更し続けたい場合、ポインタ型に対してメソッドを定義する。そうすればメソッド内でも同じインスタンスに対して変更が加えられる

// 構造体test
type test struct {
    - test string
}
// 構造体testインスタンスのアドレスを出力するメソッド
func (t test) address() {
    - fmt.Printf("関数中でのアドレス:%p\n", &t)
}
func main() {
    - test1 := test{"test"}
    - fmt.Printf("現在のアドレス:%p\n", &test1) // 現在のアドレス:0xc0001081e0
    - test1.address()                           // 関数中でのアドレス:0xc0001081f0
    - fmt.Printf("現在のアドレス:%p\n", &test1) // 現在のアドレス:0xc0001081e0

メソッドチェーンとの使い分け

メソッドチェーン

  • メソッドの戻り値に自分自身のコピーを返す、値が欲しいだけの場合などのコピーでも問題ない場合に使う。

ポインタ

  • 結果によって別の処理をしたり、他のインスタンスも並行処理する場合に向いている

連結リスト

ちょっと理解が難しいのでポイントだけメモ

構造体のフィールドにもポインタ表現が使える

type element struct {
    - value string
    - next  *element // &elm(アドレス)で受け取る
}

あと連結リストのロジックはいまいちピンときていないが重要なのはポインタの使い方

type test struct {
    - test string
}
func (t *test) echo() { // *testは*tしてアドレス参照したらtest型になるから
    - // 関数内ではアドレス参照を省略してtと書ける
}
func main() {
    - test1 := test{"テスト!"}
    - test.echo() // echo関数は*testをレシーバとして受け取るが、コンパイラが暗黙的に変換するので&test.echo()ではなくtest.echo()と書ける
}

今日の学び

改めてポインタ表現時の書き方を復習できた。

説明時にポインタ表現と書かれており、曖昧?な説明なのでもう少し用語を明確に分けたほうが覚えやすい。。

コンパイラがやることを頭で変換しながら読み書きするのは凄く脳内のメモリを使ってよく混乱する…

References