LoginSignup
2
1

More than 5 years have passed since last update.

Goのポインタについてのサンプルと注意点

Last updated at Posted at 2018-08-25

Go言語の勉強会(基礎編)のためにまとめた内容です。

おさらい

  • ポインタは値型のデータ構造(基本型、参照型、構造体)のメモリ上のアドレスと型の情報
  • Goではこれを使ってデータ構造を間接的に参照・操作できる

アドレス演算子とデリファレンス

  • アドレス演算子&で任意の型からポインタ型を生成できる
  • *でデリファレンスするとポインタ型を通して実体のデータにアクセスできる
var hoge int
p := &hoge //アドレス演算子

fmt.Printf("hoge :%d ", *p) // *でデリファレンス

サンプル

package main

import (
    "fmt"
)

func main () {
    var i int

    i = 100

    p := &i

    fmt.Printf("Type: %T\n", p)
    fmt.Printf("i = %d\n", *p)

    fmt.Println("---------------")

    pp := &p

    fmt.Printf("Type: %T\n", pp)
    fmt.Printf("i = %d\n", *pp)     // デリファレンスして実体を指すポインタにアクセス
    fmt.Printf("i = %d\n", **pp)    // 更にデリファレンスして実体にアクセス

    fmt.Printf("address = %p\n", pp)      // %pでアドレスを表示できる
}

実行結果

Type: *int
i = 100
---------------
Type: **int
i = 842350542944
i = 100
address = 0xc42000c028

関数の引数で使う場合

  • 値渡し(値のコピー)
    • 関数内で値を操作した場合、コピーされた値を操作している
  • ポインタ
    • 値の実体ではなく参照しているアドレスと型の情報
    • funcの引数にポインタを渡すとそれらの値がコピーされる
    • ポインタを通して実体にアクセスできる

サンプル

package main

import "fmt"

func main() {
    var value int

    value = 1

    //値をそのまま渡す
    fmt.Printf("before value: %d\n", value)
    copy_value(value)
    fmt.Printf("after value: %d\n", value)

    p_value := &value

    //値のポインタを渡す
    fmt.Printf("before p_value: %d\n", *p_value)
    ref_value(p_value)
    fmt.Printf("after p_value: %d\n", *p_value)

}

func copy_value (value int) {
    value = 10
}

func ref_value (p_value *int) {
    *p_value = 10
}

実行結果

before value: 1
after value: 1
before p_value: 1
after p_value: 10

配列へのポインタ型

サンプル

package main

import (
    "fmt"
)

func main() {
    v := [3]int{1, 2, 3}
    fmt.Printf("%d\n", v[0]) // これは普通

    p := &[3]int{1, 2, 3}
    //fmt.Printf("%d\n", *p[0]) // コンパイルエラー
    fmt.Printf("%d\n", (*p)[0]) // デリファレンス
    fmt.Printf("%d\n", p[0]) // *つけなくてもデリファレンスできてる


    fmt.Println("built in functions and slice")
    fmt.Println(len(p))
    fmt.Println(cap(p))
    fmt.Println(p[0:2])

    //builtInFunc(p)
}

func builtInFunc(array *[3]int) {
    fmt.Println("built in functions and slice")
    fmt.Println(len(array))
    fmt.Println(cap(array))
    fmt.Println(array[0:2])
}

実行結果

1
1
1
built in functions and slice
3
3
[1 2]

解説

  • 配列のポインタの場合、コンパイラが自動的に(*p)[i]形式に置き換えてくれる
  • 組み込み関数やsliceも配列へのポインタのデリファレンスを省略できる

文字列での注意点

サンプル

package main

import "fmt"

func main() {
    s := "HelloCamp!"

    fmt.Printf("%T\n", &s)
    fmt.Printf("%T\n", s[0])
    fmt.Printf("%s\n", &s[0]) // cannot take the address of s[0]
}

解説

  • 文字列に対するポインタは特殊
    • Goのstringはimmutable(不変)なため
    • 文字のindex(byteのポインタ)はエラーになる
  • ポインタからは外れるがstring自体について
    • 文字の結合処理は繰り返すとその分メモリに新しい文字列が生成されるため効率が悪い
    • 変数への再代入や関数の引数に使っても文字の実体がコピーされることはない
    • string型は型の仕組みそのものにポインタの仕組みを持っている

構造体でのポインタの利用

サンプル

package main

import "fmt"

type Box struct {
    width, height int
}

func switchLength(b Box) {
    width, height := b.width, b.height

    b.width = height
    b.height = width
}

func switchLengthP(b *Box) {
    width, height := b.width, b.height

    b.width = height
    b.height = width
}

func main () {
    b := Box{width:100, height:200}
    //コピーに対しての操作になる
    switchLength(b)
    fmt.Printf("%+v\n", b) //%+vでフィールド名も表示

    p_b := Box{width:100, height:200}
    switchLengthP(&p_b)
    fmt.Printf("%+v\n", p_b)

    p_bn := &Box{width:100, height:200} //こちらの方がより自然な使い方
    switchLengthP(p_bn)
    fmt.Printf("%+v\n", p_bn)
}

実行結果

{width:100 height:200}
{width:200 height:100}
&{width:200 height:100}

解説

  • 構造体は値型。そのため関数の引数として渡した場合はコピーが生成され、コピーが関数内で使用されるためもとの構造体には影響しない

サンプル その2

package main

import "fmt"

type ColorBox struct {
    width, height int
}

func NewColorBox(width int, height int) *ColorBox {
    b := new(ColorBox)
    b.width = width
    b.height = height

    return b
}

func main() {
    nb := new(ColorBox)
    fmt.Printf("%+v\n", nb)

    nb.width = 100
    nb.height = 150
    fmt.Printf("%+v\n", nb)

    fmt.Println("--address operator--")

    nb_address := &ColorBox{}
    fmt.Printf("%+v\n", nb_address)

    nb_address2 := &ColorBox{width: 100, height: 150}
    fmt.Printf("%+v\n", nb_address2)

    fmt.Println("--constructor--")

    nc := NewColorBox(400, 500)
    fmt.Printf("%+v\n", nc)
}

実行結果

&{width:0 height:0}
&{width:100 height:150}
--address operator--
&{width:0 height:0}
&{width:100 height:150}
--constructor--
&{width:400 height:500}

解説

  • new で初期化とポインタの生成が可能
    • 値がゼロ値で生成される(0でなく利用準備が整った初期値のような意味)
  • 他の言語のコンストラクタとは違う
    • コンストラクタ機能はなく、newを使って自分で実装する(func NewColorBox)
2
1
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
2
1