[Golang] Struct
此篇為各筆記之整理,非原創內容,資料來源可見下方連結與文後參考資料: 👍 Structures in Go (structs) @ medium > rungo
TL;DR
// create struct on the fly
foo := struct {
Hello string
}{
Hello: "World",
}
Anonymous Field:
type User struct {
firstName string
lastName string
birthDate string
createdAt time.Time
}
type Admin struct {
email string
password string
// anonymous / embedded field:直接帶入 User 裡面有的欄位
User
}
func main() {
a := Admin{}
a.firstName = "Aaron"
a.lastName = "Chen"
// 使用 anonymous/embedded field 可以直接取得該 nested struct 的值
// 這個行為稱作 Promoted
fmt.Println(a.firstName, a.lastName)
}
三種宣告 Person struct 的方式:
- 使用 new syntax:第二種和第三種寫法是一樣的
var user1 *Person // nil
user2 := &Person{} // {},user2.firstName 會是 ""
user3 := new(Person) // {},user3.firstName 會是 ""
structs 是在 GO 中的一種資料型態,它就類似 JavaScript 中的物件(Object)或是 Ruby 中的 Hash。
定義與使用基本的 struct
建立一個 person 型別,它本質上是 struct:
// STEP 1:建立一個 person 型別,它本質上是 struct
type Person struct {
firstName string
lastName string
}
// 等同於
type Person struct {
firstName, lastName string
}
有幾種不同的方式可以根據 struct 來建立變數的:
func main() {
// 方法一:根據資料輸入的順序決定誰是 firstName 和 lastName
alex := Person{"Alex", "Anderson"}
// 直接取得 struct 的 pointer
alex := &Person{"Alex", "Anderson"}
// 方法二(建議)
alex := Person{
firstName: "Alex",
lastName: "Anderson",
}
// 方法三:先宣告再賦值
var alex Person
alex.firstName = "Alex"
alex.lastName = "Anderson"
// 方法四
var alex = Person{
firstName: "Alex",
lastName: "Anderson",
}
}
輸出建立好的 struct:
// 如果希望一併輸出 field name 的話
fmt.Printf("%+v", alex) // {firstName:Alex lastName:Anderson}
fmt.Println(alex) // {Alex Anderson}
取值的方式:
alex.firstName // 不能用 alex["firstName"],因為 alex 是 struct,不是 map
alex.lastName
定義匿名的 struct(anonymous struct)
也可以不先宣告 struct 直接建立個 struct:
foo := struct {
Hello string
}{
Hello: "World",
}
當 pointer 指稱到的是 struct 時
當 pointer 指稱到的是 struct 時,可以直接使用這個 pointer 來對該 struct 進行設值和取值。在 golang 中可以直接使用 pointer 來修改 struct 中的欄位。一般來說,若想要透過 struct pointer(&v
)來修改該 struct 中的屬性,需要先解出其值(*p
)後使用 (*p).X = 10
,但這樣做太麻煩了,因此在 golang 中允許開發者直接使用 p.X
的方式來修改:
type Person struct {
name string
age int32
}
func main() {
p := &Person{
name: "Aaron",
}
// golang 中允許開發者直接使用 `p.age` 的方式來設值與取值
p.age = 10 // 原本應該要寫 (*p).X = 10
fmt.Printf("%+v", p) // {name:Aaron age:10}
}
另外,使用 struct pointer 時才可以修改到原本的物件,否則會複製一個新的:
func main() {
r1 := rectangle{"Green"}
// 複製新的,指稱到不同位置
r2 := r1
r2.color = "Pink"
fmt.Println(r2) // Pink
fmt.Println(r1) // Green
// 指稱到相同位置
r3 := &r1
r3.color = "Red"
fmt.Println(r3) // Red
fmt.Println(r1) // Red
}
在 struct 內關聯另一個 struct(nested struct)
在一個 struct 內可以包含另一個 struct:
// STEP 1:定義外層 struct
type person struct {
firstName string
lastName string
contact contactInfo
}
// STEP 2:定義內層 struct
type contactInfo struct {
email string
zipCode int
}
func main() {
// STEP 3:建立變數
jim := person{
firstName: "Jim",
lastName: "Party",
contact: contactInfo{
email: "jim@gmail.com",
zipCode: 94000,
},
}
alex := person{
firstName: "Alex",
lastName: "Anderson",
}
// STEP 4:印出變數
fmt.Printf("%+v\n", jim) // {firstName:Jim lastName:Party contact:{email:jim@gmail.com zipCode:94000}}
fmt.Println(jim) // {Jim Party {jim@gmail.com 94000}}
fmt.Printf("%+v\n", alex) // {firstName:Alex lastName:Anderson contact:{email: zipCode:0}}
fmt.Println(alex) // {Alex Anderson { 0}}
}
Struct field Tag(meta-data)
struct field tag 會在 struct 的 value 後面使用 backtick 來表示,例如 json:"name"
:
type User struct {
Name string `json:"name"`
Password string `json:"-"`
PreferredFish []string `json:"preferredFish,omitempty"`
CreatedAt time.Time `json:"createdAt"`
}
在 field tag 中還能帶入其他關鍵字,舉例來說:
omitempty
:指的是該欄位沒值的話,就不要顯示欄位名稱-
:表示忽略掉該欄位,Marshal 時該欄位不會出現在 JSON 中,Unmarshal 時該欄位也不會被處理
Anonymous fields
在 struct 中不一定要替欄位建立名稱,而是可以直接使用 data types,而 Go 會使用這個 data type 當作欄位名稱:
type AnonymousField struct {
string // 相似於 string string
bool // 相似於 bool bool
int // 相似於 int int
}
func main() {
anonymousField := AnonymousField{
"person", true, 30,
}
fmt.Printf("%+v", anonymousField) // {string:person bool:true int:30}
}
Function Fields
struct 中的 field 也可以是 function
type GetDisplayNameType func(string, string) string
type Person struct {
FirstName, LastName string
GetDisplayName GetDisplayNameType
}
func main() {
p := Person{
FirstName: "Aaron",
LastName: "Chen",
GetDisplayName: func(firstName, lastName string) string {
return firstName + " " + lastName
},
}
displayName := p.GetDisplayName(p.FirstName, p.LastName)
fmt.Println(displayName) // Aaron Chen
}
Promoted fields / Anonymous / Embedded
- Promoted fields and methods in Go @ medium > golangspec
- Structures in Go (structs) @ medium > rungo
定義 Promoted fields 的 struct
在 Golang 中 struct 的 fields name 可以省略,沒有 field name 的 name 被稱作 anonymous 或 embedded。在這種情況下,會直接使用 「Type 的名稱」來當作 field name:
// https://medium.com/golangspec/promoted-fields-and-methods-in-go-4e8d7aefb3e3
type Person struct {
name string
age int32
}
func (p Person) IsAdult() bool {
return p.age >= 18
}
type Employee struct {
position string
}
func (e Employee) IsManager() bool {
return e.position == "manager"
}
type Record struct {
Person
Employee
}
func main() {
fmt.Printf("%+v", record)
}
如果 nested anonymous struct 中的欄位和其 parent struct 的欄位名稱有衝突時,則該欄位不會被 promoted。
在 Promoted fields 中設值
對於 Promoted fields 來說,可以直接使用 .
來設值:
// 正確:可以直接使用 . 來對 promoted fields 設值
func main() {
record := Record{}
record.name = "record"
record.age = 29
record.position = "software engineer"
fmt.Printf("%+v", record)
}
對於 anonymous (embedded) fields 的欄位(field)或方法(method)稱作 prompted,它們就像一般的欄位一樣,但是不能跳過 Type 的名稱直接用 struct literals 的方式來賦值:
// 錯誤用法:不能在未明確定義 promoted fields 名稱的情況下,使用 struct literals 設值
func main() {
record := Record{
name: "record",
age: 29,
position: "software engineer",
}
fmt.Printf("%+v", record)
}
如此會出現錯誤訊息:
cannot use promoted field Person.name in struct literal of type Record
cannot use promoted field Person.age in struct literal of type Record
cannot use promoted field Employee.position in struct literal of type Record
但如果你是明確的定義 embedded 的結構的話,是可以的:
// 正確:明確定義要設值的 promoted fields 名稱為何
func main() {
record := Record{
Person: Person{
name: "record",
age: 29,
},
Employee: Employee{
position: "software engineer",
},
}
fmt.Printf("%+v", record)
}
在 Promoted fields 中取值
不論有沒有使用明確的 promoted fields 名稱,都可以取值:
func main() {
record := Record{
Person: Person{
name: "record",
age: 29,
},
Employee: Employee{
position: "software engineer",
},
}
// 不論有沒有使用明確的 promoted fields 名稱,都可以取值
fmt.Println("age", record.age) // 29
fmt.Println("Person.age", record.Person.age) // 29
fmt.Println("position", record.position) // software engineer
fmt.Println("Employee.position", record.Employee.position) // software engineer
}
範例程式碼
Person 有 Name
且可以 Introduce
,而 Saiyan
是 Person
,因此它也有 Name
且可以 Introduce
:
// STEP 1:建立 Person struct 與其 Method
type Person struct {
Name string
}
func (p *Person) Introduce() {
fmt.Printf("Hi, I'm %s\n", p.Name)
}
// STEP 2:建立 Saiyan struct,並將 Person embed 在內
// 意思是 Saiyan 是 Person ,而不是 Saiyan「有一個」Person
type Saiyan struct {
*Person
Power int
}
func main() {
// STEP 3:建立 goku
goku := &Saiyan{
Person: &Person{"Goku"},
Power: 9001,
}
// STEP 4:可以直接使用 goku.Name,也可以使用 goku.Person.Name
fmt.Println(goku.Name) // Goku
fmt.Println(goku.Person.Name) // Goku
// STEP 5:方法在使用時也一樣
goku.Introduce() // Hi, I'm Goku
goku.Person.Introduce() // Hi, I'm Goku
}
Interface Fields (Nested interface)
struct 中的欄位也可以是 interface,以 Employee
這個 struct 來說,其中的 salary
欄位其型別是 Salaried
這個 interface,也就是是說 salary
這個欄位的值,一定要有實作出 Salaried
的方法,如此 salary 才會符合該 interface 的 type:
- struct 中的
{ salary Salaried
} 表示 salary 要符合Salaried
interface type - 要符合該 interface type,表示
salary
要實作Salaried
interface 中所定義的 method signatures - 在定義
ross
變數時,因為 Salary 這個 struct 已經實作了 Salaried,因此可以放到salary
這個欄位中
type Salaried interface {
getSalary() int
}
type Salary struct {
basic, insurance, allowance int
}
func (s Salary) getSalary() int {
return s.basic + s.insurance + s.allowance
}
type Employee struct {
firstName, lastName string
salary Salaried // 只要 salary 實作了 Salaried,就可以 Salaried interface type
}
func main() {
ross := Employee{
firstName: "Ross",
lastName: "Geller",
// 因為 Salary struct 已經實作了 Salaried,因此可以當作 salary 的欄位值
salary: Salary{
1100, 50, 50,
},
}
fmt.Println("Ross's salary is", ross.salary.getSalary())
}