Go言語   ポインター

ホーム   Goチュートリアル


関数に引数を介して値を渡す場合、値渡し(あたいわたし)と参照渡し(さんしょうわたし)という、2通りの方法があります。

「関数」の章で使っている方法はすべて値渡しです。

参照渡しでは、ポインターというものを使います。

値渡し

passvalue.go


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、範囲)と言います。

スコープ

ここで、少しスコープの説明をします

scope.go


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 変数が定義されています。このような、関数内で定義された変数は、その関数内でしか使えません。このような、関数内でしか使えない変数のことを、ローカル変数と言います。そして、関数内にグローバル変数と同じ名前のローカル変数がある場合は、グローバル変数は無視され、ローカル変数が使われる決まりになっています。

ポインター

違う関数の間で、同じ変数の値を読み書きできたら便利です。その方法の一つとして、グローバル変数を使う方法があります。しかし、プログラミングではグローバル変数の使用は推奨されていません。プログラムの管理が難しくなるからです。そこで使われるのがポインターを使った参照渡しという方法です。

参照渡しの説明をする前にポインターの説明をします。

address.go


package main

func main() {

	num := 100
	
	println(num)	// 100 と表示される
	println(&num)	// 0xc000030780 などと表示される
}
    

変数名の前に & (アンパサンド)をつけると、その変数が格納されているメモリの場所(アドレス)が取得できます。メモリのアドレスは、0xc000030780 のように 16 進数で表示されます。


さて、このアドレスですが、アドレス専用の変数に入れておくことができます。このアドレス専用の変数のことをポインターと言います。

pointer.go


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
	

ポインターが指し示すアドレスに入っている値を変更するには、ポインターの前に *(アスタリスク)を記述します。

まとめ

変数の値を読み書きする場合
変数名をそのまま記述します。
変数のアドレスを取得する場合
変数名の前に &(アンパサンド)を付けます。
ポインターの指し示すアドレスに入っている値を読み書きする場合
変数名の前に *(アスタリスク)を付けます。
ポインターに入っているアドレスを取得する場合
ポインター名をそのまま記述します。

参照渡し

関数の引数の話に戻ります。違う関数の間で同じ変数の値を読み書きできたら便利です。その方法の一つが、ポインターを使った参照渡しです。参照渡しでは、関数の引数に、値を渡すのではなく、変数のアドレスを渡します。

passreference.go


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 バイトだけということです。しかし、値渡しの場合は、値が格納されているメモリ容量がそのままコピーされます。その値が映像などのデータだった場合はどうなるでしょうか? 大量のデータをやりとりする場合には、参照渡しは必須になります。


1530 visits
Posted: Jun. 22, 2019
Update: Jun. 22, 2019

ホーム   Goチュートリアル   目次