你有對象類,我有結構體,Go lang1.18入門精鍊教程,由白丁入鴻儒,go lang結構體(struct)的使用EP06
- 2022 年 8 月 10 日
- 筆記
再續前文,在面向對象層面,Python做到了超神:萬物皆為對象,而Ruby,則乾脆就是神:飛花摘葉皆可對象。二者都提供對象類操作以及繼承的方式為面向對象張目,但Go lang顯然有一些特立獨行,因為它沒有傳統的類,也沒有繼承,取而代之的是結構和組合的方式,也就是結構體(struct)的方式來組織代碼,達到類似類的效果。
結構體struct的聲明
在 Go lang中使用下面的語法是對結構體的聲明:
type struct_name struct {
attribute_name1 attribute_type
attribute_name2 attribute_type
...
}
假設定義一個名為 Lesson(課程) 的結構體:
type Lesson struct {
name string //名稱
target string //學習目標
spend int //學習花費時間
}
這裡聲明了一個結構體類型 Lesson ,它有 name 、 target 和 spend 三個屬性,相當於Python中類的私有屬性。
也可以把相同類型的屬性聲明在同一行,這樣可以使結構體變得更加緊湊:
type Lesson2 struct {
name, target string
spend int
}
Lesson 稱為命名的結構體(Named Structure) ,這裡 Lesson 作為一種新的數據類型而存在,而它可以用於創建 Lesson 類型的結構體變量。
此外,聲明結構體時也可以不用聲明一個新類型,這樣的結構體類型稱為匿名結構體(Anonymous Structure) ,可以理解為結構體變量:
var MyLesson struct {
name, target string
spend int
}
結構體struct的創建
聲明了結構體之後,我們可以根據聲明好的結構體類型來創建結構體,這個過程有點像Python語言中類的實例化:
import "fmt"
type Lesson struct {
name, target string
spend int
}
func main() {
// 使用字段名創建結構體
lesson1 := Lesson{
name: "go lang 1.18",
target: "學習Go lang,並完成web開發任務",
spend: 1,
}
// 不使用字段名創建結構體
lesson2 := Lesson{"go lang 1.18", "學習Go lang,並完成web開發任務", 1}
fmt.Println("lesson1 ", lesson1)
fmt.Println("lesson2 ", lesson2)
}
程序返回:
lesson1 {go lang 1.18 學習Go lang,並完成web開發任務 1}
lesson2 {go lang 1.18 學習Go lang,並完成web開發任務 1}
這裡字段名可以做省略操作,但要注意傳遞順序。
此外,也可以創建匿名結構體:
package main
import "fmt"
func main() {
// 創建匿名結構體變量
mylesson := struct {
name, target string
spend int
}{
name: "Go lang 1.18",
target: "學習go lang,完成web需求",
spend: 1,
}
fmt.Println("mylesson ", mylesson)
}
當定義好的結構體沒有被顯式賦值時,結構體的字段將會默認賦為相應類型的零值:
package main
import "fmt"
type Lesson struct {
name, target string
spend int
}
func main() {
// 不初始化結構體
var lesson = Lesson{}
fmt.Println("lesson ", lesson)
}
程序返回:
lesson { 0}
假設某個或者某幾個字段沒有賦值,也會默認賦值為對應基本數據類型的零值:
package main
import "fmt"
type Lesson struct {
name, target string
spend int
}
func main() {
// 為結構體指定字段賦初值
var lesson = Lesson{
name: "go lang 1.18",
}
// 上面的結構體變量 lesson 只初始化了 name 字段, 其他字段沒有初始化,所以會被初始化為零值
fmt.Println("lesson ", lesson)
}
程序返回:
lesson {go lang 1.18 0}
結構體struct的屬性與指針
通過點操作符 . 可以訪問結構體的屬性:
package main
import "fmt"
type Lesson struct {
name, target string
spend int
}
func main() {
var lesson = Lesson{
name: "go lang 1.18",
}
fmt.Println("lesson name ", lesson.name)
}
程序返回:
lesson name go lang 1.18
也可以使用點操作符 . 對結構體的字段進行賦值操作:
package main
import "fmt"
type Lesson struct {
name, target string
spend int
}
func main() {
var lesson = Lesson{
name: "go lang 1.18",
}
fmt.Println("lesson name ", lesson.name)
lesson.name = "Python 3.11"
fmt.Println("lesson name ", lesson.name)
}
程序返回:
lesson name go lang 1.18
lesson name Python 3.11
需要注意的是,賦值變量和結構體屬性的基本數據類型要一致。
在前一篇:借問變量何處存,牧童笑稱用指針,Go lang1.18入門精鍊教程,由白丁入鴻儒,go lang類型指針(Pointer)的使用EP05我們使用了指針來操作變量,指針也可以指向結構體:
package main
import "fmt"
type Lesson struct {
name, target string
spend int
}
func main() {
lesson := &Lesson{"go lang 1.18", "完成對應web需求", 1}
fmt.Println("lesson name: ", (*lesson).name)
fmt.Println("lesson name: ", lesson.name)
}
程序返回:
lesson name: go lang 1.18
lesson name: go lang 1.18
lesson是一個指向結構體 Lesson 的指針,上面用 (*lesson).name 訪問 lesson的 name 字段,lesson.name 是代替 (*lesson).name 的解引用訪問。
在創建結構體時,屬性可以只有類型沒有屬性名,這種屬性稱為匿名字段(Anonymous Field) :
package main
import "fmt"
type Lesson struct {
string
int
}
func main() {
lesson := Lesson{"go lang 1.18", 1}
fmt.Println("lesson ", lesson)
fmt.Println("lesson string: ", lesson.string)
fmt.Println("lesson int: ", lesson.int)
}
程序返回:
lesson {go lang 1.18 1}
lesson string: go lang 1.18
lesson int: 1
這裡程序結構體定義了兩個匿名字段,雖然這兩個字段沒有字段名,但匿名字段的名稱默認就是它的類型。所以上面的結構體 Lesoon 有兩個名為 string 和 int 的字段,同樣需要注意順序和字段數據類型的匹配問題。
嵌套結構體
結構體本身也支持複合的嵌套結構:
package main
import "fmt"
type Author struct {
name string
}
type Lesson struct {
name, target string
spend int
author Author
}
func main() {
lesson := Lesson{
name: "go lang 1.18",
spend: 1,
}
lesson.author = Author{
name: "佚名",
}
fmt.Println("lesson name:", lesson.name)
fmt.Println("lesson spend:", lesson.spend)
fmt.Println("lesson author name:", lesson.author.name)
}
程序返回:
lesson name: go lang 1.18
lesson spend: 1
lesson author name: 佚名
這裡結構體Author本身作為結構體Lesson的一個屬性而存在,賦值時,通過父結構體直接調用子結構體名稱即可。
如果結構體中有匿名的結構體類型字段,則該匿名結構體里的字段就稱為提升字段(Promoted Fields) 。這是因為提升字段就像是屬於外部結構體一樣,可以用外部結構體直接訪問:
package main
import (
"fmt"
)
type Address struct {
city, state string
}
type Person struct {
name string
age int
Address
}
func main() {
var p Person
p.name = "Naveen"
p.age = 50
p.Address = Address{
city: "Chicago",
state: "Illinois",
}
fmt.Println("Name:", p.name)
fmt.Println("Age:", p.age)
fmt.Println("City:", p.city) //city is promoted field
fmt.Println("State:", p.state) //state is promoted field
}
系統返回:
Name: Naveen
Age: 50
City: Chicago
State: Illinois
如果我們把 Person 結構體中的字段 address 直接用匿名字段 Address 代替, Address 結構體的字段例如 city 就不用像 p.address.city這樣訪問,而是使用 p.address 就能訪問 Address 結構體中的 address字段。現在結構體 Address 有city字段,訪問字段就像在 Person 里直接聲明的一樣,因此我們稱之為提升字段,說白了就是把子結構體的字段提升為父結構體的字段,但是定義還是在子結構體之中。
假設結構體的全部屬性都是可以比較的,那麼結構體也是可以比較的,那樣的話兩個結構體將可以使用 == 或 != 運算符進行比較。可以通過==運算符或 DeeplyEqual()函數比較兩個結構相同的類型並包含相同的字段值:
package main
import (
"fmt"
)
type name struct {
firstName string
lastName string
}
func main() {
name1 := name{"Steve", "Jobs"}
name2 := name{"Steve", "Jobs"}
if name1 == name2 {
fmt.Println("name1 and name2 are equal")
} else {
fmt.Println("name1 and name2 are not equal")
}
name3 := name{firstName:"Steve", lastName:"Jobs"}
name4 := name{}
name4.firstName = "Steve"
if name3 == name4 {
fmt.Println("name3 and name4 are equal")
} else {
fmt.Println("name3 and name4 are not equal")
}
}
程序返回:
name1 and name2 are equal
name3 and name4 are not equal
如果結構變量包含的字段是不可比較的,那麼結構變量是不可比較的:
package main
import (
"fmt"
)
type image struct {
data map[int]int
}
func main() {
image1 := image{data: map[int]int{
0: 155,
}}
image2 := image{data: map[int]int{
0: 155,
}}
if image1 == image2 {
fmt.Println("image1 and image2 are equal")
}
}
程序報錯:
# command-line-arguments
.\test.go:18:5: invalid operation: image1 == image2 (struct containing map[int]int cannot be compared)
由此可知,結構體的比較可以理解為其屬性的批量比較。
結構體綁定方法
在 Go lang中無法在結構體內部定義方法,這一點與 C 語言類似:
package main
import "fmt"
// Lesson 定義一個名為 Lesson 的結構體
type Lesson struct {
name, target string
spend int
}
// ShowLessonInfo 定義一個與 Lesson 的綁定的方法
func (l Lesson) ShowLessonInfo() {
fmt.Println("name:", l.name)
fmt.Println("target:", l.target)
}
func main() {
l := Lesson{
name: "go lang 1.1 8",
}
l.ShowLessonInfo()
}
程序返回:
name: go lang 1.1 8
target:
這裡定義了一個與結構體 Lesson 綁定的方法 ShowLessonInfo() ,其中 ShowLessonInfo 是方法名, (l Lesson) 表示將此方法與 Lesson 的實例綁定,這在 Go lang中稱為接收者,而 l 表示實例本身,相當於 Python 中的 self ,在方法內可以使用實例本身.屬性名稱來訪問實例屬性。
如果綁定結構體的方法中要改變實例的屬性時,必須使用指針作為方法的接收者:
package main
import "fmt"
// Lesson 定義一個名為 Lesson 的結構體
type Lesson struct {
name, target string
spend int
}
// ShowLessonInfo 定義一個與 Lesson 的綁定的方法
func (l Lesson) ShowLessonInfo() {
fmt.Println("spend:", l.spend)
}
// AddTime 定義一個與 Lesson 的綁定的方法,使 spend 值加 n
func (l *Lesson) AddTime(n int) {
l.spend = l.spend + n
}
func main() {
lesson13 := Lesson{
spend: 1,
}
fmt.Println("添加add方法前")
lesson13.ShowLessonInfo()
lesson13.AddTime(5)
fmt.Println("添加add方法後")
lesson13.ShowLessonInfo()
}
程序返回:
添加add方法前
spend: 1
添加add方法後
spend: 6
結語
大抵上,Go lang的結構體就是對象類的變種,雖然並沒有顯性的繼承操作,但是通過嵌套結構體和提升字段兩種方式,也能達到「繼承」的效果,結構體的最終目的和效果與對象類並無二致,類比的話,有點像電腦散熱的兩種方式:風冷和水冷,我們不能說哪一種方式更好或者不好,只要這種方式可以完成項目中的需求即可,不管黑貓白貓,只要能抓到耗子,就是好貓。


