Go言語には、任意の型に関数を紐づけるためのメソッドという機能が存在しています。
メソッドの定義方法
メソッドの定義は以下のように記述します。レシーバーに関する記述をする部分が、関数と異なる箇所になります
func (<レシーバー>) <関数名>([引数]) [戻り値の型] {
[関数の本体]
}
例えばPerson
という構造体を定義して、そのPerson
のプロフィールを返すメソッドを定義する場合は以下のような記述をします
type Person struct { name string age uint } func (p *Person) profile(header string) string { return fmt.Sprintf("%s Profile[name: %s, age: %d]", header, p.name, p.age) } func (p Person) profile2(header string) string { return fmt.Sprintf("%s Profile[name: %s, age: %d]", header, p.name, p.age) }
定義したメソッドは、 <レシーバ>.<メソッド> という形式で呼び出すことができます
func main() { person1 := &Person{name: "KimuraJiro", age: 28} fmt.Printf("person1 -> %s\n", person1.profile("!!!")) person2 := &Person{name: "KimuraTaro", age: 38} fmt.Printf("person2 -> %s\n", person2.profile2("@@@")) }
■実行結果
person1 -> !!! Profile[name: KimuraJiro, age: 28] person2 -> @@@ Profile[name: KimuraTaro, age: 38]
定義したメソッドの内容が実行されていることが分かります
メソッドは関数としてどう定義されているか
<レシーバの型>.<メソッド>でメソッドを関数型として参照することができます。
これを利用してPerson
に定義したprofile
メソッドとprofile2
メソッドの関数定義を確認してみます
f1 := (*Person).profile f2 := (Person).profile2 fmt.Printf("Person.profile -> %T\n", f1) fmt.Printf("Person.profile2 -> %T\n", f2)
■実行結果
Person.profile -> func(*main.Person, string) string Person.profile2 -> func(main.Person, string) string
実行結果から、メソッドのレシーバを第1引数にした関数になっていることがわかります
レシーバを値型にしたりポイント型にしたり
Person
型をレシーバとするリネームメソッドとPersonのポインタ型をレシーバとするリネームメソッドを以下のように定義します。
引数で与えられた文字列をレシーバPerson
のname
フィールドにセットしています。
type Person struct { name string age uint } func (p *Person) rename1(newName string) { p.name = newName } func (p Person) rename2(newName string) { p.name = newName }
値型に定義したメソッドとポインタに定義したメソッドを実行して結果の違いを確認してみます
fmt.Printf("Person.rename1 -> %T\n", (*Person).rename1) fmt.Printf("Person.rename2 -> %T\n", (Person).rename2) personA := &Person{name: "Hanako", age: 19} oldName := personA.name personA.rename1("Kumiko") fmt.Printf("rename1 exec renamed. %s -> %s\n", oldName, personA.name) personB := &Person{name: "Rika", age: 19} oldName = personB.name personB.rename2("Miho") fmt.Printf("rename2 exec renamed. %s -> %s\n", oldName, personB.name)
■実行結果
Person.rename1 -> func(*main.Person, string) Person.rename2 -> func(main.Person, string) rename1 exec renamed. Hanako -> Kumiko rename2 exec renamed. Rika -> Rika
実行結果から、
ポインタ型に対するメソッド呼び出しの場合はレシーバ(構造体)の値が変更されていますが、
値型に対するメソッド呼び出しの場合はレシーバ(構造体)の値が変更されていないことが分かります。
これは、以下の事を勘案すれば納得いく結果です
- メソッドがレシーバを第1引数に取る関数であること
- 値渡しした引数はコピーされて関数に渡される