構造体一覧

【Go入門】構造体の基本(2) – 構造体とメソッド

こんにちは。Go入門ブログの第12回です。
本記事では、構造体メソッドについて解説していきます。

前回の記事で解説したとおり、Goにおいて構造体とは、オブジェクト指向言語におけるクラスのように重要なものです。
その構造体と手続きを結びつけるための機能がメソッドです。

オブジェクト指向言語のクラスにもメソッドは存在しますが、Goのメソッドとはこれとは意味合いがやや異なります。
Goにおけるメソッドとは、任意の型に特化した関数を定義する為の仕組みといえます。

メソッドの定義

メソッドの定義には関数と同様に、funcステートメントを使用します。
関数と異なる点として、funcとメソッド名の間にレシーバの型と変数名を記述します。

func ([レシーバ変数名] [レシーバの型名]) [メソッド名]()

レシーバとは、メソッドを呼び出される対象と説明されます。
たとえば、構造体AからメソッドAを使用したい場合、メソッドAのレシーバは構造体Aという関係になります。

以下の例では、構造体Numsと、Nums(のポインタ)をレシーバとするメソッドMultiを定義しています。

package main

import "fmt"

type Nums struct {
    x int
    y int
}

// メソッドMultiの定義
func (nums Nums) Multi() int {
    return nums.x * nums.y
}

func main() {
    // 構造体Numsを生成
    nums := Nums{x: 2, y: 4}
    // メソッドMultiの実行
    fmt.Println(nums.Multi())
}

定義されたメソッドは、19行目のように[レシーバ].[メソッド]の形式で呼び出すことができます。

このサンプルの実行結果は以下のようになります。

$ go run main.go
8

エイリアスへのメソッド定義

構造体だけではなく、任意の型(type)にもメソッドを定義することができます。
以下の例では、int型へのエイリアスであるMyInt型を定義し、その型に対してメソッドを定義しています。

package main

import "fmt"

type MyInt int

// メソッドMultiの定義
func (myint MyInt) Multi() int {
    return int(myint) * 2
}

func main() {
    fmt.Println(MyInt(10).Multi())
}

このサンプルの実行結果は以下のようになります。

$ go run main.go
20

ポインタレシーバを用いたメソッド定義

メソッドのレシーバをポインタ型変数にすることができます。
この場合、関数と同様に、レシーバは参照渡しとなります。

package main

import "fmt"

type Nums struct {
    x int
    y int
}

// メソッドdubleの定義
func (nums *Nums) double() {
    nums.x = nums.x * 2
    nums.y = nums.y * 2
}

func main() {
    // 構造体Numsを生成
    nums := Nums{x: 2, y: 4}
    // メソッドdoubleの実行
    nums.double()
    fmt.Println(nums)
}

このサンプルの実行結果は以下のようになります。

$ go run main.go
{4 8}
ポインタ型のレシーバを使用すべき理由

原則として、構造体に定義するメソッドのレシーバはポインタ型にすべきです。

値型のレシーバを使用した場合、メソッドには構造体が値渡しで渡されます。
したがって、元の構造体のコピーが作成され、メソッドに渡されることになります。
これはすなわち、メソッドを呼び出している構造体と、メソッド内で処理される構造体が別のデータであることを意味します。

このような操作は、メソッドの本来的な意味合い・役割から考えて不自然な挙動であり、混乱を招く原因となるでしょう。
メソッド内で構造体のコピーを取り扱う明確な理由がある場合を除き、は構造体のメソッドのレシーバはポインタ型にすべきです。

メソッドと関数の違い

メソッドは、レシーバの定義を必要とする点、レシーバ専用の手続きとして機能する点で通常の関数と異なります。
しかし、メソッドの実体そのものは、レシーバを第1引数として取る関数にすぎません。
したがって、メソッドを関数型として参照することができます。

以下のサンプルは、先ほどのコードを元に、メソッドdoubleを関数として参照するよう書き換えたものです。

package main

import "fmt"

type Nums struct {
    x int
    y int
}

// メソッドdubleの定義
func (nums *Nums) double() {
    nums.x = nums.x * 2
    nums.y = nums.y * 2
}

func main() {
    // 構造体Numsを生成
    nums := Nums{x: 2, y: 4}
    // メソッドdoubleを関数型として参照し変数に代入
    f := (*Nums).double
    // メソッドdoubleを代入した関数fの実行
    f(&nums)
    fmt.Println(nums)
}

実行結果も先の例とまったく同じになります。

$ go run main.go
{4 8}

終わりに

前回の記事から2回にわたり、構造体について解説してきました。
構造体とメソッドの定義、構造体に定義されているフィールドやメソッドの操作、ポインタによる参照渡しといった
これらはいずれもGoで構造体を取り扱うための基本的かつ中心的な技術です。
ここまでの基本知識だけでも、多くのケースに対応できるはずです。

次回の記事では、構造体とならんでGoプログラミングの中心となるインターフェースについて解説します。


スポンサーリンク
スポンサーリンク
スポンサーリンク