構造体へのポインタ
CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 20/107。
構造体(struct)を扱う際、大きなデータ構造のコピーを避け、元の構造体を変更するために、ポインタを頻繁に使用します。構造体へのポインタは、その構造体インスタンスのメモリ番地を保持します。
既存の構造体に対して & 演算子を使用するか、構造体リテラルで直接アドレス演算子を使用することで、構造体ポインタを作成できます。
type Person struct {
Name string
Age int
}
p := Person{Name: "Alice", Age: 30}
ptr := &p // 既存の構造体へのポインタ
// または直接作成する
ptr2 := &Person{Name: "Bob", Age: 25}Goは、ポインタを介して構造体のフィールドにアクセスする際に便利なショートカットを提供します。(*ptr).Nameと書く代わりに、単にptr.Nameと書くことができます。Goは自動的にポインタをデリファレンスします。
ptr := &Person{Name: "Alice", Age: 30}
// どちらも同じように動作します
fmt.Println((*ptr).Name) // 明示的なデリファレンス
fmt.Println(ptr.Name) // 自動的なデリファレンスこの自動的なデリファレンスは、フィールドを変更する際にも適用されます。ポインタを介した変更は、元の構造体に影響を与えます。
p := Person{Name: "Alice", Age: 30}
ptr := &p
ptr.Age = 31 // 元の値を変更します
fmt.Println(p.Age) // 31構造体ポインタは、GoのOOPパターンにおいて不可欠です。これらにより、メソッドがレシーバの状態を変更できるようになり、大きな構造体を関数に効率的に渡すことが可能になります。また、ツリーやリストのような連結データ構造を構築するための基礎となります。
チャレンジ
簡単構造体ポインタを使用して元のデータを効率的に変更する方法を示す銀行口座システムを構築しましょう。ポインタ操作を通じて更新可能な口座を作成します。
コードを2つのファイルに分けて構成します:
account.go:Owner(string)、Balance(float64)、およびAccountNumber(string) フィールドを持つBankAccount構造体を定義します。所有者名と口座番号をパラメータとして受け取り、開始残高が 0 の新しいBankAccountへのポインタを返すNewAccountという関数を作成します。また、*BankAccountポインタと金額を受け取り、ポインタを介してその金額を口座の残高に加算するDeposit関数を作成します。main.go: 入力から口座情報を読み取り、コンストラクタ関数(ポインタを返すもの)を使用して口座を作成し、ポインタを使用して入金を行い、残高が変更されたことを確認するために口座の詳細を表示します。
以下の入力が提供されます:
- 1行目: 所有者名
- 2行目: 口座番号
- 3行目: 入金額 (float)
出力形式:
Account: [AccountNumber]
Owner: [Owner]
Balance: [Balance]例えば、John Smith、ACC-12345、500.50 が与えられた場合、出力は以下のようになります:
Account: ACC-12345
Owner: John Smith
Balance: 500.50NewAccount 関数は & 演算子を使用して *BankAccount を返す必要があること、また Deposit 関数は Go の自動デリファレンス(例:(*account).Balance の代わりに account.Balance)を使用して、ポインタを介して直接残高を変更できることを覚えておいてください。
チートシート
構造体へのポインタは、構造体インスタンスのメモリ番地を保持します。& 演算子を使用して構造体ポインタを作成します:
type Person struct {
Name string
Age int
}
p := Person{Name: "Alice", Age: 30}
ptr := &p // 既存の構造体へのポインタ
// または直接作成する
ptr2 := &Person{Name: "Bob", Age: 25}Goは、ポインタを介して構造体のフィールドにアクセスする際に自動的なデリファレンスを提供します。(*ptr).Name の代わりに、ptr.Name と書くことができます:
ptr := &Person{Name: "Alice", Age: 30}
// どちらも同じように動作します
fmt.Println((*ptr).Name) // 明示的なデリファレンス
fmt.Println(ptr.Name) // 自動的なデリファレンスポインタを介した変更は、元の構造体に影響を与えます:
p := Person{Name: "Alice", Age: 30}
ptr := &p
ptr.Age = 31 // 元の値を変更する
fmt.Println(p.Age) // 31構造体ポインタにより、メソッドがレシーバの状態を変更できるようになり、大きな構造体を関数に効率的に渡すことが可能になります。また、連結データ構造の基礎となります。
自分で試してみよう
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// 所有者の名前を読み込む
ownerName, _ := reader.ReadString('\n')
ownerName = strings.TrimSpace(ownerName)
// 口座番号を読み込む
accountNumber, _ := reader.ReadString('\n')
accountNumber = strings.TrimSpace(accountNumber)
// 入金額を読み込む
amountStr, _ := reader.ReadString('\n')
amountStr = strings.TrimSpace(amountStr)
amount, _ := strconv.ParseFloat(amountStr, 64)
// TODO: NewAccount関数を使用して新しい口座を作成する(ポインタを返す)
// TODO: 口座ポインタを使用してDeposit関数で入金を行う
// TODO: 以下の形式で口座の詳細を表示する:
// Account: [AccountNumber]
// Owner: [Owner]
// Balance: [Balance]
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
1Go オブジェクト指向の基礎
外部ファイルGo ワークスペースとモジュールパッケージとインポート公開された名前と非公開の名前Go におけるオブジェクト指向入門クラスとしての構造体構造体へのメソッド定義ポインタレシーバと値レシーバ構造体の初期化コンストラクタ関数まとめ:簡易計算機8エラー処理とOOP
error インターフェースカスタムエラー型エラーラッピング (fmt.Errorf)センチネルエラーerrors.Is() と errors.As()Panic、Defer、Recover復習 - ファイルパーサー