公式の説明
Pointers
Goはポインタを扱います。 ポインタは値のメモリアドレスを指します。
変数 T のポインタは、 *T 型で、ゼロ値は nil です。
var p *int
& オペレータは、そのオペランド( operand )へのポインタを引き出します。
i := 42
p = &i
- オペレータは、ポインタの指す先の変数を示します。
fmt.Println(*p) // ポインタpを通してiから値を読みだす
*p = 21 // ポインタpを通してiへ値を代入する
より理解を深めるためにGPT先生に聞いてみたらたとえを出しながら解説してくれたので、以下にその内容を載せます。
ポインタって何?
ポインタは「住所カード」だと思ってください。
例えば:
• ポインタは 「あるデータがどこにあるか」 を指し示すものです。
• もしメモ帳が机の上にあるとしたら、「机の上」という場所をポインタが教えてくれます。
• 実際のメモ帳(中身)はデータそのものです。
Goでは、ポインタは*という記号を使って表現します。
例:ポインタの使い方
package main
import "fmt"
func main() {
number := 42 // 普通の数値データ
pointer := &number // `&`は「住所を教えて!」という意味
fmt.Println(number) // 中身を表示:42
fmt.Println(pointer) // メモ帳の住所を表示
fmt.Println(*pointer) // 住所を頼りにメモ帳の中身を読む:42
}
• &number は、numberの住所を取得します。
• *pointer は、住所を使って中身を読むことを意味します。
オペレータとオペランドって何?
これらは数学の用語から来ていますが、プログラムではこんなイメージで考えてください。
• オペレータ:操作をする「道具」や「記号」。
• オペランド:操作される「材料」や「対象」。
例:足し算で考える
式:3 + 5
• + はオペレータ(足し算する道具)
• 3 と 5 はオペランド(計算される材料)
Goのプログラムでも、計算や操作をするときにオペレータとオペランドを使います。
Goでのオペレータの例
Goにはいろいろな種類のオペレータがあります。ポインタと関連するものを挙げます。
1. アドレス取得オペレータ(&)
「このデータの住所を教えて!」という道具。
number := 42
pointer := &number // numberの住所をpointerに代入
fmt.Println(pointer) // numberの住所が表示される
2. デリファレンスオペレータ(*)
「住所を頼りに中身を見たい!」という道具。
fmt.Println(*pointer) // pointerが指している住所の中身(42)を表示
Goのポインタ、オペレータ、オペランドのまとめ
- ポインタ:データの住所カード
- &:住所を調べるための道具
- *:住所を頼りに中身を覗く道具
- オペレータ:操作するための道具
- オペランド:操作の対象(データや変数)
Goのポインタは「住所を教えてもらって、中身を操作する仕組み」と理解すればOK!
ポインタを使う理由
ポインタを使うと、データを直接操作したり効率的に処理したりできます。特に以下の場面で便利です
• メモリ節約:大きなデータをコピーする代わりに住所だけを渡す。
• 共有:複数の場所から同じデータを操作できる。
1. 「ポインタレシーバ」って何?
• 「ポインタレシーバ」とは、レシーバ(=メソッドが使う対象)を「住所カード(ポインタ)」で受け取るものです。
• 住所カードを使えば、対象そのものを直接操作できます。住所を使わない場合はコピーを操作するだけになります。
2. 「ポインタレシーバ」を使う理由
• 元のデータ(オリジナル)を変更したいときに使います。
• コピーを操作するのではなく、元データに影響を与えたい場合に便利です。
身近なサンプルコード:銀行口座の例
package main
import "fmt"
// 銀行口座を表す構造体
type BankAccount struct {
Balance float64 // 口座残高
}
// 1. 残高を表示するメソッド
func (b BankAccount) ShowBalance() {
fmt.Printf("現在の残高: %.2f円\n", b.Balance)
}
// 2. お金を預け入れるメソッド (ポインタレシーバで定義)
func (b *BankAccount) Deposit(amount float64) {
b.Balance += amount // 残高を増やす
}
// 3. お金を引き出すメソッド (ポインタレシーバで定義)
func (b *BankAccount) Withdraw(amount float64) {
if amount > b.Balance {
fmt.Println("残高不足です。")
return
}
b.Balance -= amount // 残高を減らす
}
func main() {
// 初期残高 1000円の口座を作成
myAccount := BankAccount{Balance: 1000}
// 残高を表示
myAccount.ShowBalance()
// 500円を預け入れ
myAccount.Deposit(500)
myAccount.ShowBalance()
// 2000円を引き出そうとする (残高不足)
myAccount.Withdraw(2000)
myAccount.ShowBalance()
// 300円を引き出す
myAccount.Withdraw(300)
myAccount.ShowBalance()
}
コードの動き
1. main関数で口座を作成
myAccount := BankAccount{Balance: 1000}
• 残高1000円の口座myAccountを作ります。
2. 残高を表示する(ShowBalance)
myAccount.ShowBalance()
• 「現在の残高: 1000.00円」と表示されます。
• ShowBalanceは残高を変更しないので、変数レシーバ(値レシーバ)で十分です。
3. 500円を預け入れる(Deposit)
myAccount.Deposit(500)
• ポインタレシーバを使うことで、myAccountの残高を直接変更します。
• 残高は 1000 + 500 = 1500円になります。
4. 2000円を引き出す(Withdraw)
myAccount.Withdraw(2000)
• 残高が1500円なので、2000円を引き出そうとするとエラーになります。
• 「残高不足です。」と表示され、残高は変わりません。
5. 300円を引き出す(Withdraw)
myAccount.Withdraw(300)
• 残高は 1500 - 300 = 1200円になります。
ポインタレシーバが重要な理由
• DeDepositやWithdrawでは、残高(Balance)を変更する必要があります。
• ポインタレシーバを使うことで、myAccountそのものを変更できます。
• もし値レシーバにした場合、残高を変更してもコピーにしか影響がなく、元の口座は変わりません。
もしポインタレシーバを使わなかったら?
以下のようにDepositを値レシーバに書き換えると、元のデータは変わらなくなります。
func (b BankAccount) Deposit(amount float64) {
b.Balance += amount // 残高を増やす
}
実行結果:
現在の残高: 1000.00円
現在の残高: 1000.00円
残高不足です。
現在の残高: 1000.00円
現在の残高: 1000.00円
• 預け入れや引き出しがすべて反映されません。
• コピー(別の口座)が操作されているだけだからです。
ポインタレシーバについてのまとめ
• ポインタレシーバを使うと、元のデータ(口座の残高)を直接操作できます。
• 預け入れや引き出しのようにデータを変更する処理では、ポインタレシーバが必要です。
• 残高を表示するだけの処理では、値レシーバで十分です。