0
0

More than 3 years have passed since last update.

[The Go Programming Language Specification] Pointer

Posted at

Go

Golangのspecificationの日本語訳と、ちょっとした遊びぐらいのコードを載せていく感じで進めようと思うので、
すでに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

それでもよくわからんという方は質問したり、他の記事も併せて読んでみてください

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0