INDEX
structの概要
| 複数の異なる型のデータをひとまとめにするGoの予約語 | ||
|
struct 概要 |
||
|
structのオブジェクト指向的表現をイメージで理解

基本的なstructの使い方
以下のコードは、structを使って人物の情報を管理する例です。
package main
import "fmt"
// 構造体の定義
type Person struct {
Name string
Age int
}
func main() {
// 構造体のインスタンスを作成(フィールド名指定形式)
p := Person{Name: "太郎", Age: 25}
// フィールドにアクセス
fmt.Println("名前:", p.Name)
fmt.Println("年齢:", p.Age)
}
解説:
type Person structという宣言により、新しい独自の型Personを定義しています。- インスタンス(構造体実体)を生成する際は、
型名{フィールド: 値, ...}のリテラル記法を用います。フィールド名を明示的に記述することで、初期化の順番に依存しない安全なインスタンス化が可能になります。 - 生成したインスタンスのフィールドへの読み書きは、ドット演算子(
p.Name)を用いて直接アクセスします。
実行結果:
名前: 太郎 年齢: 25
ポインタを使った構造体の参照
Goの構造体は標準では「値型」として振る舞いますが、ポインタ演算子(*)やアドレス参照演算子(&)を付与することで、メモリ上の同一実体を効率的に共有・操作できます。
package main
import "fmt"
type Person struct {
Name string
Age int
}
func changeAge(p *Person, newAge int) {
p.Age = newAge // ポインタを介して元の実体のフィールドを更新
}
func main() {
p := Person{Name: "花子", Age: 30}
fmt.Println("変更前の年齢:", p.Age)
changeAge(&p, 35) // インスタンスのアドレス(ポインタ)を渡す
fmt.Println("変更後の年齢:", p.Age)
}
解説:
- 関数
changeAgeの第1引数の型を*Personとすることで、構造体そのもののコピーではなく「構造体が格納されているメモリ上の位置情報(アドレス)」を受け取ります。 - Go言語では、ポインタ経由のフィールドアクセスであっても、C言語のようなアロー演算子(
->)を使わず、p.Ageのように自動でデリファレンス(間接参照)されて直接ドット演算子で記述できるという親切な糖衣構文(シンタックスシュガー)が採用されています。
実行結果:
変更前の年齢: 30 変更後の年齢: 35
構造体にメソッドを定義
構造体定義の外部で特定の関数に「レシーバ」を付与することで、その型専用のメソッド(オブジェクトの持つ関数)を拡張できます。
package main
import "fmt"
// Person構造体の定義
type Person struct {
Name string
Age int
}
// メソッドの定義(ポインタレシーバ)
func (p *Person) Greet() {
fmt.Printf("こんにちは、私は%sです。%d歳です。\n", p.Name, p.Age)
}
func main() {
p := Person{Name: "次郎", Age: 20}
p.Greet() // 構造体のインスタンスから直接メソッドを呼び出し
}
解説:
func (p *Person) Greet()の(p *Person)の部分をポインタレシーバと呼びます。メソッド内で構造体の状態を更新したい場合や、メソッド呼び出しのたびに発生する構造体の値コピーのオーバーヘッドを削減したい場合にポインタレシーバを選択するのがGoの鉄則です。
実行結果:
こんにちは、私は次郎です。20歳です。
構造体の埋め込み(コンポジションによる機能拡張)
Go言語はオブジェクト指向の「クラスの継承(Inheritance)」をあえて廃止していますが、その代わりに 構造体の埋め込み(Embedding / 構造体のコンポジション) という仕組みにより、既存の構造体の能力を包括・拡張することができます。
package main
import "fmt"
// 基本構造体
type Person struct {
Name string
Age int
}
// Personを埋め込む新しい構造体
type Employee struct {
Person // フィールド名を省略して型名のみを記述(型埋め込み)
Position string
}
func main() {
e := Employee{
Person: Person{Name: "太郎", Age: 40},
Position: "エンジニア",
}
// 埋め込まれた側のフィールドに直接アクセス(フィールドの昇格)
fmt.Println("名前:", e.Name)
fmt.Println("年齢:", e.Age)
fmt.Println("職業:", e.Position)
}
解説:
Employee構造体の中に、変数名を付けずにPerson型名のみを記述することで「型埋め込み(Anonymous Fields)」が成立します。- これにより、埋め込まれた
Personの持つフィールドやメソッドが、さもEmployee自身のものであるかのようにドット演算子で直接アクセス可能になるフィールドの昇格(Promoted Fields)が発生します(例:e.Person.Nameだけでなくe.Nameと書ける)。
実行結果:
名前: 太郎 年齢: 40 職業: エンジニア
学術的・設計的注意点
- 値代入における暗黙のシャローコピーの罠: 構造体はデフォルトで「値型」であるため、
p2 := p1のように別の変数に代入したり、関数の引数にポインタを使わずそのまま渡したりすると、メモリ上で全フィールドの値が複製(シャドーコピー)されます。構造体のサイズが大きい場合、この複製処理はCPUやメモリ帯域に負荷をかけるため、関数の引数やメソッドレシーバでは特別な理由がない限りポインタ(*struct)を使用することが強く推奨されます。 - 可視性とパッケージ間エクスポートの厳格ルール:
struct自体の名前、およびstruct内部の各フィールド名は、先頭の1文字が大文字か小文字かによってパッケージ外への公開範囲が厳格に決定されます。先頭が大文字のフィールド(例:Name)は外部パッケージから自由に読み書きできますが、小文字のフィールド(例:age)は、たとえ構造体自体が外部にエクスポートされていても、他パッケージのコードからは一切見えず、アクセスを試みるとコンパイルエラー(unexported field)になります。 - 構造体の比較可能性(Comparability)の制限: Go言語では、すべての構造体が
==や!=で直接比較できるわけではありません。構造体を構成するすべてのフィールドの型が「比較可能(数値、文字列、ポインタ、配列など)」である場合のみ、構造体同士を直接比較できます。もしフィールドにスライス、マップ、関数などの比較不可能な型(nilとしか比較できない型)が1つでも含まれている場合、その構造体全体が比較不可能となり、コンパイルエラーを引き起こします。
よくある質問
- Q: 構造体のインスタンスを生成したとき、初期化を忘れたフィールドの値はどうなりますか?
- A: Go言語には未定義のメモリ破壊状態(ガベージ値)は存在しません。初期化リテラルで省略された、あるいは単に
var p Personと宣言された構造体の各フィールドは、それぞれの型のゼロ値(数値なら0、文字列なら""、ポインタやスライスならnil)で安全かつ確実に初期化されます。 - Q: ポインタレシーバ(*T)と値レシーバ(T)の使い分けの明確な基準は?
- A: 主な基準は2点です。(1)
メソッド内部で構造体の状態(フィールド)を書き換えたい場合は、必ずポインタレシーバを選択します。(2) 構造体のサイズが大きく、値コピーによるオーバーヘッドを避けたい場合もポインタにします。逆に、一度生成したら状態が変わらない極めて小さな構造体(例:座標を表すPoint{X, Y int}など)の場合に限り、値レシーバが選択されることがあります。 - Q: 構造体のフィールドの後ろについている `json:"name"` のような文字列は何ですか?
- A: それは
構造体タグ(Struct Tag)と呼ばれるメタデータ記述子です。リフレクション(`reflect` パッケージ)を介してプログラム実行時に読み取ることができ、主にjson.MarshalやデータベースのO/Rマッパーなどの外部ライブラリが、構造体のフィールドと外部データ形式(JSONキーやDBカラム名)をマッピングする際のアノテーションとして広く重用されています。 - Q: 構造体の埋め込み(コンポジション)において、親と子で同名のフィールドが競合した場合はどうなりますか?
- A:
EmployeeにNameフィールドがあり、埋め込まれたPersonにもNameがあった場合、e.Nameと記述すると最外殻のEmployee側のフィールドが最優先(シャドーイング)されます。埋め込まれた側のフィールドが消滅するわけではないため、e.Person.Nameとフルパスで明示的に指定すれば競合を回避してアクセス可能です。
まとめ
structは複数の異なるデータ型を1つにまとめ、クラスに代わるカスタムデータ型を定義する予約語。- 関数にレシーバを付与してメソッド化することで、データと振る舞いをカプセル化したクリーンな設計が可能。
- 構造体のコピーを防ぎ、関数内での破壊的更新を許可するには
ポインタ(*struct)を適用する。 - オブジェクト指向の継承の代わりに
型埋め込み(コンポジション)を採用し、フィールドの昇格による柔軟な構造拡張を実現。 - 外部パッケージからのアクセス制御は、フィールド名の
先頭1文字が大文字(公開)か小文字(非公開)かの規則で完全制御。