原文:CalliCoder — Golang Structs Tutorial with Examples
struct
是使用者定義的型別,包含已命名欄位/屬性的集合。它用於將相關資料分組在一起形成一個單位。任何具有一組屬性的現實世界實體都可以使用結構表示。
如果你有物件導向的背景知識,你可以將 struct
視為支援複合但不支援繼承的輕量級類別。
定義 struct 型別
你可以像這樣定義新的 struct
型別:
type Person struct {
FirstName string
LastName string
Age int
}
type
關鍵字引進了新型別。其後是型別(Person
)的名稱和關鍵字 struct
,表示我們正在定義一個 struct
。struct 包含了一個在大括號內的欄位
列表。每個欄位都有名稱和型別。
注意,你可以像這樣折疊相同型別的欄位:
type Person struct {
FirstName, LastName string
Age int
}
宣告和初始化 struct
宣告 struct 型別的變數
如同其他資料型別一樣,你可以像這樣宣告 struct
型別的變數:
// Declares a variable of type 'Person'
var p Person // All the struct fields are initialized with their zero value
上面的程式碼建立了一個類型為 Person
的變數,預設情況下將其設為零。對於 struct,零表示所有欄位均設定為其對應的零值。因此,欄位 FirstName
和 LastName
設定為 ""
,且 Age
設定為 0
。
初始化 struct
你可以使用 struct 定數來初始化一個 struct
型別的變數:
// Initialize a struct by supplying the value of all the struct fields.
var p = Person{"Rajeev", "Singh", 26}
注意,你需要按照在 struct
中宣告他們的順序來傳遞欄位值。另外,你不能使用以下語法僅初始化一部分欄位:
var p = Person{"Rajeev"} // Compiler Error: too few values in struct initializer
初始化 struct 時命名欄位
Go 也支援用來初始化 struct 的 name: value
語法(使用此語法時,欄位順序無關緊要)。
var p = Person{FirstName: "Rajeev", LastName: "Singh", Age: 25}
你可以用換行符號分隔多個欄位,以提高可讀性(在這種情況下,必須使用逗號結尾):
var p = Person{
FirstName: "John",
LastName: "Snow",
Age: 45,
}
name: value
語法允許你允許你僅初始化一部分欄位。所有未初始化的欄位均設定為其對應的零值:
var p = Person{FirstName: "Alien"} // LastName: "", Age: 0
var p = Person{} // FirstName: "", LastName: "", Age: 0
完整範例:定義和初始化 struct 型別
package main
import "fmt"
// Defining a struct type
type Person struct {
FirstName string
LastName string
Age int
}
func main() {
// Declaring a variable of a `struct` type
var p Person // // All the struct fields are initialized with their zero value
fmt.Println(p)
// Declaring and initializing a struct using a struct literal
p1 := Person{"Rajeev", "Singh", 26}
fmt.Println("Person1: ", p1)
// Naming fields while initializing a struct
p2 := Person{
FirstName: "John",
LastName: "Snow",
Age: 45,
}
fmt.Println("Person2: ", p2)
// Uninitialized fields are set to their corresponding zero-value
p3 := Person{FirstName: "Robert"}
fmt.Println("Person3: ", p3)
}
# Output
{ 0}
Person1: {Rajeev Singh 26}
Person2: {John Snow 45}
Person3: {Robert 0}
存取 struct 的欄位
你可以使用點(.
) 運算元來存取 struct
的各個欄位:
package main
import "fmt"
type Car struct {
Name, Model, Color string
WeightInKg float64
}
func main() {
c := Car{
Name: "Ferrari",
Model: "GTC4",
Color: "Red",
WeightInKg: 1920,
}
// Accessing struct fields using the dot operator
fmt.Println("Car Name: ", c.Name)
fmt.Println("Car Color: ", c.Color)
// Assigning a new value to a struct field
c.Color = "Black"
fmt.Println("Car: ", c)
}
# Output
Car Name: Ferrari
Car Color: Red
Car: {Ferrari GTC4 Black 1920}
指向 struct 的指標
你可以使用 &
運算元取得指向 struct
的指標:
package main
import "fmt"
type Student struct {
RollNumber int
Name string
}
func main() {
// instance of student struct type
s := Student{11, "Jack"}
// Pointer to the student struct
ps := &s
fmt.Println(ps)
// Accessing struct fields via pointer
fmt.Println((*ps).Name)
fmt.Println(ps.Name) // Same as above: No need to explicitly dereference the pointer
ps.RollNumber = 31
fmt.Println(ps)
}
# Output
&{11 Jack}
Jack
Jack
&{31 Jack}
如以上範例所示,Go 允許你透過指標直接存取 struct
的欄位,而不用明確的解參考。
使用內建的 new()
函數建立一個 struct 並取得指向它的指標
你也可以使用內建的 new()
函數來建立 struct
的實體。new()
函數分配足夠的記憶體來符合所有的 struct
欄位,將他們各自設為零值,並返回指向新分配的 struct
的指標:
package main
import "fmt"
type Employee struct {
Id int
Name string
}
func main() {
// You can also get a pointer to a struct using the built-in new() function
// It allocates enough memory to fit a value of the given struct type, and returns a pointer to it
pEmp := new(Employee)
pEmp.Id = 1000
pEmp.Name = "Sachin"
fmt.Println(pEmp)
}
# Output
&{1000 Sachin}
匯出與未匯出的 struct 和 struct 欄位
任何以大寫字母開頭的 struct
型別都會被匯出,並且可以從外部的 package 中存取。同樣的,任何以大寫字母開頭的 struct
欄位都會被匯出。
相反的,所有以小寫字母開頭的名稱僅在同一 package 中可見。
讓我們來看個範例。細想以下 Go 程式的 package 階層:
$GOPATH/src
example
main
main.go
model
address.go
customer.go
customer.go
package model
type Customer struct { // exported struct type
Id int // exported field
Name string // exported field
addr address // unexported field (only accessible inside package `model`)
married bool // unexported field (only accessible inside package `model`)
}
address.go
package model
// Unexported struct (only accessible inside package `model`)
type address struct {
houseNo, street, city, state, country string
zipCode int
}
然後,這是主要程式 main.go:
package main
import (
"fmt"
"example/model"
)
func main() {
c := model.Customer{
Id: 1,
Name: "Rajeev Singh",
}
c.married = true // Error: can not refer to unexported field or method
a := model.address{} // Error: can not refer to unexported name
fmt.Println("Programmer = ", c);
}
如你所見,名稱 address
和 married
是未匯出的,無法從 main
package 中存取。
struct 是值型別
struct 是值型別。當你將一個 struct
變數分派給另一個變數時,會建立並分派一個新的 struct
副本。同樣的,當你將 struct
傳遞給另一個函數時,該函數將取得自己的 struct
副本。
package main
import "fmt"
type Point struct {
X float64
Y float64
}
func main() {
// Structs are value types.
p1 := Point{10, 20}
p2 := p1 // A copy of the struct `p1` is assigned to `p2`
fmt.Println("p1 = ", p1)
fmt.Println("p2 = ", p2)
p2.X = 15
fmt.Println("\nAfter modifying p2:")
fmt.Println("p1 = ", p1)
fmt.Println("p2 = ", p2)
}
# Output
p1 = {10 20}
p2 = {10 20}
After modifying p2:
p1 = {10 20}
p2 = {15 20}
struct 等式
如果兩個 struct
變數的所有對應欄位都相等,則他們相等:
package main
import "fmt"
type Point struct {
X float64
Y float64
}
func main() {
// Two structs are equal if all their corresponding fields are equal.
p1 := Point{3.4, 5.2}
p2 := Point{3.4, 5.2}
if p1 == p2 {
fmt.Println("Point p1 and p2 are equal.")
} else {
fmt.Println("Point p1 and p2 are not equal.")
}
}
# Output
Point p1 and p2 are equal.
結論
在本文中,你學到了 struct 的基礎。但是還有很多值得探索的地方,如結構的方法、結構的組成、嵌入欄位、提升欄位等。我將在以後的文章中介紹這些主題。