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)