いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Goメモ-19 (ポインタ, Pointer, Tour of Go)

概要

Tour of Go の - Pointer についてのサンプル。

tour.golang.org

Go には、C言語などと同様にポインタが存在します。

ポインタは、対象の値のメモリアドレスを保持します。ゼロ値はnilです。

C言語とは違い、Go ではポインタ演算は出来ないようになっています。

使い方もC言語と同じです。

   var (
        i int
        p *int
    )

    i = 100
    p = &i

    fmt.Printf("i=%d\tp=%p\t*p=%d\n", i, p, *p)

    i = 200

    fmt.Printf("i=%d\tp=%p\t*p=%d\n", i, p, *p)

    *p = 300

    fmt.Printf("i=%d\tp=%p\t*p=%d\n", i, p, *p)

上記では、intの変数 i と、intのポインタ p を宣言して、i のアドレスを p に設定しています。

p は、i と同じアドレスを保持しているので、 i の値を変更すると p の指し示す先の値も変わっている。

同じ理屈で、p の指し示す先の値を変更すると、i の値も変わる。

i=100    p=0xc0000660a8  *p=100
i=200   p=0xc0000660a8  *p=200
i=300   p=0xc0000660a8  *p=300

ポインタを利用するのが多いパターンは、関数にデータを渡すときですね。

Goでは、値のコピーが発生するので、そのまま渡すと元の値をコピーした別のものが渡ります。

なので、渡した先の関数内で、そのデータの情報を変更しても、コピーに対して操作しているので元の値が変更されません。

そこで、ポインタを渡すことにより、ポインタの値(メモリアドレス)がコピーされて渡るので、指し示す先の値は同じものを操作できます。

また、巨大な構造体などをそのまま渡すとコピーすると、メモリ上でも処理上でもオーバヘッドがかかるので、そんなときもポインタを使ったりします。

   var (
        i int
        p *int

        f1 func(v int)
        f2 func(v *int)
    )

    i = 100
    p = &i

    f1 = func(v int) {
        v = 888
    }

    f2 = func(v *int) {
        *v = 999
    }

    fmt.Printf("i=%d\tp=%p\t*p=%d\n", i, p, *p)

    f1(i)

    fmt.Printf("i=%d\tp=%p\t*p=%d\n", i, p, *p)

    f2(p)

    fmt.Printf("i=%d\tp=%p\t*p=%d\n", i, p, *p)
i=100    p=0xc000064100  *p=100
i=100   p=0xc000064100  *p=100
i=999   p=0xc000064100  *p=999

自分で定義した構造体の場合も同様です。

   type (
        myType struct {
            Value int
        }
    )

    var (
        m myType
        p *myType

        f1 func(v myType)
        f2 func(v *myType)
    )

    m = myType{
        Value: 100,
    }

    p = &m

    f1 = func(v myType) {
        v.Value = 888
    }

    f2 = func(v *myType) {
        v.Value = 999
    }

    fmt.Printf("m=%v\tp=%p\t*p=%v\n", m, p, *p)

    f1(m)

    fmt.Printf("m=%v\tp=%p\t*p=%v\n", m, p, *p)

    f2(p)

    fmt.Printf("m=%v\tp=%p\t*p=%v\n", m, p, *p)
m={100}  p=0xc00000a170  *p={100}
m={100} p=0xc00000a170  *p={100}
m={999} p=0xc00000a170  *p={999}

サンプル

package tutorial

import "fmt"

// Pointer は、 Tour of Go - Pointers (https://tour.golang.org/moretypes/1) の サンプルです。
func Pointer() error {
    // ------------------------------------------------------------
    // Go言語のポインタ
    //
    // Go言語は、C や C++ と同様に ポインタ型 を持っている
    // 使い方もほぼ同じであるが、ポインタ演算は出来ないようになっている
    //
    // ポインタのゼロ値はnil.
    // ------------------------------------------------------------
    //noinspection GoVarAndConstTypeMayBeOmitted
    var (
        zeroPointer *int
        i           int         = 100
        p           *int        = &i
        arr         [2]int      = [2]int{0, 0}
        sli         []int       = []int{0, 0}
        m           map[int]int = map[int]int{1: 0, 2: 0}
    )

    //noinspection GoNilness
    fmt.Printf("zeroPointer is nil? [%v]\n", zeroPointer == nil)

    showValue(i, p)

    updateValue(p, 200)
    showValue(i, p)

    updateValue(&i, 300)
    showValue(i, p)

    // 配列
    fmt.Println(arr)
    updateArrayNotPointer(arr)
    fmt.Println(arr)
    updateArrayPointer(&arr)
    fmt.Println(arr)

    // スライス
    fmt.Println(sli)
    updateSliceNotPointer(sli)
    fmt.Println(sli)
    updateSlicePointer(&sli)
    fmt.Println(sli)

    // マップ
    fmt.Println(m)
    updateMapNotPointer(m)
    fmt.Println(m)
    updateMapPointer(&m)
    fmt.Println(m)

    return nil
}

func showValue(i int, p *int) {
    fmt.Printf("i=%d\tp=%p\t*p=%d\n", i, p, *p)
}

func updateValue(p *int, v int) {
    *p = v
}

func updateArrayNotPointer(arr [2]int) {
    // ポインタで渡していないので、値がコピーされて関数に渡る
    // なので、値を変更しても元の配列には影響しない
    arr[0] = 100
    arr[1] = 200
}

func updateArrayPointer(arr *[2]int) {
    // ポインタで渡しているので、参照(ポインタ)がコピーされた関数に渡る
    // なので、値を変更すると元の配列にも影響する
    // 本来であれば、arrは配列へのポインタなのでインデックス操作が出来ない
    // はずであるが、配列の場合、言語側で特殊措置が行われるため
    // ポインタであっても、そのままインデックス操作が行えるようになっている
    arr[0] = 100
    arr[1] = 200
}

func updateSliceNotPointer(sli []int) {
    // Go言語のスライスは、内部に参照先へのポインタを持っている構造になっている
    // なので、ポインタを取得して渡さなくても、値のコピー時に内部のポインタがコピーされるため
    // 結果として、同じ参照先を更新することになる。
    sli[0] = 100
    sli[1] = 200
}

func updateSlicePointer(sli *[]int) {
    // わざわざスライスのポインタを取得すると、一旦 ポインタの実態 を取り出す手間が必要となる
    // スライスは、内部に参照先へのポインタを持っているため、ポインタ取得せずそのまま関数に渡しても問題ない
    (*sli)[0] = 300
    (*sli)[1] = 400
}

func updateMapNotPointer(m map[int]int) {
    // マップもスライスと同様に内部に参照先へのポインタを持っているので
    // わざわざポインタを取得して渡す必要はない
    m[1] = 100
    m[2] = 200
}

func updateMapPointer(m *map[int]int) {
    // スライスと同様に、マップのポインタからは直接 マップ のキー指定(インデックス指定)
    // が出来ないので、一旦デリファレンスをする必要がある
    (*m)[1] = 300
    (*m)[2] = 400
}

try-golang/tutorial_gotour_13_pointer.go at master · devlights/try-golang · GitHub

実行すると以下な感じ。

[Name] "tutorial_gotour_pointer"
zeroPointer is nil? [true]
i=100   p=0xc000062100  *p=100
i=200   p=0xc000062100  *p=200
i=300   p=0xc000062100  *p=300
[0 0]
[0 0]
[100 200]
[0 0]
[100 200]
[300 400]
map[1:0 2:0]
map[1:100 2:200]
map[1:300 2:400]

過去の記事については、以下のページからご参照下さい。

  • いろいろ備忘録日記まとめ

devlights.github.io

サンプルコードは、以下の場所で公開しています。

  • いろいろ備忘録日記サンプルソース置き場

github.com

github.com

github.com