Go言語の構造体(struct)入門!使い方をわかりやすく解説

2025年4月17日木曜日

Go言語

この記事では、Go言語の構造体(struct)について、基本からしっかり解説していきます!

プログラムを書いていると、「いくつかのデータを、ひとまとめにして扱いたいな」って思う場面、結構ありますよね。

そんな時にめちゃくちゃ便利なのが、Go言語の「構造体」なんです。
この記事を読めば、構造体がどういうもので、どうやって作って、どうやって使うのかが、バッチリわかります。

この記事で学べること

  • 構造体ってそもそも何なのか
  • 構造体の基本的な作り方(定義の仕方)
  • 作った構造体の使い方(データの入れ方・取り出し方)
  • 構造体を使う上でのちょっとしたコツや注意点
  • 実際のプログラム例(コピペして動かせます!)

Go言語の構造体(struct)とは?

さて、まずは「構造体(struct)」が一体何者なのか、そこから見ていきましょう。

難しく考えなくて大丈夫!構造体は、関連するいくつかのデータをひとまとめにするための「型」を作る機能、とイメージしてください。

例えば、ゲームのキャラクターを考えてみましょう。キャラクターには「名前」「HP」「攻撃力」「防御力」…みたいに、いろんな情報がありますよね。
これらをバラバラに管理するのって、ちょっと面倒くさい。変数がいっぱいになっちゃいます。

そこで構造体の出番!「キャラクター」という構造体を作って、その中に「名前」「HP」「攻撃力」「防御力」といったデータを入れる「箱(フィールド)」を用意しておくんです。

こうすれば、「キャラクター」という一つのまとまりで、関連データをスッキリ扱えるようになります。プログラムが見やすくなるし、管理も楽になる、いいことずくめなんですよ!

   +-----------------+
   |  Go Struct: User  |
   | (データの設計図)  |
   +-----------------+
   |       |         |
   |       v         |
   |  +-----------+  |
   |  | フィールド名: |  |
   |  |   Name      |  | <-- 名前(文字列)
   |  | データ型:   |  |
   |  |   string    |  |
   |  +-----------+  |
   |       |         |
   |       v         |
   |  +-----------+  |
   |  | フィールド名: |  |
   |  |   Age       |  | <-- 年齢(整数)
   |  | データ型:   |  |
   |  |   int       |  |
   |  +-----------+  |
   |       |         |
   |       v         |
   |  (他のフィールド)|
   |  (   も追加OK   ) |
   |                 |
   +-----------------+
      ^------------^
       これらのデータを
       一つのまとまりに!

構造体を使うメリット

  • 関連するデータを一つにまとめられる
  • プログラムのコードが整理されて分かりやすくなる
  • データの管理がしやすくなる

Go言語の構造体(struct)の基本的な書き方

じゃあ、その便利な構造体をどうやって作るのか、具体的な書き方を見ていきましょう。

Go言語では、typeキーワードとstructキーワードを組み合わせて構造体を定義します。
基本形はこんな感じです。

type 構造体名 struct {
    フィールド名1 データ型1
    フィールド名2 データ型2
    // ... 必要なだけフィールドを追加
}

typeの後ろに作りたい構造体の名前(自分で好きな名前を付けられます)を書いて、struct { ... }の中に、その構造体に持たせたいデータ(フィールド)の名前とデータ型を書いていきます。

構造体型を定義する

実際に簡単な例で見てみましょう。人の「名前(Name)」と「年齢(Age)」を持つ「User」という構造体を定義してみます。

package main

import "fmt"

// User構造体の定義
type User struct {
    Name string // 名前を入れるフィールド (文字列型)
    Age  int    // 年齢を入れるフィールド (整数型)
}

func main() {
    // ここで定義したUser型を使う (使い方は後ほど!)
    fmt.Println("User構造体を定義しました!")
}

こんな風に書けば、Userというオリジナルの型が完成です!

ポイントは、構造体の名前(ここではUser)は、通常、大文字で始めるのがGo言語の慣習です。他のパッケージから利用できるようにするため、という意味合いもあります(詳しくは次の項目で!)。

フィールドの命名規則とデータ型

構造体の中に入れるデータ項目を「フィールド」と呼びます。

フィールドの名前は、変数名と同じように、分かりやすい名前を付けるのが基本です。Go言語では、複数の単語からなる名前は「キャメルケース」(例:`userName`, `maxHp`)で書くのが一般的ですね。

そして、フィールドには、string(文字列)やint(整数)、bool(真偽値)といった基本的なデータ型はもちろん、他の構造体や、配列・スライス、マップなど、いろんな型のデータを指定できます。

ここで一つ、すごく大事なルールがあります!それは、フィールド名の最初の文字が大文字か小文字かで、そのフィールドが「公開」されるか「非公開」になるかが決まる、ということです。

  • 大文字始まりのフィールド (例: Name)
    パッケージの外(別のファイル)からでもアクセスできる「公開フィールド」になります。
  • 小文字始まりのフィールド (例: age)
    定義されたパッケージの中(同じファイルや同じフォルダ内)からしかアクセスできない「非公開フィールド」になります。

他のプログラムから使われる可能性がある構造体を作る場合は、この大文字・小文字の使い分けを意識しましょう!

Go言語の構造体(struct)の実践的な使い方

さて、構造体の「設計図」(型定義)ができたところで、いよいよ実際に使ってみましょう!

設計図から、具体的なデータを持った実体を作ることを「インスタンス化」と呼びます。難しく聞こえるかもですが、「構造体型の変数を作る」くらいの感覚でOKです。

そして、作ったインスタンスの各フィールドにデータを入れたり、入っているデータを取り出したりすることを「アクセス」と言います。

構造体のインスタンス化(初期化)方法

構造体のインスタンスを作る(初期化する)方法はいくつかあります。代表的なものを紹介しますね。

1. フィールド名を指定して初期化
これが一番分かりやすくて、よく使われる方法かもしれません。

// User構造体の定義 (再掲)
type User struct {
    Name string
    Age  int
}

func main() {
    // フィールド名を指定して初期化
    user1 := User{Name: "太郎", Age: 25}
    fmt.Println(user1) // 出力: {太郎 25}
}

構造体名{フィールド名1: 値1, フィールド名2: 値2, ...} のように書きます。フィールド名を指定するので、順番を気にする必要がないのがメリットです。

2. フィールド名を省略して初期化
定義したフィールドの順番通りに値を指定する方法です。

// User構造体の定義 (再掲)
type User struct {
    Name string
    Age  int
}

func main() {
    // フィールド名を省略して初期化 (定義順に値を書く)
    user2 := User{"次郎", 30}
    fmt.Println(user2) // 出力: {次郎 30}
}

ただし、この方法はフィールドの順番を正確に覚えていないといけないのと、後からフィールドを追加したり順番を変えたりすると修正が必要になるので、少し注意が必要です。

3. ゼロ値で初期化
値を何も指定しない場合、各フィールドはそれぞれのデータ型の「ゼロ値」で初期化されます。

// User構造体の定義 (再掲)
type User struct {
    Name string // stringのゼロ値は "" (空文字列)
    Age  int    // intのゼロ値は 0
}

func main() {
    // ゼロ値で初期化 (何も指定しない)
    var user3 User
    fmt.Println(user3) // 出力: { 0}  (Nameが空文字列, Ageが0)
}

4. new関数で初期化
new関数を使うと、構造体のポインタ(後で説明します)が作られ、各フィールドはゼロ値で初期化されます。

// User構造体の定義 (再掲)
type User struct {
    Name string
    Age  int
}

func main() {
    // new関数で初期化 (ポインタが返る)
    user4 := new(User)
    fmt.Println(user4) // 出力: &{ 0} (ポインタのアドレスが表示され、値はゼロ値)
}

どの方法を使うかは状況によりますが、まずはフィールド名を指定する方法に慣れるのがおすすめです!

フィールドへのアクセスと値の変更

インスタンスができたら、その中身(フィールドの値)を見たり、変えたりしたくなりますよね。
フィールドへのアクセスは、ドット演算子 . を使います。すごく簡単!

package main

import "fmt"

// User構造体の定義
type User struct {
    Name string
    Age  int
}

func main() {
    user := User{Name: "三郎", Age: 20}

    // フィールドの値を取得して表示
    fmt.Println("名前:", user.Name) // 出力: 名前: 三郎
    fmt.Println("年齢:", user.Age)  // 出力: 年齢: 20

    // フィールドの値を変更
    user.Age = 21 // 年齢を21に変更
    fmt.Println("変更後の年齢:", user.Age) // 出力: 変更後の年齢: 21

    // 値の取得も変更も、この「変数名.フィールド名」という書き方を使います。
}

このように、変数名.フィールド名 で、そのフィールドに簡単にアクセスできるんです。

実際のサンプルコードと実行結果

では、これまでの内容を全部盛り込んだ、簡単なプログラム例を見てみましょう。
「商品(Product)」を表す構造体を定義して、インスタンスを作り、情報を表示してみます。

package main

import "fmt"

// 商品を表す構造体
type Product struct {
	ID    int    // 商品ID (公開)
	Name  string // 商品名 (公開)
	price int    // 価格 (非公開) - 同じパッケージ内からはアクセス可能
}

func main() {
	// 商品インスタンスを作成 (フィールド名指定)
	item1 := Product{
		ID:    101,
		Name:  "すごいリンゴ",
		price: 150, // 同じパッケージ(main)なのでアクセス可能
	}

	// 商品情報を表示
	fmt.Println("--- 商品情報 ---")
	fmt.Println("ID:", item1.ID)
	fmt.Println("商品名:", item1.Name)
	fmt.Println("価格:", item1.price) // priceは小文字始まりだが、同じパッケージ内なのでOK

	// 価格を変更
	item1.price = 180
	fmt.Println("値上げ後の価格:", item1.price)

    // このように、定義・インスタンス化・アクセスを組み合わせることで、
    // 構造体を使ったプログラムが動きます。
}

ソースコードの表示結果

--- 商品情報 ---
ID: 101
商品名: すごいリンゴ
価格: 150
値上げ後の価格: 180

どうでしょう?構造体を使うイメージが湧いてきましたか?

Go言語の構造体(struct)のポインタ:なぜ使うのか?

ここでちょっとステップアップして、「ポインタ」の話をします。

Go言語では、構造体を関数の引数として渡したり、関数から返したりする際に、「ポインタ」を使うことがよくあります。

「ポインタって何だっけ…?」という方も大丈夫。簡単に言うと、データそのものではなく、「データがメモリ上のどこにあるか」という場所情報(アドレス)を指し示すものです。

ポインタについては、こちらの記事で詳しく解説していますので気になる方は御覧ください。

じゃあ、なぜ構造体でポインタを使うのでしょうか?主な理由は二つあります。

  1. 効率が良いから
    構造体がたくさんのフィールドを持っていてサイズが大きい場合、関数に渡すたびにデータ全体をコピーするのは非効率です。ポインタ(場所情報だけ)を渡せば、コピーするデータ量が少なく済み、プログラムが速く動くことがあります。
  2. 元の構造体の値を変更したいから
    関数に普通の構造体(値渡し)を渡すと、関数の中ではコピーされたものが使われます。そのため、関数内でフィールドの値を変更しても、元の構造体には影響しません。
    一方、構造体のポインタを渡せば、関数は元の構造体の場所を知っているので、関数内でフィールドの値を変更すると、元の構造体の値もちゃんと変わります

ポインタのインスタンスを作るには、new関数を使うか、既存のインスタンスのアドレスを取得する&演算子を使います。

package main

import "fmt"

type Point struct {
	X int
	Y int
}

// Point構造体のポインタを受け取り、X座標を増やす関数
func moveRight(p *Point) { // 引数がポインタ (*Point)
	p.X = p.X + 10 // ポインタ経由でフィールドにアクセス
    // Goでは p.X と書くだけでOK ( (*p).X と書かなくても良い )
}

func main() {
	// newでポインタを作成
	p1 := new(Point) // p1は *Point 型
	p1.X = 100
	p1.Y = 200
	fmt.Println("p1(new):", p1) // &{100 200}

	// 既存のインスタンスのアドレスを取得
	p2 := Point{X: 5, Y: 5}
	p3 := &p2 // p3は *Point 型 (p2のアドレスを持つ)
	fmt.Println("p2:", p2)  // {5 5}
	fmt.Println("p3(&p2):", p3) // &{5 5}

    // ポインタを関数に渡して値を変更する例
    moveRight(p3) // p3 (p2のアドレス) を渡す
    fmt.Println("moveRight後のp2:", p2) // 出力: moveRight後のp2: {15 5} ← 元のp2の値が変わっている!
    fmt.Println("moveRight後のp3:", p3) // 出力: moveRight後のp3: &{15 5}
}

ポインタは最初は少し難しく感じるかもしれませんが、Go言語ではよく使われるので、徐々に慣れていきましょう!

Go言語の構造体(struct)のネスト(入れ子構造)

構造体の便利な機能の一つに、「ネスト(入れ子)」があります。
これは、ある構造体のフィールドとして、別の構造体型を指定できる機能です。

例えば、「ユーザー(User)」構造体が、「名前」や「年齢」だけでなく、「住所(Address)」情報も持ちたい、という場合を考えてみましょう。

住所も「都道府県」「市区町村」「番地」など、複数のデータで構成されますよね。
そんな時、まず「住所」を表すAddress構造体を定義し、それをUser構造体のフィールドとして埋め込むことができます。

package main

import "fmt"

// 住所を表す構造体
type Address struct {
	Prefecture string // 都道府県
	City       string // 市区町村
}

// ユーザーを表す構造体
type User struct {
	Name    string
	Age     int
	// Address構造体をフィールドとしてネスト!
	HomeAddress Address // フィールド名はHomeAddress, 型はAddress
}

func main() {
	// ネストした構造体のインスタンスを作成
	user := User{
		Name: "花子",
		Age:  30,
		HomeAddress: Address{ // ネストされたAddress構造体も初期化
			Prefecture: "東京都",
			City:       "渋谷区",
		},
	}

	// ネストされたフィールドへのアクセス
	fmt.Println("名前:", user.Name)
	fmt.Println("都道府県:", user.HomeAddress.Prefecture) // user.Addressフィールドの、さらにPrefectureフィールドにアクセス
	fmt.Println("市区町村:", user.HomeAddress.City)

    // このように、User構造体の中にAddress構造体を入れることで、
    // 関連性の高い情報をグループ化して、より分かりやすく管理できます。
}

ソースコードの表示結果

名前: 花子
都道府県: 東京都
市区町村: 渋谷区

アクセスする時は、user.HomeAddress.Prefecture のように、ドット.を繋げていけばOKです。便利でしょ?

Go言語の構造体(struct)の注意点とベストプラクティス

構造体はとっても便利ですが、いくつか気をつけておくと良い点や、より良い使い方があります。

ゼロ値を意識する

インスタンスを作る際に値を指定しなかったフィールドは、自動的に「ゼロ値」(数値なら0、文字列なら""、真偽値ならfalse、ポインタならnilなど)で初期化されます。意図しないゼロ値になっていないか、確認する癖をつけると良いでしょう。

フィールドの可視性(大文字・小文字)を使い分ける

外部のパッケージから使われる構造体なのか、内部だけで使う構造体なのかを考えて、フィールド名の大文字・小文字を適切に使い分けることが、Goらしいコードを書くコツです。むやみに全て大文字にするのは避けましょう。

ネストはほどほどに

ネストは便利ですが、深くしすぎると、かえって構造が複雑になり、アクセスも長くなってしまいます(例:`user.Profile.Settings.Notification.Email`みたいな…)。適切な粒度で構造体を分けることを意識しましょう。

フィールド名は分かりやすく

これは構造体に限りませんが、フィールド名はその役割がすぐに分かるような、具体的で分かりやすい名前を付けましょう。未来の自分や他の人が読んでも理解できるように!

初期化はフィールド名指定がおすすめ

前述の通り、フィールド名を指定する初期化方法(User{Name: "太郎", Age: 25})は、順番を気にせず、どのフィールドに何を設定しているか一目瞭然なので、コードが読みやすくなり、おすすめです。

【まとめ】Go言語の構造体(struct)をマスターしてコードを整理しよう

お疲れ様でした!今回はGo言語の基本であり、めちゃくちゃ重要な「構造体(struct)」について、その概念から定義、使い方、ポインタ、ネスト、注意点まで、一通り見てきました。
構造体は、関連するデータをまとめて扱うための強力な武器です。これを使いこなせるようになると、Goのプログラムがグッと整理されて、見やすく、そして管理しやすくなりますよ!

最初はちょっと戸惑う部分もあるかもしれませんが、この記事のサンプルコードを実際に動かしてみたり、自分のプログラムで使ってみたりするうちに、きっと感覚が掴めてくるはずです。

構造体をマスターすれば、より複雑なデータ構造も扱えるようになり、Goプログラミングがもっと楽しくなること間違いなし!

【関連記事】 Go言語とは?特徴・できること・将来性

このブログを検索

  • ()

自己紹介

自分の写真
リモートワークでエンジニア兼Webディレクターとして活動しています。プログラミングやAIなど、日々の業務や学びの中で得た知識や気づきをわかりやすく発信し、これからITスキルを身につけたい人にも役立つ情報をお届けします。 note → https://note.com/yurufuri X → https://x.com/mnao111

QooQ