[Goのはじめ方] Part13: 構造体(struct)の定義と初期化

Go

Go言語で独自のデータ型を定義する方法を学びましょう!

構造体(struct)とは? 🤔

構造体(struct)は、複数の異なる型のデータをひとまとめにして、新しい独自のデータ型を定義するための仕組みです。例えば、「ユーザー」情報を管理したい場合、名前(文字列)、年齢(整数)、メールアドレス(文字列)といった複数の情報を一つの「ユーザー」型として扱えるようになります。

オブジェクト指向プログラミングにおけるクラスの概念に似ていますが、Goの構造体はよりシンプルです。関連するデータをグループ化するのに非常に便利です。

構造体の定義方法 📝

構造体は type キーワードと struct キーワードを使って定義します。type の後に新しい型名を、struct の後に波括弧 {} で囲んでフィールド(構造体のメンバー変数)を定義します。各フィールドは「フィールド名 型」の形式で記述します。

package main

import "fmt"

// User構造体の定義
type User struct {
    Name  string // ユーザー名
    Age   int    // 年齢
    Email string // メールアドレス
}

func main() {
    // この時点ではまだ構造体を使っていません
    fmt.Println("User構造体を定義しました!")
}

上記の例では、User という名前の新しい型を定義しました。この型は Name(文字列)、Age(整数)、Email(文字列)という3つのフィールドを持っています。

フィールド名の最初の文字を大文字にすると、他のパッケージからアクセス可能な「公開(exported)」フィールドになります。小文字で始めると、同じパッケージ内からのみアクセス可能な「非公開(unexported)」フィールドになります。これはGoの重要なルールの1つです。

構造体の初期化 🚀

定義した構造体を使うには、まず「初期化」して具体的な値を持つ変数(インスタンス)を作成する必要があります。初期化にはいくつかの方法があります。

1. フィールド名を指定して初期化 (推奨)

構造体名{ フィールド名1: 値1, フィールド名2: 値2, ... } の形式で初期化します。フィールド名を指定するため、順序を気にする必要がなく、どのフィールドにどの値を入れるかが明確です。一部のフィールドのみを初期化することも可能で、その場合、指定されなかったフィールドはそれぞれの型のゼロ値で初期化されます。この方法が最も安全で読みやすいため、一般的に推奨されます ✅。

package main

import "fmt"

type User struct {
    Name  string
    Age   int
    Email string
}

func main() {
    // フィールド名を指定して初期化
    user1 := User{
        Name:  "Alice",
        Age:   30,
        Email: "alice@example.com", // 末尾のカンマは付けても付けなくても良いですが、複数行の場合は付けるのが一般的です
    }
    fmt.Printf("User1: %+v\n", user1) // %+v を使うとフィールド名付きで表示されて便利です

    // 一部のフィールドのみ初期化も可能
    user2 := User{
        Name: "Bob",
        Age:  25,
        // Emailは初期化しない → "" (文字列のゼロ値) になる
    }
    fmt.Printf("User2: %+v\n", user2)

    // 順番を変えても問題ない
    user3 := User{
        Email: "carol@sample.net",
        Name:  "Carol",
        Age:   35,
    }
    fmt.Printf("User3: %+v\n", user3)
}

2. フィールド名を省略して初期化(順序指定)

構造体名{ 値1, 値2, ... } の形式で、構造体定義時のフィールドの順序通りに値を指定する方法です。この方法を使う場合は、定義されている全てのフィールドに対して値を指定する必要があります。簡潔に書けますが、フィールドの順序や数に変更があった場合にコードの修正が必要となり、間違いやすいため、あまり推奨されません 🤔。

package main

import "fmt"

type User struct {
    Name  string
    Age   int
    Email string
}

func main() {
    // フィールド名を省略して初期化(定義順: Name, Age, Email)
    user4 := User{"David", 40, "david@example.com"} // 全てのフィールドを定義順に指定する必要がある
    fmt.Printf("User4: %+v\n", user4)
}
注意: この方法では、構造体の定義でフィールドを追加したり、順序を変更したりすると、初期化部分のコードがエラーになるか、意図しない値が設定される可能性があります。

3. ゼロ値による初期化

変数を var キーワードで宣言するだけで、明示的に初期値を指定しない場合、構造体の各フィールドはその型の「ゼロ値」で初期化されます。

Goにおける主な型のゼロ値:

  • 数値型(int, float64 など): 0
  • string 型: ""(空文字列)
  • bool 型: false
  • ポインタ、interface、スライス、チャネル、マップ、関数型: nil
package main

import "fmt"

type Config struct {
    Host    string
    Port    int
    Enabled bool
}

func main() {
    // var宣言によるゼロ値初期化
    var cfg Config
    // cfg の値は Config{Host:"", Port:0, Enabled:false} となる
    fmt.Printf("Config (ゼロ値): %+v\n", cfg)
}

4. `new` 関数を使った初期化(ポインタ)

new(型) 関数を使うと、指定した型のゼロ値で初期化された値へのポインタ(メモリアドレス)を生成できます。つまり、構造体の値そのものではなく、構造体へのポインタ (*User のような型) が返されます。

package main

import "fmt"

type User struct {
    Name  string
    Age   int
    Email string
}

func main() {
    // newを使って初期化(User型へのポインタ *User が返る)
    user5Ptr := new(User) // user5Ptr は *User 型
    // この時点では user5Ptr はゼロ値で初期化されたUser構造体を指している
    // (*user5Ptr の値は User{Name:"", Age:0, Email:""})
    fmt.Printf("User5 Pointer (ゼロ値): %+v\n", user5Ptr)

    // ポインタ経由でフィールドにアクセスして値を設定(後述)
    user5Ptr.Name = "Eve"
    user5Ptr.Age = 50
    // Emailは設定しないのでゼロ値 "" のまま
    fmt.Printf("User5 Pointer (値設定後): %+v\n", user5Ptr)
    fmt.Printf("User5 Value (参照外し): %+v\n", *user5Ptr) // アスタリスク(*)でポインタが指す先の値を取得
}

ポインタについては、このステップの後半「ポインタと参照」で詳しく学びます。今は「new を使うと構造体のポインタが得られ、フィールドはゼロ値で初期化される」という点を覚えておきましょう。

初期化方法の比較表

初期化方法 書き方例 特徴 推奨度 戻り値の型
フィールド名指定 User{Name:"A", Age:10} 順序不問、可読性高、一部初期化可 高 ✅ 構造体 (User)
フィールド名省略 User{"B", 20, "b@ex.com"} 全フィールド必須、定義順依存、変更に弱い 低 🤔 構造体 (User)
ゼロ値 var u User 全フィールドがゼロ値で初期化 中 (用途による) 構造体 (User)
new関数 new(User) ゼロ値で初期化、ポインタを返す 中 (ポインタが必要な場合) 構造体へのポインタ (*User)

フィールドへのアクセス 👆

構造体の変数(インスタンス)が作成できたら、ドット演算子 . を使って各フィールドの値を取得したり、新しい値を設定したりできます。これは非常に直感的です。

package main

import "fmt"

type Point struct {
    X int
    Y int
}

func main() {
    // フィールド名を指定して初期化
    p1 := Point{X: 10, Y: 20}

    // ドット(.)を使ってフィールドの値を取得
    fmt.Println("初期値:")
    fmt.Println("  p1.X:", p1.X) // 出力: p1.X: 10
    fmt.Println("  p1.Y:", p1.Y) // 出力: p1.Y: 20

    // ドット(.)を使ってフィールドに新しい値を設定
    p1.X = 15
    p1.Y = 25
    fmt.Println("変更後:")
    fmt.Println("  p1.X:", p1.X) // 出力: p1.X: 15
    fmt.Println("  p1.Y:", p1.Y) // 出力: p1.Y: 25

    // ポインタの場合もドット演算子でアクセス可能
    p2Ptr := new(Point) // p2Ptr は *Point 型 (ゼロ値 Point{X:0, Y:0} へのポインタ)
    fmt.Printf("p2Ptr初期値 (ポインタ経由): X=%d, Y=%d\n", p2Ptr.X, p2Ptr.Y) // 出力: p2Ptr初期値 (ポインタ経由): X=0, Y=0

    // ポインタ変数でもドット(.)でフィールドにアクセスできる (Goの便利な機能)
    p2Ptr.X = 5
    p2Ptr.Y = 8
    fmt.Printf("p2Ptr変更後 (ポインタ経由): X=%d, Y=%d\n", p2Ptr.X, p2Ptr.Y) // 出力: p2Ptr変更後 (ポインタ経由): X=5, Y=8

    // (参考) 厳密には (*p2Ptr).X のように書くところを、Goが p2Ptr.X と書けるようにしている
    fmt.Printf("p2Ptr変更後 (明示的な参照外し): X=%d, Y=%d\n", (*p2Ptr).X, (*p2Ptr).Y) // 出力: p2Ptr変更後 (明示的な参照外し): X=5, Y=8
}

まとめ 🎉

今回はGo言語の構造体(struct)の基本となる定義方法と初期化方法について学びました。

  • 定義: type 名前 struct { フィールド名 型 ... } で独自のデータ型を作成します。
  • 初期化:
    • フィールド名を指定する T{フィールド1: 値1, ...} が最も推奨される方法です。
    • フィールド名を省略する方法 T{値1, 値2, ...} は注意が必要です。
    • var 宣言でゼロ値で初期化したり、new(T) でゼロ値のポインタを生成したりできます。
  • アクセス: ドット演算子 . を使ってフィールドの読み書きを行います (ポインタの場合も同様)。

構造体は、関連するデータをまとめて扱うための強力なツールです。Goプログラミングにおいて非常に頻繁に使われます。

次のレッスン「構造体へのメソッド定義」では、今回作成した構造体に、関連する操作(関数)を追加する方法を学びます。お楽しみに!💪

コメント

タイトルとURLをコピーしました