関数に引数を介して値を渡す場合、値渡し(あたいわたし)と参照渡し(さんしょうわたし)という、2通りの方法があります。
「関数」の章で使っている方法はすべて値渡しです。参照渡しでは、ポインターというものを使います。
package main
func main() {
name := "名前は、main です"
println(name)
someFunc(name)
println(name)
}
func someFunc(name string) {
name = "名前は、someFunc で書き換えられました"
println(name)
}
名前は、main です
名前は、someFunc で書き換えられました
名前は、main です
passvalue.go では、main 関数の name 変数の値を、somuFunc 関数で書き換えることを期待しています。しかし実際には main 関数の name 変数の値は書き換えられません。
someFunc(name)
somuFunc 関数を呼び出しています。ここで引数で渡している値は、実際には変数 name の値ではありません。引数に渡している値は、変数 name のコピー(複製)の値です。つまり、変数 name の値が、メモリーの別の領域にコピーされ、そのコピーされた値が、someFunc 関数に渡されています。
func someFunc(name string) {
sumeFunc 関数の name 変数(仮引数)が受け取っているのは、新しい領域にコピーされた値です。main 関数の name 変数とは違うものです。
name = "名前は、someFuncで書き換えられました"
println(name)
sumeFunc 関数の name 変数を書き換えていますが、これはあくまでも someFunc 関数の name 変数です。関数内では、引数名を、そのまま変数名として使えます。main 関数の name 変数を書き換えているわけではありません。
そして、次の行で name 変数の値をターミナル表示しています。ここで使われる name 変数も、あくまでも someFunc 関数の name 変数です。
なお、関数内で定義された変数は、その関数の中だけで使えます。このように、変数の使える範囲のことを、スコープ(scope、範囲)と言います。
ここで、少しスコープの説明をします
package main
var name = "私は、main パッケージの name 変数です"
func main() {
println(name)
someFunc()
}
func someFunc() {
name := "私は、someFunc 関数の name 変数です"
println(name)
}
私は、main パッケージの name 変数です
私は、someFunc 関数の name 変数です
var name = "私は、main パッケージの name 変数です"
パッケージのルート階層で定義した変数は、パッケージ内のどこからでも使えます。このように、パッケージのどこからでも使える変数のことを、グローバル変数と言います。
なお、グローバル変数の定義では、name := という簡略した方法は使えません。
func main() {
println(name)
main 関数には、name 変数は定義されていません。そのような場合は、グローバル変数の name が使われます。
name := "私は、someFunc 関数の name 変数です"
println(name)
someFunc 関数には、独自の name 変数が定義されています。このような、関数内で定義された変数は、その関数内でしか使えません。このような、関数内でしか使えない変数のことを、ローカル変数と言います。そして、関数内にグローバル変数と同じ名前のローカル変数がある場合は、グローバル変数は無視され、ローカル変数が使われる決まりになっています。
違う関数の間で、同じ変数の値を読み書きできたら便利です。その方法の一つとして、グローバル変数を使う方法があります。しかし、プログラミングではグローバル変数の使用は推奨されていません。プログラムの管理が難しくなるからです。そこで使われるのがポインターを使った参照渡しという方法です。
参照渡しの説明をする前にポインターの説明をします。
package main
func main() {
num := 100
println(num) // 100 と表示される
println(&num) // 0xc000030780 などと表示される
}
変数名の前に & (アンパサンド)をつけると、その変数が格納されているメモリの場所(アドレス)が取得できます。メモリのアドレスは、0xc000030780 のように 16 進数で表示されます。
さて、このアドレスですが、アドレス専用の変数に入れておくことができます。このアドレス専用の変数のことをポインターと言います。
package main
func main() {
num := 100
var pnum *int = &num
// pnum := &num という簡略定義もできます
println(pnum) // 0xc000030780 などと表示される
println(*pnum) // 100 と表示される
*pnum = 200
println(pnum) // 0xc000030780 などと、上記と同じアドレスが表示される
println(*pnum) // 200 と表示される
}
var pnum *int = &num
ポインターの定義です。ポインター名前は、先頭に p を付けたり、最後に ptr を付ける場合が多いみたいです。また、ポインターの型は、ポインターが指し示すアドレスに入っている値の型の前に *(アスタリスク)付けて指定します
pnum := &num
このような、簡易定義もできます。
println(pnum)
ポインターに入っているのは、アドレスですので、ターミナルにはアドレスが表示されます。
println(*pnum)
ポインターが指し示すアドレスに入っている値を取得するには、ポインターの前に *(アスタリスク)を記述します。
*pnum = 200
ポインターが指し示すアドレスに入っている値を変更するには、ポインターの前に *(アスタリスク)を記述します。
関数の引数の話に戻ります。違う関数の間で同じ変数の値を読み書きできたら便利です。その方法の一つが、ポインターを使った参照渡しです。参照渡しでは、関数の引数に、値を渡すのではなく、変数のアドレスを渡します。
package main
func main() {
name := "名前は、main です"
println(name)
someFunc(&name)
println(name)
}
func someFunc(name *string) {
*name = "名前は、someFuncで書き換えられました"
println(*name)
}
名前は、main です
名前は、someFuncで書き換えられました
名前は、someFuncで書き換えられました
someFunc(&name)
今度の someFunc は、引数として string 型のポインターを受け取ります。したがって、someFunc を呼び出す時には、引数として、name 変数のアドレスを渡しています。
func someFunc(name *string) {
someFunc 関数の定義では、引数の型として string 型のアドレス、つまりポインターを指定しています。
*name = "名前は、someFuncで書き換えられました"
ポインターの指し示すアドレスに格納されている値を変更するには、ポインター名の前絵に *(アスタリスクを付けます。
println(*name)
ポインターが指し示すアドレスに格納されている値を取得するには、ポインター名の前に *(アスタリスク)を付けます。
参照渡しで渡されるのも、ポインターが指し示すアドレスのコピーです。しかしアドレスを格納するために必要なメモリ容量は、64 ビットコンピュータで、わずか 8 バイトです。つまり新たに消費されるメモリは 8 バイトだけということです。しかし、値渡しの場合は、値が格納されているメモリ容量がそのままコピーされます。その値が映像などのデータだった場合はどうなるでしょうか? 大量のデータをやりとりする場合には、参照渡しは必須になります。