2.4 struct型
struct
Go言語では、Cや他の言語と同じように、他の型の属性やフィールドのコンテナとして新しい型を宣言することができます。例えば、一個人のエンティティを表しているperson
型を作成することができます。このエンティティは属性を持っています:性別と年齢です。このような型はstruct
と呼ばれます。以下にコードを示します:
type person struct {
name string
age int
}
お分かりいただけましたでしょうか?structの宣言はこのように簡単です。上の型は2つのフィールドを持っています。
- string型のフィールドnameはユーザの名前を保存するプロパティです。
- int型のフィールドageはユーザの年齢を保存するプロパティです。
どのようにstructは使用されるのでしょうか?下のコードをご覧ください。
type person struct {
name string
age int
}
var P person // Pは現在person型の変数です。
P.name = "Astaxie" // "Astaxie"を変数Pのnameプロパティに代入します。
P.age = 25 // "25"を変数Pのageプロパティに代入します。
fmt.Printf("The person's name is %s", P.name) // Pのnameプロパティにアクセスします。
上のようなPの宣言以外に他にもいくつかの宣言方法があります。
1.順序にしたがって初期化する。
P := person{"Tom", 25}
2.
field:value
の方法によって初期化します。この場合は順序は任意でかまいません。P := person{age:24, name:"Tom"}
3.もちろん
new
関数を通してポインタを作ることもできます。このPの型は*personです。P := new(person)
以下ではひと通りのstructの使用例をご説明します。
package main
import "fmt"
// 新しい型を宣言します。
type person struct {
name string
age int
}
// 二人の年齢を比較します。年齢が大きい方の人を返し、また年齢差も返します。
// structも値渡しです。
func Older(p1, p2 person) (person, int) {
if p1.age>p2.age { // p1とp2の二人の年齢を比較します。
return p1, p1.age-p2.age
}
return p2, p2.age-p1.age
}
func main() {
var tom person
// 初期値を代入します。
tom.name, tom.age = "Tom", 18
// 2つのフィールドを明確に初期化します。
bob := person{age:25, name:"Bob"}
// structの定義の順番に従って初期化します。
paul := person{"Paul", 43}
tb_Older, tb_diff := Older(tom, bob)
tp_Older, tp_diff := Older(tom, paul)
bp_Older, bp_diff := Older(bob, paul)
fmt.Printf("Of %s and %s, %s is older by %d years\n",
tom.name, bob.name, tb_Older.name, tb_diff)
fmt.Printf("Of %s and %s, %s is older by %d years\n",
tom.name, paul.name, tp_Older.name, tp_diff)
fmt.Printf("Of %s and %s, %s is older by %d years\n",
bob.name, paul.name, bp_Older.name, bp_diff)
}
structの匿名フィールド
上でstructをどのように定義するかご紹介しました。定義する際はフィールド名とその型が一つ一つ対応しています。実はGoは型だけの定義もサポートしています。これはフィールド名を書かない方法ではなく、匿名フィールドです。組み込みフィールドとも呼ばれます。
匿名フィールドがstructである場合、このstructがもつすべてのフィールドは隠されたまま現在定義しているこのstructに導入されます。
ひとつ例をお見せしましょう。上の説明がより具体的になります。
package main
import "fmt"
type Human struct {
name string
age int
weight int
}
type Student struct {
Human // 匿名フィールド、デフォルトでStudentはHumanのすべてのフィールドを含むことになります。
speciality string
}
func main() {
// 学生を一人初期化します。
mark := Student{Human{"Mark", 25, 120}, "Computer Science"}
// 対応するフィールドにアクセスします。
fmt.Println("His name is ", mark.name)
fmt.Println("His age is ", mark.age)
fmt.Println("His weight is ", mark.weight)
fmt.Println("His speciality is ", mark.speciality)
// 対応するメモ情報を修正します。
mark.speciality = "AI"
fmt.Println("Mark changed his speciality")
fmt.Println("His speciality is ", mark.speciality)
// 彼の年齢情報を修正します。
fmt.Println("Mark become old")
mark.age = 46
fmt.Println("His age is", mark.age)
// 体重情報も修正します。
fmt.Println("Mark is not an athlet anymore")
mark.weight += 60
fmt.Println("His weight is", mark.weight)
}
図解:
図2.7 StudentとHumanの継承方法
Studentがageとnameの属性にアクセスする際、あたかも自分のフィールドであるかのようにアクセスしたのをご覧いただけるかと思います。そうです。匿名フィールドというのはこういうものです。フィールドの継承を実現できるのです。これってクールじゃないですか?もっとクールにする方法もありますよ。studentはHumanのフィールド名でアクセスできます。下のコードを御覧ください。ほら、とってもクールでしょ?
mark.Human = Human{"Marcus", 55, 220}
mark.Human.age -= 1
匿名によるアクセスとフィールドの修正はとても便利です。でも単なるstructのフィールドですから、すべてのビルトイン型と自分で定義した型をすべて匿名フィールドとみなすことができます。下の例をご覧ください。
package main
import "fmt"
type Skills []string
type Human struct {
name string
age int
weight int
}
type Student struct {
Human // 匿名フィールド、struct
Skills // 匿名フィールド、自分で定義した型。string slice
int // ビルトイン型を匿名フィールドとします。
speciality string
}
func main() {
// 学生Jannを初期化します。
jane := Student{Human:Human{"Jane", 35, 100}, speciality:"Biology"}
// ここで対応するフィールドにアクセスしてみます。
fmt.Println("Her name is ", jane.name)
fmt.Println("Her age is ", jane.age)
fmt.Println("Her weight is ", jane.weight)
fmt.Println("Her speciality is ", jane.speciality)
// 彼のskill技能フィールドを修正します。
jane.Skills = []string{"anatomy"}
fmt.Println("Her skills are ", jane.Skills)
fmt.Println("She acquired two new ones ")
jane.Skills = append(jane.Skills, "physics", "golang")
fmt.Println("Her skills now are ", jane.Skills)
// 匿名ビルトイン型のフィールドを修正します。
jane.int = 3
fmt.Println("Her preferred number is", jane.int)
}
上の例のとおり、structはstructを匿名フィールドとするだけでなく、自分で定義した型やビルトイン型も匿名フィールドとすることができます。また、対応するフィールド上で関数操作を行うこともできます(例えば例の中のappendです)。
ここで一つ疑問がでてきます:もしhumanにphoneというフィールドがあったとすると、studentもphoneと呼ばれるフィールドができます。これはどうすべきでしょうか?
Goでは簡単にこの問題を解決することができます。外側が優先的にアクセスされますので、student.phone
とアクセスした場合studentの中のフィールドにアクセスし、humanのフィールドにはアクセスしません。
このように匿名フィールドを通じてフィールドを継承することができます。当然もしあなたが対応する匿名型のフィールドにアクセスしたくなったら、匿名フィールドの名前からアクセスすることができます。下の例をご覧ください。
package main
import "fmt"
type Human struct {
name string
age int
phone string // Human型がもつフィールド
}
type Employee struct {
Human // 匿名フィールドHuman
speciality string
phone string // 社員のphoneフィールド
}
func main() {
Bob := Employee{Human{"Bob", 34, "777-444-XXXX"}, "Designer", "333-222"}
fmt.Println("Bob's work phone is:", Bob.phone)
// もし我々がHumanのphoneフィールドにアクセスする場合は
fmt.Println("Bob's personal phone is:", Bob.Human.phone)
}