はじめに
前回の続きで Go 言語の文法をまとめておきたいと思います。
- Go言語基礎 その1 Go言語入門
- Go言語基礎 その2 Go言語の開発環境と go コマンド
- Go言語基礎 その3 Go言語の基本構文1 (コメント, 型, 変数, 定数, 演算子)
- Go言語基礎 その4 Go言語の基本構文2 (関数, init, 制御構文)
- Go言語基礎 その5 Go言語の基本構文3 (配列, interface, slice, map, 型アサーション, Defined type, Type alias)
- Go言語基礎 その6 Go言語の基本構文4 (ポインタ, 構造体, メソッド, タグ, インターフェイス)
- Go言語基礎 その7 Go言語の基本構文5 (可視性, スコープ, goto, defer, panic, recover, goroutine, channel)
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"`
}
インターフェイス
- インターフェイスは型の一種
- 任意の型がどのようなメソッドを実装するべきか
- 代表的なインターフェイスが
error
やfmt.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 言語の文法の続きを復習したいと思います。