Goでは、structは フィールド と呼ばれる名前付きの要素を並べたものです。各フィールドは名前と型を持ちます。フィールドの名前は、その構造体の中で一意でなければなりません。構造体は、オブジェクト指向プログラミングのパラダイムにおける クラス にたとえられます。
typeとstructというキーワードを使って新しい構造体を作成し、フィールドの名前と型を明示的に定義します。たとえば、ここでは図形の名前とサイズを保持するShapeという構造体を定義します:
type Shape struct {
name string
size int
}
構造体のフィールド名はGoの慣習に従います。小文字で始まる名前のフィールドは同じパッケージ内のコードからしか参照できず、大文字で始まる名前のフィールドは他のパッケージからも参照できます。
structを定義したら、次はフィールド名を使って、任意の順序でフィールドを指定しながら新しいインスタンスを作成します:
s := Shape {
name: "Square",
size: 25,
}
インスタンスのフィールドを読み取ったり変更したりするには、.記法を使います:
// Update the name and the size
s.name = "Circle"
s.size = 35
fmt.Printf("name: %s size: %d\n", s.name, s.size)
// Output: name: Circle size: 35
初期値が割り当てられていないフィールドは、ゼロ値になります。たとえば:
s := Shape{name: "Circle"}
fmt.Printf("name: %s size: %d\n", s.name, s.size)
// Output: name: Circle size: 0
フィールドを 順番どおりに 定義するのであれば、フィールド名を使わずにstructのインスタンスを作成することもできます:
s := Shape {
"Oval",
20,
}
ただし、この構文は 壊れやすいコード と見なされます。フィールドが追加されたときに、特に新しいフィールドが別の型である場合に、壊れてしまうからです。次の例では、Shapeにフィールドをひとつ追加します:
type Shape struct {
name string
description string // new field 'description' added
size int
}
s := Shape{
"Circle",
20,
}
// Since the second field of the struct is now a string and not an int,
// the compiler will throw an error when compiling the program:
// Output: cannot use 20 (type untyped int) as type string in field value
// Output: too few values in Shape{...}
構造体のインスタンスを作成するのに役立つ関数があると便利なことがあります。慣習として、こうした関数は通常Newと呼ばれるか、名前がNewで始まりますが、ただの通常の関数なので、好きな名前を付けてもかまいません。他の言語のコンストラクターを思い起こさせるかもしれませんが、Goではこれらはただの通常の関数です。
次の例では、こうしたNew関数のひとつを使ってShapeの新しいインスタンスを作成し、図形のsizeのデフォルト値を自動的に設定しています:
func NewShape(name string) Shape {
return Shape{
name: name,
size: 100, //default-value for size is 100
}
}
NewShape("Triangle")
// => Shape { name: "Triangle", size: 100 }
New関数を使うと、次のような利点があります:
New関数は、初期化する構造体と同じパッケージで宣言されることが多いため、構造体の非公開フィールドも初期化できるこの演習では、さまざまな種類のラジコンカーのレースを組み立てていきます。 それぞれの車には、スピードとバッテリー消費という特性があります。
車はバッテリーが満タン(100%)の状態からスタートします。 ラジコンで車を操作して走らせるたびに、車はスピードの分だけメートル単位で進み、バッテリー残量はバッテリー消費の割合だけ減ります。
車のバッテリー残量がバッテリー消費の割合を下回ると、その車はもう走らせられません。
レースコースごとに距離は決まっています。 車は、バッテリー切れを起こさずにコースを完走できるかどうかで試されます。
次のint型のフィールドを持つCar構造体を定義します。
車のスピード(メートル)とバッテリー消費の割合を2つの入力(どちらもint型)として受け取り、Carのインスタンスを返す関数NewCarを定義すると、ラジコンカーを作れるようになります。
speed := 5
batteryDrain := 2
car := NewCar(speed, batteryDrain)
// => Car{speed: 5, batteryDrain: 2, battery:100, distance: 0}
整数型のフィールドdistanceを持つ、Trackという別の構造体を定義します。
コースの距離(メートル)を唯一の入力(int型)として受け取る関数NewTrackを定義すると、レースコースを作れるようになります。
distance := 800
track := NewTrack(distance)
// => Track{distance: 800}
車のスピードに応じて走ったメートル数を更新し、バッテリー消費に応じてバッテリーを減らすDrive関数を実装します。
もう一度走らせるだけのバッテリーがなければ、車は動きません。
speed := 5
batteryDrain := 2
car := NewCar(speed, batteryDrain)
car = Drive(car)
// => Car{speed: 5, batteryDrain: 2, battery: 98, distance: 5}
レースを完走するには、車がレースの距離を走りきれる必要があります。
つまり、ゴールラインを越える前にバッテリーを使い切らないということです。
CarとTrackのインスタンスを入力として受け取り、その車がレースを完走できるならtrueを、そうでなければfalseを返すCanFinish関数を実装します。
車はレースのスタートラインにいて、これからレースのためにエンジンをかけるものとします。 レースを始める時点で、車のバッテリーが必ずしも満タンとは限らないことに注意しましょう。
speed := 5
batteryDrain := 2
car := NewCar(speed, batteryDrain)
distance := 100
track := NewTrack(distance)
CanFinish(car, track)
// => true