Go
Golangのspecificationの日本語訳と、ちょっとした遊びぐらいのコードを載せていく感じで進めようと思うので、
すでにGolangでコードを書いている人にはつまらないかもしれないです
また、間違っている部分があればぜひご指摘いただけると嬉しいです🙇♂️
想定する読者層
- Golang初めてそこまで期間が経っていない
- 英語の公式読むの辛い
Pointer types
本記事は公式のドキュメントを参考にしています
Pointer とは?
Pointer はその変数のメモリ上のアドレスを示します
は?と大学時代の私はなりC言語のことが嫌いになって一度プログラミングを挫折しました
Pointerを使って返却されたアドレスをみてシンプルに拒否反応が出ました
ちなみに↓のような値でした
0xc0000a0008
結局その後、PHP, Python, Rubyと様々な(動的)言語を学び、 Golang を学ぶ必要が出て久しぶりに Pointer に出会いました
いろんな言語を学んでみて思いますが、 Pointer も他の文法と同じようにただ淡々と学ぶことができていれば大学の時に挫折に済んだのかなと思っています(ちなみに、ポインタの概念はどの言語にも存在していますが、本記事では取り上げません)
という余談は置きつつ、Pointer の宣言とかどういう使い方があるのかとかを説明しています
私の説明で ? となってしまったら申し訳ないですが、質問していただいたり他の方の記事も参考にしていただけたらと思います
Pointer の宣言方法
Pointerは以下のようにして型の前に *
をつけて宣言します
var p *int
var p *string
初期化していない状態では Pointer には nil が設定されます
var p *int
fmt.Println(p) // = nil
普通に宣言した変数からアドレスを取り出すこともでき、pointerで宣言した変数には&
を使って格納します
i := 10
fmt.Println(&i) // = 0xc0000aa000
var p *int
p = &i
fmt.Println(p) // = 0xc0000aa000
pointer で宣言した変数の実体は *
で取得することができます (pointer を宣言した時の *
と最初はごっちゃになってしまうので使って慣れましょう)
fmt.Println(*p) // 10
ちなみにgolangではnewを使って、動的にメモリの確保を行うこともできます
p := new(int)
fmt.Println(p) // = 0xc0000aa000
サンプル
おそらく Pointer の宣言とアドレス、実体値を取り出せるようになっただけではだから何?
という気持ちになります(私はなりました)
c言語をやったことがあるひとは知っていると思いますが、ポインタ(参照)渡し
と値渡し
の2パターンを知っていると使いどころが少しわかってくるのではないかと思っています
正直、言葉だけで考えようとすると意味わからなくなってプログラム(ポインタ)のことが嫌いになってしまうかもしれないので
ただ、淡々と文法を覚えるかのようにサンプルを書いて覚えてしまいましょう
値渡し
値渡しは、pointerではなく宣言した変数を関数に渡した時に、その変数のコピーが渡されることです
そしてその変数のコピーは引数で渡した変数とは異なるアドレスに作成されます
package main
import "fmt"
func main() {
var s = "James"
fmt.Printf("%v address is %p\n", s, &s)
Greeting(s)
}
func Greeting(s string) {
fmt.Println("Hello, " + s)
fmt.Printf("%v address is %p\n", s, &s)
}
James address is 0x40c138
Hello, James
James address is 0x40c150
関数に渡す前に s
で宣言した変数のアドレスは 0x40c138
でしたが、
関数に渡された後の s
のアドレスは 0x40c150
でした
つまり Greeting に渡される前の s
と Greeting に渡された s
は全くの別物であるということです
https://play.golang.org/p/WcYtv-7BKHa
なので、関数内で s
の中身を別の文字列に変えようが、関数に渡される前の s
には全く影響がありません
package main
import "fmt"
func main() {
var s = "James"
fmt.Printf("%v address is %p\n", s, &s) // 1
Greeting(s)
fmt.Printf("%v address is %p\n", s, &s) // 4
}
func Greeting(s string) {
fmt.Println("Hello, " + s) // 2
fmt.Printf("%v address is %p\n", s, &s) // 3
s = "Hoge"
}
James address is 0x40c138
Hello, James
James address is 0x40c150
James address is 0x40c138
play ground で実行して確かめて遊んでみると、理解度が増すかもしれません
ポインタ渡し
次に、本記事のメインであるポインタを使った方法で関数に値を渡すようにしてみましょう
大事な点は二つあります
- 関数内外での変数のアドレス
- Greeting 後での変数の中身
以上2点に気をつけて見てみましょう
package main
import "fmt"
func main() {
var s = "James"
fmt.Printf("%v address is %p\n", s, &s) // 1
Greeting(&s)
fmt.Printf("%v address is %p\n", s, &s) // 4
}
func Greeting(s *string) {
fmt.Printf("Hello, %v\n", *s) // 2
fmt.Printf("%v address is %p\n", *s, s) // 3
*s = "Hoge"
}
James address is 0x40c138
Hello, James
James address is 0x40c138
Hoge address is 0x40c138
いかがでしょう、2つの点に気をつけながら見てみるとわかりやすかったのではないでしょうか
少しわかりにくい言い方をするのであれば、ローカル変数を別の関数上で書き換え可能であるという感じでしょうか
ここまで試した方は疑問に思っているはずです
わざわざ関数内で変数の中身を変えずに、mainのなかで変えれば良くないか
ポインタのメリットは以下の点などが挙げられます
- 省メモリ化
- 高速化
これらについては、本記事よりもとてもわかりやすい記事を書いてくださっている方がいるのでそちらを読んでみてはいかがでしょうか
http://pg-kura.hatenablog.com/entry/20120616/1339856279
それでもよくわからんという方は質問したり、他の記事も併せて読んでみてください