Golangのポインタ渡しに関して
ポインタはC言語を勉強したときに理解していたけどGolangでポインタ渡しを使う際に
「なぜ使うのか」
「値渡しとどう違うのか」
という部分があいまいだったので書いてみました。
ポインタに関しては下記記事を参照
「ポインタってなんだよ」みたいな方は下記記事を読んでおくと理解しやすいかもです。
ポインタ
ポインタ変数
まずは関数への値渡しでアドレスを出力してみる
func Test(a int) {
fmt.Println("aのアドレス(関数内):", &a)
}
func main() {
a := 10
fmt.Println("aのアドレス:", &a)
Test(a)
}
// aのアドレス: 0xc4200160f0
// aのアドレス(関数内): 0xc4200160f8
表示されたaのアドレスに注目してください。
違うアドレスが出力されていますね。これはmain( )の中のaとTest( )の中のaが別物であることを表しています。
関数の引数に値渡しをした場合は、関数が実行される際に値がコピーされるからです。
値渡し
この関数が実行されるときに値がコピーされるという知識を踏まえてTest( )の中で変数aを変更しようとしてみます。
func Test(a int) {
a += 1
fmt.Println("aの値(関数内):", a)
}
func main() {
a := 10
fmt.Println("Test()呼び出し前のaの値:", a)
Test(a)
fmt.Println("Test()呼び出し後のaの値:", a)
}
// 実行結果
// Test()呼び出し前のaの値: 10
// aの値(関数内): 11
// Test()呼び出し後のaの値: 10
予想通りですね。aをTest( )に渡すことによってaは11になると思いきやそれはTest( )の中だけの話でmain( )の中でもう一度aを出力すると10のままでした。
この動きがポインタ渡しを理解するときのポイントです。ではポインタ渡しを見てみましょう。
ポインタ渡し
ポインタ渡しを理解するときは
-
fmt.Println(&a)
とやるとaのアドレスが出力されるということ -
*a
がポインタが指し示している変数を表していること(👉がポインタで📦が変数だとすると、こういうイメージ→👉📦)
の2つを押さえておけばオッケーです。
func Test(a *int) { //←aはint型のポインタ変数になる。👉を作ったイメージ。
*a += 1 //aポインタが指している変数に入る数を+1する
fmt.Println("aの値(関数内):", *a)
}
func main() {
a := 10
fmt.Println("Test()呼び出し前のaの値:", a)
// 呼び出しで、アドレスを渡す。
Test(&a)
fmt.Println("Test()呼び出し後のaの値:", a)
}
// Test()呼び出し前のaの値: 10
// aの値(関数内): 11
// Test()呼び出し後のaの値: 11
先ほどの値渡しとの違いはどこでしょうか。そうです。Test( )呼び出し後のaの値です。
Test関数実行後もaが11になっていますね。
これがポインタ渡しの特徴です。値渡しと違って、Test関数実行後にも変更を保持することができます。便利ですね。
なぜポインタ渡しを使うのか
ややこしいし、値渡しでよくねと正直値思っていたのですが、
-
値渡しと違って、間接的に参照・操作できる
-
上で見たように値渡しだと関数の引数として構造体を渡した場合に、その構造体のコピーが生成されてしまい、元の構造体に影響を与えない。ポインタ渡しだと影響を与えることができる。
-
単純にORマッパーなどのライブラリがポインタを使うので使わざるを得ない
-
大きな構造体を値渡しするとコピー処理で性能が劣化する。 ポインタ渡しの場合には固定で8バイト(64bit)/4バイト(32bit)なので性能が劣化しない。
というような理由から使うようです。
間違い等ありましたら教えていただけると嬉しいです!!
参考
https://www.yoheim.net/blog.php?q=20170901
https://tour.golang.org/moretypes/1