Goのポインタと構造体
最近、Goで何か作りたい欲が出てきました。
Goプログラミングではポインタと構造体がよくセットで使われるらしいのでやっていきます
まずはポインタ。
ポインタ
ポインタとは、「値型(value type)」に分類されるデータ構造(基本型や参照型、構造体など)のメモリ上のアドレスと型の情報。
ポインタの定義
ポインタ型は*int
のように、参照・操作したい型の前に*
をつけることで定義できる。
*float64
であればfloat64型のポインタに、*[2]int
であれば*[2]int型のポインタになる。
また、定義した時の初期値はnil
func main() {
var p *int
fmt.Println(p == nil)
}
true
ポインタ定義につかった「*」を重ねると、ポインタのポインタを定義できる。
むずそうだしあまり使わないらしいので紹介だけ。
var **int // int型のポインタを参照・操作するためのポインタ型
さらに、参照型にもポインタ型を定義できるらしいが、参照型は元から参照渡しが可能なので、必要なのかどうかはわからない。
var (
s *[]stirng
m *map[int]int
)
アドレス演算子とデリファレンス
演算子「&」を使って、任意の型からそのポインタを生成できる。アドレス演算子と呼ばれるらしい。
func main() {
var i int
p := &i
fmt.Printf("%T\n", p)
}
*int
ポインタ型から値を参照するには演算子「*」をポインタ型変数の前に置くとポインタ型が指すデータの「デリファレンス」ができる。
func main() {
var i int
p := &i
i = 5
fmt.Println(i)
*p = 10
fmt.Println(i)
}
5
10
これらを利用して、関数の引数へ値型の参照渡しが可能。
func squ(p *int) {
*p *= *p
}
func main() {
i := 2
squ(&i)
squ(&i)
fmt.Println(i)
}
16
ポインタ型の値
冒頭に説明した通り、ポインタ型は**「値型(value type)」に分類されるデータ構造のメモリ上のアドレスと型の情報**なので、ポインタは値として「メモリ上のアドレス」を保持する。試してみる。
func main() {
i := 0
p := &i
fmt.Printf("type: %T address: %p\n", p, p)
}
type: *int address: 0xc00007c090
構造体
構造体とは「複数の任意の型の値を一つにまとめたもの」。
基本型、配列型、参照型、ポインタ型等のさまざまなデータ構造を一つの型として扱える。
type
構造体の前に、Goの予約語typeについて確認。type [定義する型] [既定の型]
のように使用。
func main() {
type MyInt int
var a MyInt = 3
b := MyInt(5)
fmt.Println(a, b)
}
3 5
上記は基本の使い方だが、これではtype
の恩恵がわからないので少し活用法を。
次のように、map[string]int
などの複雑な型にHumanのようなエイリアスを定義することでコードを見やすくする事ができる。
type (
IntPair [2]int
Human map[string]int
)
pair := IntPair{1, 2}
human := Human{"Taro": 19}
構造体の定義
構造体を使用するには、一般的にはtypeと組み合わせて新しい型を定義する。
順序はstructで定義された構造体に、typeによって新しい型名を与えるといった感じ。
type Point struct {
X, Y int
}
構造体は値型の1種なので、それぞれのフィールドに必要なメモリ領域が確保され、それぞれフィールドは型に合った初期値を取る。
構造体のフィールドを参照するには[構造体型].[フィールド名]
というように「.」で区切って表現する。
func main() {
var pt Point
fmt.Println(pt.X, pt.Y)
pt.X, pt.Y = 3, 5
fmt.Println(pt.X, pt.Y)
}
0 0
3 5
複合リテラル
構造体に初期値を指定しつつ構造体を生成するための、複合リテラルがある。int
などでも似たような形があるのでさらっと。
pt := Point{1, 2}
上記の書き方では。最初に定義されたXに一番目の値が、Yに二番目の値が代入されるが、仮に構造体のフィールド定義に変更があった場合にズレてしまう。
それを回避する為に、次のような明示的な定義ができる
pt := Point{Y:2, X:1}
二つの定義方法があるが、書くのが少し面倒でも後者を使った方が後々楽な可能性が高そう
構造体 in 構造体
構造体の中に構造体を定義できる。フィールド名を省略すると、フィールド名が一意に定まる場合のみフィールド名を省略して参照・操作できる
Age
は一つしかないため、a.Data.Age
をa.Age
といったように省略できる。構造体がどれだけ入れ子になっても、一意である限り使える。
type Data struct {
Name string
Age int
}
type Human struct {
Name string
Data /*フィールド名を省略した埋め込み 省略しない場合 => ` Data Data ` */
}
func main() {
a := Human{
Name: "Sato",
Data: Data{
Name: "Taro",
Age: 19,
},
}
fmt.Println(a.Name, a.Data.Name, a.Age)
}
Sato Taro 19
構造体とポインタ
構造体は値型なため、関数の引数に指定したときは構造体のコピーが渡されてしまう。
なので以下のようなコードを書いたら上手くいかない。
type Point struct {
X, Y int
}
func swap(p Point) {
p.X, p.Y = p.Y, p.X
}
func main() {
p := Point{1, 2}
swap(p)
fmt.Println(p)
}
{1, 2}
これを解決するためにポインタを使う。
先ほど説明した値型の参照渡しをする。変更箇所は2箇所のみ。
type Point struct {
X, Y int
}
func swap(p *Point) { // Point型から*Point型を受け取るように変更。
p.X, p.Y = p.Y, p.X
}
func main() {
p := Point{1, 2}
swap(&p) // 引数をpからpのポインタに変更
fmt.Println(p)
}
{2, 1}
まとめ
この記事を書くにあたって、スターティングGo言語という本を参考にさせて頂いております。
Goの書き方などについて、とてもわかりやすくまとまっているので興味があったら是非お手にとってお読みください。
以上、「Goのポインタと構造体」でした。最後まで読んでいただき、ありがとうございました。