Golangの構造体(struct)の基礎


Golangの構造体(struct)の基礎

エンジニアになりたての雑魚エンジニアがGolangを学習した忘備録的な記事なので、
間違い等があったら、優しく指摘してもらえるとありがたいです。

参照 スターティングGo言語

構造体(struct)とは?

複数の任意の型の値を一つにまとめたものである。
基本型、配列型、関数型、参照型、ポインタ型などの様々なデータ構造を持つ型を1つの型として取り扱うことが可能となる。

struct {
 //ここに任意の変数名 型を定義していく
 Name string
 Age int
}
//また個々のデータをフィールドと呼ぶ
//struct { [フィールドの定義] }

構造体に入る前に・・・type!

型から新しい型を定義するtype

typeとは既にある型をもとに新しい型を定義するための機能である。
これによってエイリアス(別名)を定義することができる。
しかし、あくまで既存の型から新しい型を定義している為、型の互換性は気をつけなければならない。

type MyInt int
//これで、int型から新しい型であるMyInt型が定義される
//type [新しく定義する型] [既存の型]

これを複数でまとめて書くこともできる

type (
  IntPair [2]int
  String []string
  AreaMap map[string][2]float64
)
//type ()でまとめて定義することも可能

メリットとしては、複雑な型定義をプログラムから取り除くことができるため、
コードの見通しがよくなる

型の互換性に注意するとは?

下記のコードでパターン1とパターン2は同じだが、パターン3ではコンパイルエラーとなる

type T0 int
type T1 int

//パターン1
t0 := T0(5)
//5
i0 := int(t0)
//5

//パターン2
t1 := T1(8)
//8
i1 := int(t1)
//8

パターン3
t0 = i0
//コンパイルエラー

エイリアスの元となる型とエイリアスで定義した型には互換性が保たれるが、同じint型を元にしたエイリアス同士では型の互換性がなくなる。
エイリアス間では型の互換性がなくなることに注意しなければならない。

また関数型にもエイリアスを定義できる

type CallBack func(i int) int

//func(i int) int型をCallBackとエイリアスを定義

//第2引数の宣言がtype使わなければ、func(i int) intと書かなければならず、
//なんのための関数だか、見ただけで分からず可読性が下がる
func Sum(ints []int, callback CallBack) int {
    var sum int
    for _, i := range ints {
        sum += i
    }
    return callback(sum)
}

func main() {
    n := Sum(
        []int{1, 2, 3, 4, 5},
        //ここの部分のを丸っとCallBackとして別名で使える
        //====
        func(i int) int {
            return i * 2
        },
        //====
    )
    fmt.Println(n)
    //30
}

typeと構造体

構造体は上述したように複数の任意の型の値を一つにまとめたものである。
そのまとめた構造体にtypeを使って、新しい型名を与えられる。
構造体は基本的にtypeと併用して使用されるケースが多い。

type Point struct {
  X int
  Y int
}

//structで定義された構造体にtypeを使って新しい型名を与える

構造体の初期値と代入

構造体は値の一種であり、構造体型の変数を定義すると構造体に定義されている各フィールドに必要なメモリ領域が確保され、型に合わせた初期値を取る

type Point struct {
  X int
  Y int
}

//Point型の変数を定義
//初期値はint型の0をとっている
var pt Point
pt.X //0
pt.Y //0

//代入
pt.X = 10
pt.Y = 12

pt.X //10
pt.Y //12

//初期値をまとめて与えることもできる
pt := Point{ 1, 2 }
//フィールドを明示的に指定して初期値を定義することもできる
pt := Point{ X: 1, Y: 2 }
//この方法の方が、与えたい初期値を反対に書いてしまう等の
//ヒューマンエラーを防ぐことに加え、コードの可読性も増す

構造体とポインタ

構造体は値型である
関数の引数として構造体を渡しても、値型の為、構造体のコピーがメモリに新たに生成され、それが関数で処理される。
元の構造体には影響を与えられない。

type Point struct {
    X, Y int
}

func swap(p Point) {
    フィールドX,Yの値を入れ替え
    x, y = p.Y, p.X
    p.X = x
    P.Y = y
}

//X, Yへ値を代入
p := Point(X: 1, Y: 2)

//値の入れ替えを関数で行う
swap(p)

swap関数で処理した後も構造体の値は変わらない
p.X //1
p.Y //2

変数pが示す元の構造体とswap関数内のコピー構造体は別であることが分かる

値渡しは、新たに別のメモリ領域にコピーを生成してしまうため、
これを元のアドレスを渡す、参照渡しで関数に構造体を渡す必要がある。
そこで、ポインタを使用する。

type Point struct {
    X, Y int
}

//Point型のポインタへ引数を変更
func swap(p *Point) {
    フィールドX,Yの値を入れ替え
    x, y = p.Y, p.X
    p.X = x
    P.Y = y
}

//X, Yへ値を代入しPoint型のポインタを生成
p := &Point(X: 1, Y: 2)

//値の入れ替えを関数で行う
swap(p)

p.X //2
p.Y //1

構造体は主にポインタ型を経由して使用する。
こうすることで、値渡しではなく、参照渡しで処理を実現することができる。