2017/09/22更新

[Go] ポインタについて学ぶ(基本的なところ)

このエントリーをはてなブックマークに追加      

こんにちは、@yoheiMuneです。
最近仕事ではGo言語でプログラミングを行なっています。せっかく使っているので少しずつ知識を蓄えたいなと思う今日この頃。今日は、ポインターについてブログを書きたいと思います。
画像


目次




Go言語にはポインターがある

PHP、JavaScript、Python、などを触っていると扱うことのないポインターですが、Go言語ではそれを扱うことができます。Goでのポインターの使いどころとしては、

  • 値を参照渡ししたい
  • nilを表現したい(例えばStringの初期値は空文字だが、それと区別したい)
  • ライブラリがポインターを扱うので使わざる得ない(ORマッパーなど)

といった時に、ポインターによる実装を行います。
今日は、ポインタの基本的なところをブログに書けたらと思います。



ポインターの基本のき

以下のコードは、https://play.golang.org/p/sq5BnlFVE9で実際に触ってみることができます。


Go言語によるポインタは、以下のようにして扱うことができます。
// 普通、int型は以下のように定義しますが、
var n int = 100

// ポインタを扱う場合には、「*int」という表現で定義します.
var pointer *int

// &(アドレス演算子)を使うと、その変数のアドレスにアクセスできます.
// ここでは変数nのアドレスを、pointer変数に格納しています.
// (つまり、変数nと変数pointerは同じアドレスを参照するようにしました)
pointer = &n

// 同じアドレスを参照していることを確認できます.
fmt.Println("nのアドレス:", &n)           // nのアドレス: 0x10410020
fmt.Println("pointerの値:", pointer)     // pointerの値: 0x10410020

// 同じアドレスなら、もちろん、値も同じです.
// ポインタを示す変数(pointer)から値を取り出す場合には、「*pointer」と表現します.
fmt.Println("nの値:", n)                 // nの値: 100
fmt.Println("pointerの中身:", *pointer)  // pointerの中身: 100

// nの値を変更してみると、
n += 1

// どちらの変数も値が変わります.
// (同じアドレスを参照しているので当然と言えば当然)
fmt.Println("nの値:", n)                 // nの値: 101
fmt.Println("pointerの中身:", *pointer)  // pointerの中身: 101

// pointer変数にて、中身を変更してみても、
*pointer = 99

// 同じく、値が変わります.
fmt.Println("nの値:", n)                 // nの値: 99
fmt.Println("pointerの中身:", *pointer)  // pointerの中身: 99

// アドレス自体は演算できません(Goの場合)
// pointer = pointer + 1                   
// tmp/sandbox390357081/main.go:37:20: invalid operation: pointer + 1 (mismatched types *int and int)   
という感じで、*&を使いながら、ポインターを扱います。
最初はなかなか慣れなくて、今扱っている変数がポインターなのか否かで混乱したりと大変ですが、それを乗り越えるところまでは頑張りどころです。



関数とポインタ

関数の引数にポインタを渡すような使い方もできます。


関数への値渡し

関数の引数に値を渡した場合には、関数が実行される際に値がコピーされ、参照先のアドレスが変わります。
// https://play.golang.org/p/jG6_qZt4yK

func myFunc(a int) {
    fmt.Println("aのアドレス(関数内):", &a)
}

func main() {
    a := 10
    fmt.Println("aのアドレス:", &a)
    myFunc(a)
}

// 実行結果
// aのアドレス: 0x10410020
// aのアドレス(関数内): 0x10410024
そのため、以下のように関数内で値を変更したとしても、呼び出し元の変数には値が反映されません。
// https://play.golang.org/p/cdLZyR9rwb
func myFunc(a int) {
    a += 1
    fmt.Println("aの値(関数内):", a)
}

func main() {
    a := 10
    fmt.Println("aの値(呼び出し前):", a)
    myFunc(a)
    fmt.Println("aの値(呼び出し後):", a)
}

// 実行結果
// aの値(呼び出し前): 10
// aの値(関数内): 11
// aの値(呼び出し後): 10
また、この現象は構造体などで値を保持している場合も、同様に関数呼び出し時に変数の中身がコピーされます。
// https://play.golang.org/p/0r3Ba4Gx-X

type User struct { Name string }

func myFunc(user User) {
    user.Name = "Satoshi"
    fmt.Println("user.Nameの値(関数内):", user.Name)
}

func main() {
    user := User{"Yohei"}
    fmt.Println("user.Nameの値(呼び出し前):", user.Name)
    myFunc(user)
    fmt.Println("user.Nameの値(呼び出し後):", user.Name)
}

// 実行結果
// user.Nameの値(呼び出し前): Yohei
// user.Nameの値(関数内): Satoshi
// user.Nameの値(呼び出し後): Yohei
この辺の動きは、JavaやJavaScriptだと参照渡しになるので、それらの言語との違いで混乱しやすいところです。


関数へのポインタ渡し

そして、ポインタを用いることで、関数内での値の変更を呼び出し元にも反映することができます。具体的には関数の引数をポインタで受け取ることで、関数呼び出しでアドレスを共有することができます。
// intの例をポインターにした場合
// https://play.golang.org/p/gk6awn91iN

// 引数を「*int」にします
func myFunc(a *int) {
    *a += 1
    fmt.Println("aの値(関数内):", *a)
}

func main() {
    a := 10
    fmt.Println("aの値(呼び出し前):", a)
    
    // 呼び出しで、アドレスを渡すようにします。
    myFunc(&a)
    fmt.Println("aの値(呼び出し後):", a)
}

// 実行結果
// aの値(呼び出し前): 10
// aの値(関数内): 11
// aの値(呼び出し後): 11
// 構造体の例をポインターにした場合
// https://play.golang.org/p/FEmjK2uG21

type User struct { Name string }

// 引数を「*User」に変更します.
func myFunc(user *User) {
    user.Name = "Satoshi"
    fmt.Println("user.Nameの値(関数内):", user.Name)
}

func main() {
    user := User{"Yohei"}
    fmt.Println("user.Nameの値(呼び出し前):", user.Name)
    
    // 「&user」としてアドレスを渡します.
    myFunc(&user)
    fmt.Println("user.Nameの値(呼び出し後):", user.Name)
}

// 実行結果
// user.Nameの値(呼び出し前): Yohei
// user.Nameの値(関数内): Satoshi
// user.Nameの値(呼び出し後): Satoshi
という感じで、ポインターで渡すことで関数内での値の変更を、関数実行終了後にも保持することができます。
さてここで上記の構造体でのポインター例で、user.Nameのところ、正しくは(*user).Nameじゃないか、と思うかもしれませんが、構造体の場合は、どちらでも動作します(Go言語がいい感じに処理してくれます)。この辺のことはまた別のブログにでも書けたらなと思います。



参考資料

Go言語の学習は、以下を参照しています。どちらも少し難解ですが、中級以上になりたければ言語仕様を学んだ方がいいかなと思って、少しずつ読んでます。読むとためになるので楽しいです。

書籍:プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)

Web:http://golang.jp/go_spec



最後に

新しい言語に取り組むのは楽しいですね。せっかくやるならある程度ハマって、思想なども含めて自分の糧にしたいところです。今後もGo言語のブログはいっぱい書きたいと思います。

最後になりますが本ブログでは、Python・Linux・フロントエンド・Node.js・インフラ・開発環境・Go言語・Swift・Java・機械学習など雑多に情報発信をしていきます。自分の第2の脳にすべく、情報をブログに貯めています。気になった方は、本ブログのRSSTwitterをフォローして頂けると幸いです ^ ^。

最後までご覧頂きましてありがとうございました!





こんな記事もいかがですか?

RSS画像

もしご興味をお持ち頂けましたら、ぜひRSSへの登録をお願い致します。