Calvert's murmur

Golang struct 教學與範例

2019-11-15

約 5596 字 / 需 31 分鐘閱讀

原文:CalliCoderGolang 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,零表示所有欄位均設定為其對應的零值。因此,欄位 FirstNameLastName 設定為 "",且 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);
}

如你所見,名稱 addressmarried 是未匯出的,無法從 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 的基礎。但是還有很多值得探索的地方,如結構的方法、結構的組成、嵌入欄位、提升欄位等。我將在以後的文章中介紹這些主題。

Tags: Golang