構造体を定義する (struct)

Go 言語の構造体は、struct というキーワードを使って定義します。

構造体の基本

構造体は、複数のデータをまとめて扱うためのデータ構造です。 下記の例では、書籍のタイトルと著者、値段をフィールドとして持つ Book という構造体を定義しています。

type Book struct {
	Title   string
	Authors []string
	Price   int
}

組込み型に別名を付ける ときには type Age int のように記述しますが、この後ろの int の部分が struct {...} という記述に置き換わったと考えればよいでしょう。

定義した構造体は下記のように使用することができます。

// Book 構造体を定義する
type Book struct {
	Title   string
	Authors []string
	Price   int
}

func main() {
	// Book オブジェクトを生成する
	b := Book{
		Title:   "Golang ABC",
		Authors: []string{"Maku", "Moja"},
		Price:   2500,
	}

	// 各フィールドの値を参照する
	fmt.Println(b.Title)    // Golang ABC
	fmt.Println(b.Authors)  // [Maku Moja]
	fmt.Println(b.Price)    // 2500
}

この例では、各フィールドの 名前を大文字で始めている(例えば Title)ので、そのフィールドは別のパッケージからも参照可能なフィールドとして公開されます。 逆に小文字で始める(例えば title)ように定義すると、そのフィールドは同じパッケージ内のコードからのみ参照できるようになります(≒ 同じディレクトリ内の .go ファイルであれば参照できる)。

ちなみに、オブジェクトを生成するときに、下記のようにフィールド名を省略して初期値を設定することもできます。 その場合は、構造体定義時と同じ順序で、すべてのフィールドの初期値を指定する必要があります。

b := Book{"Golang ABC", []string{"Maku", "Moja"}, 2500}

フィールド名を指定する方法であれば、任意のフィールドの初期値を省略してオブジェクトを生成することができます。 省略したフィールドの値は、その型の ゼロ値 となります。

// すべてのフィールドを省略
b1 := Book{}  //=> "", [], 0

// Title フィールドだけ初期値を指定
b2 := Book{Title: "Golang"}  //=> "Golang", [], 0

構造体へのポインタ

関数に渡された構造体データのフィールドを書き換えたいときは、ポインタ型で渡す必要があります(そうしないとコピーが渡されてしまいます)。 下記サンプルの、raisePrice 関数は、渡された Book オブジェクトの Price フィールドの値を2倍に書き換えます。

func raisePrice(b *Book) {
	b.Price *= 2
}

func main() {
	b := Book{
		Title: "Golang ABC",
		Price: 2500,
	}
	raisePrice(&b)
	fmt.Println(b.Price)  //=> 5000(2倍になってる)
}

C/C++ の文法とは異なり、ポインタ経由のフィールドアクセスにもドット (.) を使用していることに注目してください。

C/C++ の場合:  b->Price
   Go の場合:  b.Price

Go 言語では、ポインタ経由のアクセスも同じ記法を使えるようにすることで、コードをシンプルに保てるようにしています。 この性質を利用すると、上記の main 関数は下記のように書き直すことができます。 変数 b を最初からポインタ型として定義してしまうことで、それ以降のコードで &b のような形でアドレス取得する手間を省くことができます(ここでは一か所だけですけどね^^)。

func main() {
	b := &Book{
		Title: "Golang ABC",
		Price: 2500,
	}
	raisePrice(b)
	fmt.Println(b.Price)
}

ここでのサンプルコードでは、raisePrice 関数のパラメータとして Book ポインタを受け取るように実装しましたが、オブジェクト指向風に実装するのであれば、メソッドの形で実装 してしまえばシンプルになります。

new 関数による初期化

すべてのフィールドをゼロ値で初期化したオブジェクトを生成するときは、new(型) という組込み関数を使って C++ 風の書き方をすることもできます。 new 関数の戻り値は、指定した型のポインタとなります。

空のオブジェクトを生成する方法いろいろ
var b *Book = new(Book)
var b *Book = &Book{}

// 関数内なら次のように簡潔に書けます
b := new(Book)
b := &Book{}

&Book{} の形式を使うとタイプ数を削減できますが、new(Book) の方がわかりやすいかもしれません。