Go言語基礎 その6 Go言語の基本構文4 (ポインタ, 構造体, メソッド, タグ, インターフェイス)

はじめに

前回の続きで Go 言語の文法をまとめておきたいと思います。

Go言語文法

ポインタ

  • ポインタとは変数のアドレスを格納している変数のこと
  • 参照型(slice, map, channel)や string は内部でポインタを保持しているため、これらの型のポインタは基本的に使用しない
// 
var p *int

// C言語と互換性をもたせるためポインタのポインタもかける
var pp **int
var ppp ***int

num := 10
np := &num // アドレス演算子
*num // dereference *num == 10

a := [3]string{"Hoge","Fuga","Piyo"}
p := &a
a[1]
p[1] // derefence しなくてもよい
for i, v := range a {
    fmt.Println(i, v)
}

// ポインタは型とメモリ上のアドレスを組み合わせたデータ型
// つまりメモリ上のアドレスを保持する
i := 5
p := &i
fmt.Printf("type=%T, address=%p\n", p, p)


// new(int) で `*int` 型のポインタを生成可能
i2 := new(int)
  • Go の string はかなり特殊
    • immutable(不変)なデータなので要素のアドレスを取得できない
    • Go は文字列の結合処理を行うたびに新しい文字列がメモリ上に生成される
s := "Hoge"
&s // OK
s[0] // == H
&s[0] // == Compile error

string 型の値を変数への再代入や関数の引数として使った場合であっても、文字列の実態が別のメモリにコピーされるわけではない
そのため、 *string として渡す必要は基本的にはない

https://cs.opensource.google/go/go/+/go1.17.7:src/builtin/builtin.go

// string is the set of all strings of 8-bit bytes, conventionally but not // necessarily representing UTF-8-encoded text. A string may be empty, but // not nil. Values of string type are immutable.

https://github.com/golang/go/blob/go1.16.2/src/runtime/string.go#L228-L231

type stringStruct struct {
	str unsafe.Pointer
	len int
}

構造体

様々なデータ構造を 1 つの型として取り扱うことができる

type Point struct {
    x, y int
}
p Point
p.x = 5
p.y = 7

type Person struct{
    ID, name string
    age uint
    height, weight float64
}
p := Person{
    ID: "A1111",
    Name: "田中太郎",
    age: 65,
    height: 165.0,
    weight: 55.0,
}

type T struct {
    N uint
    _ int16 // 無名フィールド
   S []string
}

type Wheel struct {
    Name string
    Radius int
}
type Car struct{
    Name string
    Wheel Wheel
    // Wheelといった書き方でも良い
}

car := Car{
    Name: "Ferrali",
    Wheel: Wheel{
        Name: "Michelin",
        Size: 100,
    }
}

埋め込み構造体 (embedded struct)

type Wheel struct {
    Name string
    Radius int
}
type Car struct{
    Name string
    Wheel
}
// フィールド名が一意の場合、中間のフィールド名を省略してアクセスできる
car.Size = 50

構造体の互換性

type Point struct {
    X, Y int
}

func print(p struct{ X, Y int}) {
    fmt.Println(p)
}
p := Point{X: 3, Y: 5}
print(p)

構造体は値型

func swap(p Point) {
    x, y := p.Y, p.X
    p.X = x
    p.Y = y
}

p := Point{X: 3, Y: 5}
swap(p)
fmt.Println(p.X, p.Y) // ==> 3, 5

メソッド

  • 任意の型に特化した関数を定義するための仕組み
  • メソッドではレシーバの型とその変数名が必要
  • エイリアスにもメソッドを定義できる
  • メソッドはレシーバーを第一引数として取る単なる関数
  • 原則、レシーバーはポインタ型にすべき
type Point struct {X, Y int}
func (p *Point) Render() {
    fmt.Printf("%d, %d", p.X, p.Y)
}
p := Point{}
p.Render()

コンストラクタはないが NewUser のように New型名 の関数を作成してコンストラクタのように型を生成するのが慣習

type User struct { Name string }
func NewUser(name string) *User {
    u := new(User)
    u.Name = name
    return u
}

タグ

  • Go の構造体にはタグというフィールドにメタ情報を付与する機能がある
  • reflect パッケージを使用して取り出すことが可能
  • json パッケージ等で有効に活用されている
type User struct {
    Id int "ユーザーID"
}

func main() {
    u := User{}
    t := reflect.TypeOf(u)
    for i := 0; i < t.NumField(); i++ {
        f := t.Field(i)
        fmt.Println(f.Name, f.Tag)
    }
}
type User struct {
    Id int `json:"user_id"`
}

インターフェイス

  • インターフェイスは型の一種
  • 任意の型がどのようなメソッドを実装するべきか
  • 代表的なインターフェイスが errorfmt.Stringer
    • fmt.Stringer インターフェイスを実装することで fmt.Print* 系の関数の出力文字列をカスタマイズできる
type error interface {
    Error() string
}
type MyError struct{
    Message string
}
// error interfaceを実装
func (e *MyError) Error() string() {
    return e.Message
}
func returnError() error {
    return &MyError{
        Message: "My Error!"
    }
}

e := returnError()
fmt.Println(e.Error()) // => "My Error!"

型アサーションで本来の型を取り出せる

e, ok := err.(*MyError)
if ok {
    e.Message
}
  • インターフェイスにはインターフェイスを含めることができる(メソッド名を重複させてはいけない)
  • interface{} とは実装すべきメソッドが一つも定義されていないインターフェイス = 空のインターフェイス = どんな型でも満たす
type I0 interface {
    Method1() int
}
type I1 interface {
    I0
    Method2() int
}
type I2 interface {
    I1
    Method3() int
}

参考にした本

おわりに

次回は Go 言語の文法の続きを復習したいと思います。