2.4 struct

struct

We can define new types of containers of other properties or fields in Go just like in other programming languages. For example, we can create a type called person to represent a person, with fields name and age. We call this kind of type a struct.

  1. type person struct {
  2. name string
  3. age int
  4. }

Look how easy it is to define a struct!

There are two fields.

  • name is a string used to store a person’s name.
  • age is a int used to store a person’s age.

Let’s see how to use it.

  1. type person struct {
  2. name string
  3. age int
  4. }
  5. var P person // p is person type
  6. P.name = "Astaxie" // assign "Astaxie" to the field 'name' of p
  7. P.age = 25 // assign 25 to field 'age' of p
  8. fmt.Printf("The person's name is %s\n", P.name) // access field 'name' of p

There are three more ways to initialize a struct.

  • Assign initial values by order
    1. P := person{"Tom", 25}
  • Use the format field:value to initialize the struct without order
    1. P := person{age:24, name:"Bob"}
  • Define an anonymous struct, then initialize it
    1. P := struct{name string; age int}{"Amy",18}
    Let’s see a complete example.
  1. package main
  2. import "fmt"
  3. // define a new type
  4. type person struct {
  5. name string
  6. age int
  7. }
  8. // struct is passed by value
  9. // compare the age of two people, then return the older person and differences of age
  10. func Older(p1, p2 person) (person, int) {
  11. if p1.age > p2.age {
  12. return p1, p1.age - p2.age
  13. }
  14. return p2, p2.age - p1.age
  15. }
  16. func main() {
  17. var tom person
  18. tom.name, tom.age = "Tom", 18
  19. bob := person{age: 25, name: "Bob"}
  20. paul := person{"Paul", 43}
  21. tb_Older, tb_diff := Older(tom, bob)
  22. tp_Older, tp_diff := Older(tom, paul)
  23. bp_Older, bp_diff := Older(bob, paul)
  24. fmt.Printf("Of %s and %s, %s is older by %d years\n", tom.name, bob.name, tb_Older.name, tb_diff)
  25. fmt.Printf("Of %s and %s, %s is older by %d years\n", tom.name, paul.name, tp_Older.name, tp_diff)
  26. fmt.Printf("Of %s and %s, %s is older by %d years\n", bob.name, paul.name, bp_Older.name, bp_diff)
  27. }

embedded fields in struct

I’ve just introduced to you how to define a struct with field names and type. In fact, Go supports fields without names, but with types. We call these embedded fields.

When the embedded field is a struct, all the fields in that struct will implicitly be the fields in the struct in which it has been embedded.

Let’s see one example.

  1. package main
  2. import "fmt"
  3. type Human struct {
  4. name string
  5. age int
  6. weight int
  7. }
  8. type Student struct {
  9. Human // embedded field, it means Student struct includes all fields that Human has.
  10. specialty string
  11. }
  12. func main() {
  13. // instantiate and initialize a student
  14. mark := Student{Human{"Mark", 25, 120}, "Computer Science"}
  15. // access fields
  16. fmt.Println("His name is ", mark.name)
  17. fmt.Println("His age is ", mark.age)
  18. fmt.Println("His weight is ", mark.weight)
  19. fmt.Println("His specialty is ", mark.specialty)
  20. // modify mark's specialty
  21. mark.specialty = "AI"
  22. fmt.Println("Mark changed his specialty")
  23. fmt.Println("His specialty is ", mark.specialty)
  24. fmt.Println("Mark become old. He is not an athlete anymore")
  25. mark.age = 46
  26. mark.weight += 60
  27. fmt.Println("His age is", mark.age)
  28. fmt.Println("His weight is", mark.weight)
  29. }

2.4. struct - 图1

Figure 2.7 Embedding in Student and Human

We see that we can access the age and name fields in Student just like we can in Human. This is how embedded fields work. It’s very cool, isn’t it? Hold on, there’s something cooler! You can even use Student to access Human in this embedded field!

  1. mark.Human = Human{"Marcus", 55, 220}
  2. mark.Human.age -= 1

All the types in Go can be used as embedded fields.

  1. package main
  2. import "fmt"
  3. type Skills []string
  4. type Human struct {
  5. name string
  6. age int
  7. weight int
  8. }
  9. type Student struct {
  10. Human // struct as embedded field
  11. Skills // string slice as embedded field
  12. int // built-in type as embedded field
  13. specialty string
  14. }
  15. func main() {
  16. // initialize Student Jane
  17. jane := Student{Human: Human{"Jane", 35, 100}, specialty: "Biology"}
  18. // access fields
  19. fmt.Println("Her name is ", jane.name)
  20. fmt.Println("Her age is ", jane.age)
  21. fmt.Println("Her weight is ", jane.weight)
  22. fmt.Println("Her specialty is ", jane.specialty)
  23. // modify value of skill field
  24. jane.Skills = []string{"anatomy"}
  25. fmt.Println("Her skills are ", jane.Skills)
  26. fmt.Println("She acquired two new ones ")
  27. jane.Skills = append(jane.Skills, "physics", "golang")
  28. fmt.Println("Her skills now are ", jane.Skills)
  29. // modify embedded field
  30. jane.int = 3
  31. fmt.Println("Her preferred number is ", jane.int)
  32. }

In the above example, we can see that all types can be embedded fields and we can use functions to operate on them.

There is one more problem however. If Human has a field called phone and Student has a field with same name, what should we do?

Go use a very simple way to solve it. The outer fields get upper access levels, which means when you access student.phone, we will get the field called phone in student, not the one in the Human struct. This feature can be simply seen as field overloading.

  1. package main
  2. import "fmt"
  3. type Human struct {
  4. name string
  5. age int
  6. phone string // Human has phone field
  7. }
  8. type Employee struct {
  9. Human
  10. specialty string
  11. phone string // phone in employee
  12. }
  13. func main() {
  14. Bob := Employee{Human{"Bob", 34, "777-444-XXXX"}, "Designer", "333-222"}
  15. fmt.Println("Bob's work phone is:", Bob.phone)
  16. fmt.Println("Bob's personal phone is:", Bob.Human.phone)
  17. }