はじめに
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] (要素は変更されるが、追加された要素は反映されない)
}
このコードでは、スライスnums
をupdateSlice
関数に渡しています。スライスは、配列への参照を保持しており、スライスを関数に渡すときは参照渡しのような振る舞いをします。ただし、スライスの要素に対する変更と、スライス自体への変更は異なります。
updateSlice
関数内で、引数sの要素(s[0]
)を変更すると、元のスライスnums
の要素も変更されます。しかし、関数内でスライス自体を変更する操作(append
関数で要素を追加する)を行うと、元のスライスnums
には反映されません。これは、スライスが別のメモリ領域(新しい配列)を参照するようになるためです。
この例では、スライスを関数に渡すと、要素の変更は元のスライスに影響を与えるが、スライス自体の変更(要素の追加や削除など)は元のスライスに影響を与えないことがわかります。
まとめ
この記事では、Go言語の値渡しと参照渡しについて、基本的なデータ型、ポインタ、構造体、スライスの振る舞いを用いたサンプルコードを解説しました。これらのサンプルコードを通して、以下の点について理解が深まることを目指しました。
- 基本的なデータ型は値渡しで、関数内で変更しても元の変数に影響がない。
- ポインタを使って参照渡しを実現し、関数内で変更すると元の変数も変更される。
- 構造体やスライスは内部的には参照渡しですが、その振る舞いは値渡しに似ている。
- 構造体をポインタで渡すことで参照渡しを実現し、関数内で変更すると元の構造体も変更される。
- スライスの要素を変更すると、元のスライスに影響がありますが、スライス自体を変更(要素の追加や削除)すると元のスライスには影響がありません。
これらの概念を理解することで、Go言語での関数呼び出しやデータの受け渡しについて効果的なコードを書くことができます。値渡しと参照渡しの使い分けにより、必要に応じてメモリの効率やデータの変更の影響範囲をコントロールすることが可能になります。