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

たった 1 日で基本が身に付く! Go 言語 超入門:書籍案内|技術評論社

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

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

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()と書ける
}

今日の学び

  • 改めてポインタ表現時の書き方を復習できた。
  • 説明時にポインタ表現と書かれており、曖昧?な説明なのでもう少し用語を明確に分けたほうが覚えやすい。。
  • コンパイラがやることを頭で変換しながら読み書きするのは凄く脳内のメモリを使ってよく混乱する…
Hugo で構築されています。
テーマ StackJimmy によって設計されています。