こんにちは。Go入門ブログの第12回です。
本記事では、構造体とメソッドについて解説していきます。
前回の記事で解説したとおり、Goにおいて構造体とは、オブジェクト指向言語におけるクラスのように重要なものです。
その構造体と手続きを結びつけるための機能がメソッドです。
オブジェクト指向言語のクラスにもメソッドは存在しますが、Goのメソッドとはこれとは意味合いがやや異なります。
Goにおけるメソッドとは、任意の型に特化した関数を定義する為の仕組みといえます。
メソッドの定義
メソッドの定義には関数と同様に、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行目のように[レシーバ].[メソッド]の形式で呼び出すことができます。
このサンプルの実行結果は以下のようになります。
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()) }
このサンプルの実行結果は以下のようになります。
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) }
このサンプルの実行結果は以下のようになります。
{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) }
実行結果も先の例とまったく同じになります。
{4 8}
終わりに
前回の記事から2回にわたり、構造体について解説してきました。
構造体とメソッドの定義、構造体に定義されているフィールドやメソッドの操作、ポインタによる参照渡しといった
これらはいずれもGoで構造体を取り扱うための基本的かつ中心的な技術です。
ここまでの基本知識だけでも、多くのケースに対応できるはずです。
次回の記事では、構造体とならんでGoプログラミングの中心となるインターフェースについて解説します。