package main //liststart1
import (
"fmt"
"time"
)
type Counter struct { // Counter型を定義
total int // 合計
lastUpdated time.Time // 最終更新時刻
}
func (c *Counter) Increment() { // ポインタレシーバ(cはポインタ)
c.total++
c.lastUpdated = time.Now()
}
func (c Counter) String() string { // 値レシーバ(cにはコピーが渡される)
return fmt.Sprintf("合計: %d, 更新: %v", c.total, c.lastUpdated)
}
func main() {
var c Counter
fmt.Println(c.String())
c.Increment() //「(&c).Increment()」と書かなくてもよい
fmt.Println(c.String())
}
Counter型の定義
type Counter struct {
total int // 合計
lastUpdated time.Time // 最終更新時刻
}
ここでは Counter
という構造体を定義しています。この構造体は total
(合計値)と lastUpdated
(最終更新時刻)という2つのフィールドを持っています。
Increment メソッド
func (c *Counter) Increment() {
c.total++
c.lastUpdated = time.Now()
}
このメソッドはポインタレシーバを使用しています。ポインタレシーバとは、メソッドがポインタ型のレシーバを受け取ることを意味します。この場合、c
は *Counter
型のポインタです。
- 理由: ポインタレシーバを使うと、メソッド内で構造体のフィールドを直接変更できます。これは、構造体が大きい場合や、メソッド内で状態を変更する必要がある場合に特に重要です。
String メソッド
func (c Counter) String() string {
return fmt.Sprintf("合計: %d, 更新: %v", c.total, c.lastUpdated)
}
こちらは値レシーバを使っています。値レシーバは、メソッドが構造体のコピーを受け取ることを意味します。
-
理由: 値レシーバを使うと、元の構造体を変更することなく、その値を取得することができます。この場合、
Counter
の状態を変更する必要がないため、値レシーバを使用しています。
main 関数
func main() {
var c Counter
fmt.Println(c.String()) // 初期状態を表示
c.Increment() // カウンタをインクリメント
fmt.Println(c.String()) // 更新後の状態を表示
}
-
var c Counter
でCounter
型の変数c
を初期化します。この時点ではtotal
は 0、lastUpdated
はゼロ値(1900年1月1日)です。 -
c.String()
で初期状態を表示します。この時、値レシーバのString
メソッドが呼ばれ、カウンタの合計と最終更新時刻が表示されます。 -
c.Increment()
を呼び出すと、ポインタレシーバのIncrement
メソッドが実行され、total
が 1 増加し、lastUpdated
が現在の時刻に更新されます。 - 再度
c.String()
を呼び出して、更新された状態が表示されます。
Go言語では、メソッドを呼び出す際に、レシーバの型によって自動的にポインタが解決されるため、&c
と書かなくても大丈夫です。この仕組みを詳しく説明します。
レシーバの自動解決
Increment
メソッドはポインタレシーバ (*Counter
) を使用しています。
c.Increment()
と書くと、Goは c
が Counter
型の値であることを認識し、自動的にポインタを取って &c
を実行します。
そのため、c.Increment()
は実際には (&c).Increment()
と同じ意味になります。
まとめ
- ポインタレシーバ: 構造体のフィールドを変更する必要がある場合に使用します。メモリ効率が良い。
- 値レシーバ: 構造体の状態を変更する必要がない場合に使用します。元のオブジェクトを安全に保つことができます。