ksg

ksg@software_engineer

Portfolio Blog

Go2026/07/15

【中学生でもわかる】Go言語入門④構造体とインターフェースを理解しよう(構造体とインターフェース編)

【中学生でもわかる】Go言語入門④構造体とインターフェースを理解しよう(構造体とインターフェース編)

前回のおさらい

前回は、関数とパッケージについて学びました。今回は、複数のデータをひとまとめにして扱うための 構造体(こうぞうたい) と、Goらしいプログラムを書く上で欠かせない インターフェース という考え方を学びます。少し難しく感じる回ですが、1つずつ確認していけば大丈夫です。

構造体ってなに?

これまでは、name := "太郎"age := 14 のように、1つの変数に1つのデータだけを入れてきました。しかし実際のプログラムでは、「1人の人物に関する、名前・年齢・身長といった複数のデータを、まとめて扱いたい」ということがよくあります。

こういうとき、それぞれをバラバラの変数として管理するのは大変です。

name1 := "太郎"
age1 := 14
name2 := "花子"
age2 := 13

これだと、「name1age1は同じ人物のデータだ」ということが、名前からしかわかりません。人数が増えるとさらに管理が大変になります。

そこで登場するのが構造体です。構造体は、「複数の、種類が違うデータをひとまとめにした、オリジナルの型」 です。

学級名簿をイメージするとわかりやすいかもしれません。名簿の1行には、「名前」「年齢」「身長」といった、複数の項目がセットになって書かれていますよね。構造体は、まさにこの「1行分の項目セット」を、プログラムの中で定義するためのものです。

構造体を作ってみよう

「人物」を表す構造体を作ってみましょう。

package main

import "fmt"

type Person struct {
	Name   string
	Age    int
	Height float64
}

func main() {
	taro := Person{
		Name:   "太郎",
		Age:    14,
		Height: 160.5,
	}

	fmt.Println(taro.Name)
	fmt.Println(taro.Age)
	fmt.Println(taro.Height)
}

実行結果は次のようになります。

太郎
14
160.5

1つずつ見ていきましょう。

type Person struct {
	Name   string
	Age    int
	Height float64
}

これは、「Person(パーソン、人物)という名前の、新しい型を定義しますよ」という宣言です。type 型名 struct { ... } という形が決まった書き方です。{ } の中には、この構造体が持つ項目(フィールドと呼びます)を、「フィールド名 型」の形で並べます。ここでは、Name(文字列)、Age(整数)、Height(小数)という3つのフィールドを持つ、Person型を定義しています。

taro := Person{
	Name:   "太郎",
	Age:    14,
	Height: 160.5,
}

こちらは、実際にPerson型のデータ(インスタンスと呼ぶこともあります)を1つ作って、taroという変数に入れている部分です。「学級名簿のフォーマット(Person型)に沿って、太郎さんの実際のデータを1行分書いた」というイメージです。

fmt.Println(taro.Name)

構造体の中の、それぞれのフィールドの値を取り出したいときは、変数名.フィールド名 という書き方をします。taro.Name で「taroさんのNameフィールドの値」、つまり "太郎" を取り出せます。

このように構造体を使うことで、関連する複数のデータを、意味のあるまとまりとして管理できるようになります。

構造体にメソッドをつける

構造体には、その構造体専用の関数を持たせることができます。これをメソッドと呼びます。

package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

func (p Person) Introduce() string {
	return fmt.Sprintf("私は%sです。%d歳です。", p.Name, p.Age)
}

func main() {
	taro := Person{Name: "太郎", Age: 14}
	fmt.Println(taro.Introduce())
}

実行結果は次のようになります。

私は太郎です。14歳です。

func (p Person) Introduce() string { ... } の、(p Person) の部分がポイントです。これはレシーバーと呼ばれ、「この関数は、Person型専用の関数(メソッド)ですよ。関数の中では、対象のPersonのデータをpという名前で扱えますよ」という意味になります。

呼び出す側では、taro.Introduce() のように、変数名.メソッド名() という形で呼び出します。第3回で学んだ普通の関数とは違い、「Person型のデータに対して行う専用の処理」として、Person型のデータにくっつけて呼び出せる、というのがメソッドの特徴です。

なお、fmt.Sprintf という新しい関数が出てきました。これは fmt.Println の仲間で、「画面に表示する」代わりに「文字列を組み立てて返す」関数です。%s の部分に文字列を、%d の部分に整数を、それぞれ順番にあてはめて、1つの文字列を作ってくれます。

インターフェースってなに?

最後に、この回でいちばん理解が難しいインターフェースについて説明します。

インターフェースを一言で表すと、「これができれば、なんという型でもOKですよ、という約束事」 です。

たとえるなら、「電気製品の、コンセントの規格」をイメージしてください。日本の家庭用コンセントの形さえ合っていれば、それが掃除機であろうと、テレビであろうと、ドライヤーであろうと、同じコンセントに挿すことができますよね。コンセント側は、「挿すものが掃除機かテレビか」を気にしていません。「コンセントの形(規格)さえ合っていればOK」というルールになっています。

インターフェースも同じ考え方です。「このメソッドさえ持っていれば、型が何であっても、この処理の中で扱えますよ」というルールを定義するものです。

実際にコードで見てみましょう。「鳴く」というSound()メソッドを持つ動物であれば、なんでも扱える、というプログラムです。

package main

import "fmt"

type Animal interface {
	Sound() string
}

type Dog struct{}

func (d Dog) Sound() string {
	return "ワン!"
}

type Cat struct{}

func (c Cat) Sound() string {
	return "ニャー"
}

func introduce(a Animal) {
	fmt.Println(a.Sound())
}

func main() {
	dog := Dog{}
	cat := Cat{}

	introduce(dog)
	introduce(cat)
}

実行結果は次のようになります。

ワン!
ニャー

順番に確認しましょう。

type Animal interface {
	Sound() string
}

これが、インターフェースの定義です。「Animalという名前のインターフェースは、Sound() stringというメソッド(引数なしで、string型を返すメソッド)を持っていることを約束します」という意味です。中身の処理は書かず、「どんなメソッドを持っているべきか」というルールだけを定義します。

type Dog struct{}

func (d Dog) Sound() string {
	return "ワン!"
}

Dogという構造体(中身のフィールドはなし)に、Sound()メソッドを実装しています。Catも同様です。

ここで重要なポイントがあります。Goでは、「この構造体はAnimalインターフェースです」と明示的に宣言する必要が ありませんSound() string というメソッドを持ってさえいれば、DogCatも、自動的にAnimalインターフェースとして扱えるようになります。これを、Goでは「ダックタイピング」ならぬ構造的部分型と呼んだりします。「アヒルのように歩き、アヒルのように鳴くなら、それはアヒルだとみなそう」という考え方です。

func introduce(a Animal) {
	fmt.Println(a.Sound())
}

このintroduce関数は、「Animalインターフェースを満たしている型なら、なんでも受け取れる」関数です。中身ではa.Sound()を呼び出しているだけで、「渡されたのがDogなのかCatなのか」を気にしていません。

introduce(dog)
introduce(cat)

Dog型のdogも、Cat型のcatも、どちらもSound()メソッドを持っているので、同じintroduce関数に渡すことができます。それぞれのSound()の中身が違うので、実行結果も「ワン!」「ニャー」と、それぞれの動物らしい結果になる、というわけです。

インターフェースは何がうれしいの?

「型ごとに専用のintroduceDog関数、introduceCat関数を作ればいいのでは?」と思うかもしれません。ですが、動物の種類が増えるたびに専用の関数を作っていては、キリがありません。

インターフェースを使うと、「Sound()メソッドさえ持っていれば、将来Bird(鳥)やCow(牛)が増えても、introduce関数はそのまま使い回せる」という、柔軟で拡張しやすいプログラムを書けるようになります。この考え方は、少し規模の大きいプログラムを書くようになると、非常によく使うことになります。

まとめ

今回は、以下のことを学びました。

  • 構造体は「複数の、種類が違うデータをひとまとめにした型」で、type 名前 struct { ... } で定義する
  • 構造体には、専用の関数である「メソッド」を持たせることができる
  • インターフェースは「特定のメソッドさえ持っていれば、型を問わず同じように扱える、という約束事」
  • Goでは、明示的な宣言なしに、必要なメソッドを持っている型は自動的にインターフェースを満たしたことになる

構造体とインターフェースは、Goで少し本格的なプログラムを書くようになると、必ずと言っていいほど登場する重要な考え方です。最初は難しく感じても大丈夫なので、何度もコードを書いて動かしながら、少しずつ慣れていきましょう。

次回は、Goの大きな特徴の1つである、複数の処理を同時に進める仕組み「ゴルーチン」と「チャネル」について学んでいきます。

次回:「Go言語入門⑤ゴルーチン・チャネル(並行処理)編」お楽しみに。

おすすめの記事