Go 言語の構造体 (struct)

Go には明示的なクラス定義がありません。オブジェクト志向プログラミングの実装には、ここで説明する構造体を利用します。

Go の構造体とは?

複数のデータをひとまとまりにして扱いたい場合に、構造体 (struct) を利用すると便利です。

例えば、人 (person) のデータモデルを考えた時に、「名前 (name)」と「年齢 (age)」をひとまとまりにして、特定のひとの情報としたいとします。

このとき、Go では次のように書くことで、 string 型の name と int 型の age という二つのデータフィールドをもつ、 person という名前のデータ構造を定義することができます。

type person struct {
	name string
	age  int
}

type キーワードで構造体をひとつ作るということは、struct で定義された新しい型 (type) を定義する、ということになります。

これを使うと、次のように person 型の変数を使うことができます。

var p person
p.name = "John"
p.age = 30

それでは、構造体の使い方をもう少しみていきましょう。

Go の構造体の定義方法

Go の構造体は struct キーワードを使って、次のように定義できます。

type 構造体の名前 struct {
	フィールド名1 データ型1
	フィールド名2 データ型2
	...
}

フィールドは任意の数だけ定義できます。

構造体の名前はアルファベットの文字で始めます。外部パッケージからのアクセスを許可する場合は、大文字で始めます。

また、フィールド名も外部パッケージからのアクセスを許可する場合は、大文字のアルファベットから始まるように命名します。

Go の構造体の作成

上で定義した person 型をもう一度見てみましょう。

type person struct {
	name string
	age  int
}

この構造体は、ビルトインの型と同様に次のように作成できます。

var p person

:= を使う場合は構造体のリテラル {} を使って次のように書けます。

p := person{}

構造体の作成時に初期化を行う場合には、次のように書けます。

p := person{
	name: "John",
	age:  30,
}

フィールド名は省略でき、次のように書いても同じです。

p := person{
	"John",
	30,
}

とはいえ、フィールド名がないと何に何の値をセットしているかわかりにくいので、多用しない方が無難と思います。

Go の構造体にメソッドを実装する

特定の型が関連付けされた関数のことを特に、メソッド (method) といいます。

Go の関数の定義にレシーバ (receiver)を指定することによって、レシーバで指定した型と関数を関連付けできます。

func レシーバ メソッド名(引数リスト) 戻り値リスト {
}

レシーバは (変数 型) という形式で記述します。

具体例をみてみましょう。上述の person 型の構造体に hello() というメソッドを定義するには次のように書きます。

package main

import "fmt"

type person struct {
	name string
	age  int
}

func (p person) hello() {
	fmt.Printf("%s (%d)\n", p.name, p.age)
}

func main() {
	p := person{
		name: "John",
		age:  30,
	}
	p.hello() // John (30)
}

10行目、func キーワードの後に書いてある (p person) の部分がレシーバになります。 このレシーバを経由して構造体のフィールドの値にアクセスできます。

Go のポインタレシーバとバリューレシーバ

関数の引数に参照渡しと値渡しがあるのと同様に、レシーバにも参照渡しと値渡しがあります。

参照渡しのレシーバを特に ポインタレシーバ (pointer receiver)、値渡しのレシーバーをバリューレシーバ(value receiver) といいます。

メソッド内でフィールドの値を変更する場合には、ポインタレシーバを利用する必要があります。

Go のポインタについては、「Go 言語のポインタ」を参考にしてください。

具体例として、上記 person 型に、年齢 (age) をひとつインクリメントする increment() メソッドを追加してみましょう。

func (p person) hello() {
	fmt.Printf("%s (%d)\n", p.name, p.age)
}

func (p *person) increment() {
	p.age++
}

func main() {
	p := person{
		name: "John",
		age:  30,
	}
	p.hello() // John (30)
	p.increment()
	p.hello() // John (31)
}

5行目のレシーバが (p *person) というように、ポインターで渡されているところに注意してください。

6行目で age をインクリメントしています。ポインタレシーバーであるために、このインクリメントは成功します。このため、15行目で increment() を呼び出した後に、 もう一度 hello() を呼び出すと、 age がひとつ増えて 31 となっていることがわかります。

以上、ここでは Go 言語の構造体の基本について説明しました。

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Go 言語入門