LoginSignup
3
2

More than 1 year has passed since last update.

Go言語で理解する値渡しと参照渡し

Posted at

はじめに

Go言語での値渡しと参照渡しについて、基本的なデータ型、ポインタ、構造体、スライスの振る舞いを用いたサンプルコードを解説していきます。
値渡しと参照渡しの概念を確認していきます。

基本データ型の場合

値渡し

package main

import "fmt"

func updateValue(x int) {
    x = x + 1
}

func main() {
    a := 10
    fmt.Println("Before updateValue:", a) // 10
    updateValue(a)
    fmt.Println("After updateValue:", a) // 10 (aは変わらない)
}

このコードでは、整数型の変数aの値をupdateValue関数に渡しています。渡された引数xは値渡しのため、関数内でxの値を変更しても、元の変数aの値に影響を与えません。updateValue関数内でxに1を加算しても、元の変数aの値は10のままです。

参照渡し

package main

import "fmt"

func updateValue(x *int) {
    *x = *x + 1
}

func main() {
    a := 10
    fmt.Println("Before updateValue:", a) // 10
    updateValue(&a)
    fmt.Println("After updateValue:", a) // 11 (aが更新される)
}

このコードでは、整数型の変数aのアドレスをupdateValue関数に渡しています。引数xの型を*intに変更して、ポインタ型を受け取れるようにしました。&aは変数aのアドレスを表し、関数内で引数xを介して元の変数aを参照・変更することができます。
updateValue関数内で*xに1を加算すると、*xは元の変数aを指しているため、変数aの値が更新されます。

構造体

値渡しのような振る舞い

package main

import "fmt"

type Student struct {
    Name string
    Age  int
}

func updateStudent(s Student) {
    s.Age = s.Age + 1
}

func main() {
    student := Student{Name: "Taro", Age: 16}
    fmt.Println("Before updateStudent:", student) // {Taro 16}
    updateStudent(student)
    fmt.Println("After updateStudent:", student) // {Taro 16} (student.Ageは変わらない)
}

このコードでは、構造体Studentが定義され、それを引数としてupdateStudent関数に渡しています。Student構造体は値渡しのような振る舞いをします。つまり、updateStudent関数内で引数sの値(s.Age)を変更しても、元のstudent構造体の値に影響を与えません。この例では、updateStudent関数内でstudent.Ageに1を加算しても、元のstudent.Ageの値は変わらないことがわかります。

参照渡し

package main

import "fmt"

type Student struct {
    Name string
    Age  int
}

func updateStudent(s *Student) {
    s.Age = s.Age + 1
}

func main() {
    student := Student{Name: "Taro", Age: 16}
    fmt.Println("Before updateStudent:", student) // {Taro 16}
    updateStudent(&student)
    fmt.Println("After updateStudent:", student) // {Taro 17} (student.Ageが更新される)
}

このコードでは、構造体Studentのポインタを引数としてupdateStudent関数に渡しています。引数sの型を*Studentに変更して、ポインタ型を受け取れるようにしました。&studentは変数studentのアドレスを表し、関数内で引数sを介して元のstudent構造体を参照・変更することができます。

updateStudent関数内で*sの値(s.Age)に1を加算すると、*sは元の構造体studentを指しているため、student.Ageの値が更新されます。この例では、Go言語の参照渡しを構造体のポインタを使って実現しており、関数内で引数の値を変更することができることを示しています。

スライス

振る舞い

package main

import "fmt"

func updateSlice(s []int) {
    s[0] = 100 // 要素を変更
    s = append(s, 200) // スライス自体を変更
}

func main() {
    nums := []int{1, 2, 3}
    fmt.Println("Before updateSlice:", nums) // [1 2 3]
    updateSlice(nums)
    fmt.Println("After updateSlice:", nums) // [100 2 3] (要素は変更されるが、追加された要素は反映されない)
}

このコードでは、スライスnumsupdateSlice関数に渡しています。スライスは、配列への参照を保持しており、スライスを関数に渡すときは参照渡しのような振る舞いをします。ただし、スライスの要素に対する変更と、スライス自体への変更は異なります。

updateSlice関数内で、引数sの要素(s[0])を変更すると、元のスライスnumsの要素も変更されます。しかし、関数内でスライス自体を変更する操作(append関数で要素を追加する)を行うと、元のスライスnumsには反映されません。これは、スライスが別のメモリ領域(新しい配列)を参照するようになるためです。

この例では、スライスを関数に渡すと、要素の変更は元のスライスに影響を与えるが、スライス自体の変更(要素の追加や削除など)は元のスライスに影響を与えないことがわかります。

まとめ

この記事では、Go言語の値渡しと参照渡しについて、基本的なデータ型、ポインタ、構造体、スライスの振る舞いを用いたサンプルコードを解説しました。これらのサンプルコードを通して、以下の点について理解が深まることを目指しました。

  1. 基本的なデータ型は値渡しで、関数内で変更しても元の変数に影響がない。
  2. ポインタを使って参照渡しを実現し、関数内で変更すると元の変数も変更される。
  3. 構造体やスライスは内部的には参照渡しですが、その振る舞いは値渡しに似ている。
  4. 構造体をポインタで渡すことで参照渡しを実現し、関数内で変更すると元の構造体も変更される。
  5. スライスの要素を変更すると、元のスライスに影響がありますが、スライス自体を変更(要素の追加や削除)すると元のスライスには影響がありません。

これらの概念を理解することで、Go言語での関数呼び出しやデータの受け渡しについて効果的なコードを書くことができます。値渡しと参照渡しの使い分けにより、必要に応じてメモリの効率やデータの変更の影響範囲をコントロールすることが可能になります。

3
2
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2