Menu
Coddy logo textTech

値渡し vs 参照渡し

CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 21/107。

Goは値渡しの言語です。関数に変数を渡すと、Goはその値のコピーを作成します。つまり、関数内での変更は元の変数には影響しません。

func double(n int) {
    n = n * 2
}

func main() {
    x := 10
    double(x)
    fmt.Println(x)  // 10 (変更なし)
}

構造体についても同様です。構造体を関数に渡すと、そのすべてのフィールドがコピーされるため、関数内での変更は失われます。

type Person struct {
    Name string
    Age  int
}

func birthday(p Person) {
    p.Age++
}

func main() {
    alice := Person{Name: "Alice", Age: 30}
    birthday(alice)
    fmt.Println(alice.Age)  // 30 (変更なし)
}

元の値を変更するには、代わりにポインタを渡します。ポインタ自体はコピーされますが、それでも同じメモリ番地を指しているため、元のデータを変更することができます。

func birthday(p *Person) {
    p.Age++
}

func main() {
    alice := Person{Name: "Alice", Age: 30}
    birthday(&alice)
    fmt.Println(alice.Age)  // 31 (変更されました!)
}

元の値を変更する必要がある場合や、コストの高いコピーを避けるために大きな構造体を渡す場合には、ポインタを使用してください。意図しない変更から元のデータを保護したい場合は、値を使用してください。

challenge icon

チャレンジ

簡単

構造体を値渡しする場合とポインタ渡しする場合の違いを実証するショッピングカートシステムを構築しましょう。ポインタ引数だけが元のデータを変更できることを、実際に確認してください。

コードは2つのファイルに分けて構成します:

  • cart.go: Name (string)、Price (float64)、Quantity (int) フィールドを持つ CartItem 構造体を定義します。数量の更新を試みる2つの関数を作成してください:
    • UpdateQuantityByValueCartItem (値渡し) と新しい数量を受け取り、アイテムの数量を新しい値に設定します。
    • UpdateQuantityByPointer*CartItem (ポインタ渡し) と新しい数量を受け取り、アイテムの数量を新しい値に設定します。
    また、価格に数量を掛けた値を返す Total メソッドを CartItem に作成してください。
  • main.go: 入力からアイテムの詳細を読み取り、CartItem を作成し、両方の更新アプローチを実証します。まず値渡し関数で更新を試みて結果を表示し、次にポインタ渡し関数で更新して再度結果を表示します。

以下の入力が提供されます:

  • 1行目: アイテム名
  • 2行目: 価格 (float)
  • 3行目: 初期の数量 (integer)
  • 4行目: 設定する新しい数量 (integer)

出力形式:

Initial: [Name] x[Quantity] = $[Total]
After value update: [Name] x[Quantity] = $[Total]
After pointer update: [Name] x[Quantity] = $[Total]

例えば、Laptop999.9913 が与えられた場合、出力は以下のようになります:

Initial: Laptop x1 = $999.99
After value update: Laptop x1 = $999.99
After pointer update: Laptop x3 = $2999.97

値渡しによる更新の後では数量が 1 のまま(関数がコピーを修正したため)ですが、ポインタ渡しによる更新の後では 3 に変わっている(関数がオリジナルを修正したため)ことに注目してください。合計金額は小数点以下2桁でフォーマットしてください。

チートシート

Goは**値渡し**(pass-by-value)の言語です。変数を関数に渡すと、Goはその値のコピーを作成するため、関数内での変更は元の変数に影響を与えません。

func double(n int) {
    n = n * 2
}

func main() {
    x := 10
    double(x)
    fmt.Println(x)  // 10 (変更なし)
}

これは構造体にも当てはまります。構造体を関数に渡すと、そのすべてのフィールドがコピーされます:

type Person struct {
    Name string
    Age  int
}

func birthday(p Person) {
    p.Age++
}

func main() {
    alice := Person{Name: "Alice", Age: 30}
    birthday(alice)
    fmt.Println(alice.Age)  // 30 (変更なし)
}

元の値を変更するには、**ポインタ**を渡します。ポインタ自体はコピーされますが、それでも同じメモリ番地を指しています:

func birthday(p *Person) {
    p.Age++
}

func main() {
    alice := Person{Name: "Alice", Age: 30}
    birthday(&alice)
    fmt.Println(alice.Age)  // 31 (変更されました!)
}

元の値を変更する必要がある場合や、大きな構造体を渡す際に高コストなコピーを避ける場合にはポインタを使用します。意図しない変更から元のデータを保護したい場合には値を使用します。

自分で試してみよう

package main

import (
	"fmt"
)

func main() {
	// 入力を読み込む
	var name string
	var price float64
	var initialQty, newQty int

	fmt.Scanln(&name)
	fmt.Scanln(&price)
	fmt.Scanln(&initialQty)
	fmt.Scanln(&newQty)

	// TODO: 入力値を使用して CartItem を作成する

	// TODO: 次の形式を使用して初期状態を出力する:
	// fmt.Printf("Initial: %s x%d = $%.2f\n", ...)

	// TODO: UpdateQuantityByValue を使用して数量の更新を試みる
	// その後、結果を出力する(数量が変わらないことに注意)

	// TODO: UpdateQuantityByPointer を使用して数量を更新する
	// その後、結果を出力する(数量が変わることに注意)
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン