スターティングGo言語、5章を読んだ。

- 作者:松尾 愛賀
- 発売日: 2016/04/15
- メディア: 単行本(ソフトカバー)
Chapter 5 構造体とインターフェース
ポインタ
Goにはポインタがある。ポインタはメモリ上のアドレスと型の情報
ポインタの定義
「*」を型の前に置くことで定義できる。
var p *int // *ppのポインタのポインタ var pp ***int // &を使って任意の型からポインタを生成できる i := 100 pointa := &i fmt.Println(*pointa) // 100が表示される
&をつけると、ポインタを生成できる。
ポインタの示す値を参照するには*
をつける。
ポインタ型の変数の前に*
を置くことでポインタ型が指し示すデータ本体を参照できる。
この仕組をデリファレンスと呼ぶ。
配列のポインタ
p := &[3]int{1, 2, 3} fmt.Println(*p) // [1 2 3] が表示 // 配列の要素を取得(ポインタ型のデリファレンス → 要素の参照) fmt.Println((*p)[0]) // 1 // これでもいける(ポインタ型のデリファレンス → 要素の参照をコンパイラが置き換えてくれる) fmt.Println(p[0]) // 1 // この書き方はエラー fmt.Println(*p[0])
文字列型のポインタ
Goの文字列はイミュータブルで、一度生成された文字列は変更できない。 そのため文字列を構成するbyte型の配列に対してポインタ型を定義できないようになっている。 (ポインタ型を定義できると、文字列に対する破壊的変更を許可することになるため) Goの文字列は。文字列の結合処理を行う度に新しい文字列がメモリ上に作成される。 String型を変数への再代入や関数の引数として使った場合も、文字列の実態が別メモリ領域にはコピーされず、参照先が渡される。
構造体
構造体(struct)は複数の型の値を1つにまとめたもの。 構造体に構造体を含めることもできる。
typeについて
typeはエイリアスを定義することができる。 エイリアス同士の互換性はない。
// intのエイリアスのMyIntを定義 type MyInt int // int型として使える var n1 MyInt = 5 n2 := MyInt(7) // エイリアス型には互換性がない type T0 int type T1 int t0 := T0(1) t1 := T1(2) // コンパイルエラー t0 = t1
構造体の定義
構造体はstructで囲われた範囲で定義し、typeを使って新しい型名をあたえる。
type FullName struct { FirstName string LastName string } var fullName FullName // 構造体フィールドへの代入 fullName.FirstName = "Yamada" fullName.LastName = "Tarou" // 複合リテラルで構造体を生成 fullName2 := FullName{"Suzuki", "Hanako" } // フィールドを明示的に指定して定義 fullName3 := FullName{ FirstName: "Suzuki", LastName: "Hanako" }
構造体の中に構造体も定義できる。
type Feed struct { Name string Amount string } type Animal struct { Name string Feed Feed } a := Animal{ Name: "Monkey", Feed: Feed{ Name: "Banana", Amount: 10, }, } // ネストした構造体は階層的にたどってアクセスできる fmt.Println(a.Feed.Amount)
上記のAminal内で定義してるFeed型のフィールド名を省略すると、フィールド名は暗黙的にFeedになる。 階層的アクセスの記述も省略して記述できる。
type Animal struct { Name string Feed // フィールド名を省略 } // フィールド名を省略してアクセスできる fmt.Println(a.Amount) // 暗黙的に設定されたフィールド名をたどってアクセスもできる。 fmt.Println(a.Feed.Amount)
この挙動は埋め込まれた構造体のフィールド名が一意の場合に限り省略してアクセスできる。
構造体は再帰的な定義はできない。 構造体は値型になるので、関数の引数として渡して、その構造体が関数のなかで書き換えられても元の構造体にはなんの影響もない。
newで指定した型のポインタ型を生成
newを使うとポインタ型を生成できる。
type Person struct { Id int Name string Area string } // ポインタ型になる p := new(Person)
メソッド
Goのメソッドは任意の型に特化した関数を定義できる。
type Point struct{ X, Y int } // Point型へのメソッド追加 func (p *Point) Render() { fmt.Printf("<%d, %d>\n", p.X, p.Y) } // Point型のpを生成 p := &Point{X: 5, Y: 12} // pは定義したメソッドを呼び出すことができる p.Render()
構造体で定義した変数名とメソッド名が同名だとコンパイルエラーになる。 同名のメソッドでもレシーバ名が異なっていれば定義できる。
型のコンストラクタ
構造体を初期化するために「型のコンストラクタ」というパターンが使用できる。 コンストラクタはNewXXXというイディオムがよく利用される。
メソッドを定義するときはレシーバはポインタ型にすべき
メソッド定義時にレシーバが値型の場合、呼び出したときにレシーバそのもののコピーが発生するため、メソッド呼び出し側とメソッド内部でレシーバの実態がことなることがある。またレシーバがポインタ型でも呼び出せてしまうため、挙動が異なることがある。
タグ
タグはフィールドにメタ情報を付与する機能。 メタ情報のため、プログラムの実行には影響はない。
// 各フィールドにタグをつける type User struct { Id int `json:"user_id"` Name string `json:"user_name"` Age int `json:"user_age"` } u := User{Id: 1, Name: "Taro", Age: 32} bs, _ := json.Marshal(u) fmt.Println(string(bs)) // タグがjsonのキーとして表示される // {"user_id":1,"user_name":"Taro","user_age":32}
インターフェース
インターフェースは型の一種であり、任意の型が「どのようなメソッドを実装すべきか」を規定するための枠組み。 インターフェースはメソッドの型だけを記述した型となる。 構造体を定義し、インターフェースで定義したメソッドを実装する。 異なる構造体でも共通のインターフェースをもたせることで共通の性質を付与できる。
// Animalインターフェースを定義 type Animal interface { Bark() } // Monkey構造体を定義 type Monkey struct { Name string } // インターフェースで定義されたメソッドを定義する func (m Monkey) Bark() { fmt.Println(m.Name) } // Lion構造体を定義 type Lion struct { Name string } // インターフェースで定義されたメソッドを定義する func (l Lion) Bark() { fmt.Println(l.Name + "!!!!") } func main() { var animal Animal // インターフェースに格納し、実行 animal = Monkey{"Taro"} animal.Bark() // インターフェースのメソッドを実装していれば、どんな型も格納できる。 animal = Lion{"スカー"} animal.Bark() }
所感
5章までで構文的なことは網羅してそう。6章以降はGoのツールやパッケージについてなので必要に応じて読んでいこうと思う。 DBを使うとかAPIの実装とかまだまだよくわかってないとこがあるので、次はGoプログラミング実践入門 標準ライブラリでゼロからWebアプリを作る impress top gearシリーズでも読んでいこうかと思う。